BI Dude
BI Dude

Reputation: 2016

How to Optimise INSERT Statement

I'm generating [Dim_Calendar] table for the data warehouse. I've developed a query below that takes 29 seconds to execute & inserts 27k rows. I would like to optimize it if it is possible. I do understand that while loop is not helping performance and I do not know how to replace it to achieve the same results.

I'm using SQL Server 2012 BI edition.

IF EXISTS(SELECT * FROM sys.indexes WHERE name='PK_Dim_Calendar_1' AND object_id = OBJECT_ID('Dim_Calendar'))
BEGIN
    ALTER TABLE [dbo].[Dim_Calendar] DROP CONSTRAINT [PK_Dim_Calendar_1]
END

SET DATEFIRST 1--Sets Monday as 1st day of the week.
DECLARE @today DATETIME = ( SELECT  GETDATE())
DECLARE @start DATETIME = DATEADD(dd, 1, (SELECT Max(date) FROM Dim_Calendar))

IF @start IS NULL
BEGIN
    INSERT INTO [dbo].[Dim_Calendar]
    VALUES (19000101, '1900-01-01', 'Monday',   1   ,'Unknown', 1, 'January', 1900, 1)
    SET @start = '1940-01-01'
END


DECLARE @end DATETIME   = (SELECT DATEFROMPARTS(YEAR(@today), 12, 31))


WHILE @start <= @end
    BEGIN

        INSERT INTO [dbo].[Dim_Calendar]
            SELECT
                YEAR(@start) * 10000 + MONTH(@start) * 100 + DAY(@start)
                ,@start
                ,DATENAME(dw, @start)
                ,DATEPART(wk, @start)
                ,'w/c ' + CONVERT(char(8), DATEADD(dd, 1 - DATEPART(dw, @start), @start), 3)
                ,DATEPART(mm, @start)
                ,DATENAME(MONTH, @start)
                ,YEAR(@start)
                ,DATEPART(QQ, @start)
        SET @start = DATEADD(dd, 1, @start)
    END


ALTER TABLE [dbo].[Dim_Calendar] ADD  CONSTRAINT [PK_Dim_Calendar_1] PRIMARY KEY CLUSTERED 
(
    [FullDateID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

Upvotes: 1

Views: 146

Answers (3)

StackUser
StackUser

Reputation: 5398

Try this

IF EXISTS(SELECT *
          FROM   sys.indexes
          WHERE  name = 'PK_Dim_Calendar_1'
                 AND object_id = OBJECT_ID('Dim_Calendar'))
  BEGIN
      ALTER TABLE [dbo].[Dim_Calendar]
        DROP CONSTRAINT [PK_Dim_Calendar_1]
  END

SET DATEFIRST 1--Sets Monday as 1st day of the week.
DECLARE @today DATETIME = (SELECT GETDATE())
DECLARE @start DATETIME = DATEADD(dd, 1, (SELECT Max(date)
                  FROM   Dim_Calendar))

IF @start IS NULL
  BEGIN
      INSERT INTO [dbo].[Dim_Calendar]
      VALUES      (19000101,
                   '1900-01-01',
                   'Monday',
                   1,
                   'Unknown',
                   1,
                   'January',
                   1900,
                   1)

      SET @start = '1940-01-01'
  END

DECLARE @end DATETIME = (SELECT DATEFROMPARTS(YEAR(@today), 12, 31));

WITH T(start)
     AS (SELECT @start
         UNION ALL
         SELECT start + 1
         FROM   T
         WHERE  T.start < @end)
INSERT INTO [dbo].[Dim_Calendar]
SELECT YEAR(start) * 10000 + MONTH(start) * 100 + DAY(start),
       start,
       DATENAME(dw, start),
       DATEPART(wk, start),
       'w/c '
       + CONVERT(CHAR(8), DATEADD(dd, 1 - DATEPART(dw, start), start), 3),
       DATEPART(mm, start),
       DATENAME(MONTH, start),
       YEAR(start),
       DATEPART(QQ, start)
FROM   T
OPTION (MAXRECURSION 0);

ALTER TABLE [dbo].[Dim_Calendar]
  ADD CONSTRAINT [PK_Dim_Calendar_1] PRIMARY KEY CLUSTERED ( [FullDateID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

GO 

Upvotes: 0

Steve Ford
Steve Ford

Reputation: 7763

This should work for you (quick too):

;WITH [dates] 
AS 
   (SELECT @start AS [date] 
    UNION ALL 
    SELECT DATEADD(d, 1, [date]) AS [date] 
    FROM [dates] 
    WHERE [date] < @end) 

SELECT 
      YEAR([date]) * 10000 + MONTH([date]) * 100 + DAY([date])
                ,[date]
                ,DATENAME(dw, [date])
                ,DATEPART(wk, [date])
                ,'w/c ' + CONVERT(char(8), DATEADD(dd, 1 - DATEPART(dw, [date]), [date]), 3)
                ,DATEPART(mm, [date])
                ,DATENAME(MONTH, [date])
                ,YEAR([date])
                ,DATEPART(QQ, [date]) 
FROM [dates] 
OPTION (MAXRECURSION 32747);

Upvotes: 2

Panagiotis Kanavos
Panagiotis Kanavos

Reputation: 131722

The query is slow because you are using a loop to create values one at a time.

When dealing with value sequences it's useful to have a 'Numbers' table with numbers from 1 to however many items you want. By selecting or joining with that table you can generate sequences, identify gaps etc. Aaron Bertrand wrote a series of articles on how to create a Numbers table and use it to create a set of dates.

Assuming you already have such a Numbers(n), creating a Calendar table is as simple as:

DECLARE @start DATE = '2005-07-01'; 
DECLARE @end DATE = DATEADD(DAY, -1, DATEADD(YEAR, 30, @start));

DECLARE @days int = DATEDIFF(DAY, @start, @end) + 1

SELECT TOP (@days) 
  d = CONVERT(DATE, DATEADD(DAY, n-1, @start))
INTO dbo.Calendar
FROM dbo.Numbers ORDER BY n;

In your case the SELECT part would be something like:

;WITH Dates (d)
AS (
    SELECT TOP (@days) 
        d = CONVERT(DATE, DATEADD(DAY, n-1, @start))
    FROM dbo.Numbers 
    ORDER BY d)
 SELECT
    YEAR(d) * 10000 + MONTH(d) * 100 + DAY(d)
    ,d
    ,DATENAME(dw, d)    ,DATEPART(wk, d)
    ,'w/c ' + CONVERT(char(8), DATEADD(dd, 1 - DATEPART(dw, d), d), 3)
    ,DATEPART(mm, d)
    ,DATENAME(MONTH, d)
    ,YEAR(d)
    ,DATEPART(QQ, d)
from Dates

To generate the Numbers table you can use the following statement:

SELECT TOP (1000000) n = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id]))
INTO dbo.Numbers
FROM sys.all_objects AS s1 CROSS JOIN sys.all_objects AS s2
OPTION (MAXDOP 1);

CREATE UNIQUE CLUSTERED INDEX n ON dbo.Numbers(n)

Upvotes: 2

Related Questions