Using maxrecursion in INSERT query - sql-server

Thank you for looking at this.
I have two CTE tables that generate duty times, the calculations are convoluted so wont bother you with them.
Here is the SQL (Simplified)
DECLARE #StartDate Datetime -- Start Date for the Duties to be generated
DECLARE #PatternStartPoint int --Start Point in Pattern eg 3 -
DECLARE #PatternSize int --The length of the shift pattern
DECLARE #User int
SET #User = 1111;
SET #PatternSize = 3;
SET #DaysRequired=16; --Testing assume 16
SET #StartDate = DATEADD(dd, 0, DATEDIFF(dd, 0, GETDATE())); --for testing use today
SET #PatternStartPoint =3;
--Get Job Duty Pattern
WITH Pattern (P_DayNumber, P_DayType, P_Start, P_End)
AS
(
Select D_DayNumber,D_Type,D_Start,D_End
from dbo.Tbl_Jobs as J
join dbo.Tbl_Patterns as P
on J.J_PatternID = P.Pattern_ID
join dbo.Tbl_PatternDetails as D
on P.Pattern_ID=d.D_Pattern_ID
)
,
--Get DateList
Datelist (MyDate, DayNumber) AS
(
SELECT #StartDate AS MyDate, #PatternStartPoint as DayNumber
UNION ALL
Select MyDate + 1, (DayNumber+1) - (CAST((Daynumber+1)/(#PatternSize+.01) as int)*#PatternSize) --% (#PatternSize)
FROM Datelist
WHERE MyDate < (#Startdate + #DaysRequired)-1
)
INSERT INTO [IDAHO].[dbo].[Appointments]
([Subject]
,[Description]
,[Start]
,[End]
,[RoomID]
,[UserID]
)
(
SELECT
'Standard Work Pattern'
,'IDAHO Generated on ' + CONVERT(nvarchar(20),getdate())
,mydate + p.P_Start
,mydate + p.P_End
,1
,#User as A_User
FROM Datelist as d
join Pattern as p
on p.P_DayNumber=d.DayNumber
Where mydate + p.P_Start is not null
OPTION (MAXRECURSION 0)
)
I am getting an error with the MAXRECURSION line, if I remove the line and try to generate more than 100 'Duties' it errors (being the default value).
If I Change the last statement to a simple select it seems to work??? as shown below
DECLARE #StartDate Datetime -- Start Date for the Duties to be generated
DECLARE #PatternStartPoint int --Start Point in Pattern eg 3 -
DECLARE #PatternSize int --The length of the shift pattern
DECLARE #DaysRequired int --The length of the shift pattern
DECLARE #User int
SET #User=1111;
SET #PatternSize = 3;
SET #DaysRequired=160; --Testing assume 16
SET #StartDate = DATEADD(dd, 0, DATEDIFF(dd, 0, GETDATE())); --for testing use today
SET #PatternStartPoint =3;
--Get Job Duty Pattern
WITH Pattern (P_DayNumber, P_DayType, P_Start, P_End)
AS
(
Select D_DayNumber,D_Type,D_Start,D_End
from dbo.Tbl_Jobs as J
join dbo.Tbl_Patterns as P
on J.J_PatternID = P.Pattern_ID
join dbo.Tbl_PatternDetails as D
on P.Pattern_ID=d.D_Pattern_ID
)
,
--Get DateList
Datelist (MyDate, DayNumber) AS
(
SELECT #StartDate AS MyDate, #PatternStartPoint as DayNumber
UNION ALL
Select MyDate + 1, (DayNumber+1) - (CAST((Daynumber+1)/(#PatternSize+.01) as int)*#PatternSize) --% (#PatternSize)
FROM Datelist
WHERE MyDate < (#Startdate + #DaysRequired)-1
)
SELECT
'Standard Work Pattern'
,'IDAHO Generated on ' + CONVERT(nvarchar(20),getdate())
,mydate + p.P_Start
,mydate + p.P_End
,1
,#User as A_User
FROM Datelist as d
join Pattern as p
on p.P_DayNumber=d.DayNumber
Where mydate + p.P_Start is not null
OPTION (MAXRECURSION 0)
Have I put the command in the wrong place?
Why does it work with select but not insert?
Thank you

I think its because the CTE doesn't finally get assembled into a result set until the final select is run, which is why tour second query works. To make the insert work, try:
DECLARE #StartDate Datetime -- Start Date for the Duties to be generated
DECLARE #PatternStartPoint int --Start Point in Pattern eg 3 -
DECLARE #PatternSize int --The length of the shift pattern
DECLARE #DaysRequired int --The length of the shift pattern
DECLARE #User int
SET #User=1111;
SET #PatternSize = 3;
SET #DaysRequired=160; --Testing assume 16
SET #StartDate = DATEADD(dd, 0, DATEDIFF(dd, 0, GETDATE())); --for testing use today
SET #PatternStartPoint =3;
--Get Job Duty Pattern
INSERT INTO [IDAHO].[dbo].[Appointments]
([Subject]
,[Description]
,[Start]
,[End]
,[RoomID]
,[UserID]
)
select *
from
(
SELECT
'Standard Work Pattern' [Subject]
,'IDAHO Generated on ' + CONVERT(nvarchar(20),getdate()) [Description]
,mydate + p.P_Start [Start]
,mydate + p.P_End [End]
,1 [RoomID]
,#User [UserID]
FROM
(
SELECT #StartDate AS MyDate, #PatternStartPoint as DayNumber
UNION ALL
Select MyDate + 1, (DayNumber+1) - (CAST((Daynumber+1)/(#PatternSize+.01) as int)*#PatternSize) --% (#PatternSize)
FROM Datelist
WHERE MyDate < (#Startdate + #DaysRequired)-1
) as d
join
(
Select D_DayNumber,D_Type,D_Start,D_End
from dbo.Tbl_Jobs as J
join dbo.Tbl_Patterns as P
on J.J_PatternID = P.Pattern_ID
join dbo.Tbl_PatternDetails as D
on P.Pattern_ID=d.D_Pattern_ID
) as p
on p.P_DayNumber=d.DayNumber
Where mydate + p.P_Start is not null
) x;
That might work.
(Expanded as requested)
(Amended to remove CTEs)

Related

Translating SQL Server Cursor to Azure Synapse

I have the following code that loops through a table with unique model numbers and creates a new table that contains, for each model numbers, a row based on the year and week number. How can I translate this so it doesn't use a cursor?
DECLARE #current_model varchar(50);
--declare a cursor that iterates through model numbers in ItemInformation table
DECLARE model_cursor CURSOR FOR
SELECT model from ItemInformation
--start the cursor
OPEN model_cursor
--get the next (first value)
FETCH NEXT FROM model_cursor INTO #current_model;
DECLARE #year_counter SMALLINT;
DECLARE #week_counter TINYINT;
WHILE (##FETCH_STATUS = 0) --fetch status returns the status of the last cursor, if 0 then there is a next value (FETCH statement was successful)
BEGIN
SET #year_counter = 2019;
WHILE (#year_counter <= Datepart(year, Getdate() - 1) + 2)
BEGIN
SET #week_counter = 1;
WHILE (#week_counter <= 52)
BEGIN
INSERT INTO dbo.ModelCalendar(
model,
sales_year,
sales_week
)
VALUES(
#current_model,
#year_counter,
#week_counter
)
SET #week_counter = #week_counter + 1
END
SET #year_counter = #year_counter + 1
END
FETCH NEXT FROM model_cursor INTO #current_model
END;
CLOSE model_cursor;
DEALLOCATE model_cursor;
If ItemInformation contains the following table:
model,invoice
a,4.99
b,9.99
c,1.99
d,8.99
then the expected output is:
model,sales_year,sales_week
A,2019,1
A,2019,2
A,2019,3
...
A,2019,52
A,2020,1
A,2020,2
A,2020,3
...
A,2020,51
A,2020,52
A,2020,53 (this is 53 because 2020 is leap year and has 53 weeks)
A,2021,1
A,2021,2
...
A,2022,1
A,2022,2
...
A,2022,52
B,2019,1
B,2019,2
...
D, 2022,52
Using CTE's you can get all combinations of weeks and years within the range required. Then join your data table on.
declare #Test table (model varchar(1), invoice varchar(4));
insert into #Test (model, invoice)
values
('a', '4.99'),
('b', '9.99'),
('c', '1.99'),
('d', '8.99');
with Week_CTE as (
select 1 as WeekNo
union all
select 1 + WeekNo
from Week_CTE
where WeekNo < 53
), Year_CTE as (
select 2019 YearNo
union all
select 1 + YearNo
from Year_CTE
where YearNo <= datepart(year, current_timestamp)
)
select T.model, yr.YearNo, wk.WeekNo
from Week_CTE wk
cross join (
select YearNo
-- Find the last week of the year (52 or 53) -- might need to change the start day of the week for this to be correct
, datepart(week, dateadd(day, -1, dateadd(year, 1, '01 Jan ' + convert(varchar(4),YearNo)))) LastWeek
from Year_CTE yr
) yr
cross join (
-- Assuming only a single row per model is required, and the invoice column can be ignored
select model
from #Test
group by model
) T
where wk.WeekNo <= yr.LastWeek
order by yr.YearNo, wk.WeekNo;
As you have advised that using a recursive CTE is not an option, you can try using a CTE without recursion:
with T(N) as (
select X.N
from (values (0),(0),(0),(0),(0),(0),(0),(0)) X(N)
), W(N) as (
select top (53) row_number() over (order by ##version) as N
from T T1
cross join T T2
), Y(N) as (
-- Upper limit on number of years
select top (12) 2018 + row_number() over (order by ##version) AS N
from T T1
cross join T T2
)
select W.N as WeekNo, Y.N YearNo, T.model
from W
cross join (
select N
-- Find the last week of the year (52 or 53) -- might need to change the start day of the week for this to be correct
, datepart(week, dateadd(day, -1, dateadd(year, 1, '01 Jan ' + convert(varchar(4),N)))) LastWeek
from Y
) Y
cross join (
-- Assuming only a single row per model is required, and the invoice column can be ignored
select model
from #Test
group by model
) T
-- Filter to required number of years.
where Y.N <= datepart(year, current_timestamp) + 1
and W.N <= Y.LastWeek
order by Y.N, W.N, T.model;
Note: If you setup your sample data in future with the DDL/DML as shown here you will greatly assist people attempting to answer.
I don't like to see a loop solution where a set solution can be found. So here goes Take II with no CTE, no values and no row_number() (the table variable is just to simulate your data so not part of the actual solution):
declare #Test table (model varchar(1), invoice varchar(4));
insert into #Test (model, invoice)
values
('a', '4.99'),
('b', '9.99'),
('c', '1.99'),
('d', '8.99');
select Y.N + 2019 YearNumber, W.WeekNumber, T.Model
from (
-- Cross join 5 * 10, then filter to 52/53 as required
select W1.N * 10 + W2.N + 1 WeekNumber
from (
select 0 N
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
) W1
cross join (
select 0 N
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9
) W2
) W
-- Cross join number of years required, just ensure its more than will ever be needed then filter back
cross join (
select 0 N
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9
) Y
cross join (
-- Assuming only a single row per model is required, and the invoice column can be ignored
select model
from #Test
group by model
) T
-- Some filter to restrict the years
where Y.N <= 3
-- Some filter to restrict the weeks
and W.WeekNumber <= 53
order by YearNumber, WeekNumber;
I created a table to temporary calendar table containing all the weeks and years. To account for leap years, I took the last 7 days of a year and got the ISO week for each day. To know how many weeks are in a year, I put these values into another temp table and took the max value of it. Azure Synapse doesn't support multiple values in one insert so it looks a lot longer than it should be. I also have to declare each as variable since Synapse can only insert literal or variable. I then cross-joined with my ItemInformation table.
CREATE TABLE #temp_dates
(
year SMALLINT,
week TINYINT
);
CREATE TABLE #temp_weeks
(
week_num TINYINT
);
DECLARE #year_counter SMALLINT
SET #year_counter = 2019
DECLARE #week_counter TINYINT
WHILE ( #year_counter <= Datepart(year, Getdate() - 1) + 2 )
BEGIN
SET #week_counter = 1;
DECLARE #day_1 TINYINT
SET #day_1 = Datepart(isowk, Concat('12-25-', #year_counter))
DECLARE #day_2 TINYINT
SET #day_2 = Datepart(isowk, Concat('12-26-', #year_counter))
DECLARE #day_3 TINYINT
SET #day_3 = Datepart(isowk, Concat('12-27-', #year_counter))
DECLARE #day_4 TINYINT
SET #day_4 = Datepart(isowk, Concat('12-28-', #year_counter))
DECLARE #day_5 TINYINT
SET #day_5 = Datepart(isowk, Concat('12-29-', #year_counter))
DECLARE #day_6 TINYINT
SET #day_6 = Datepart(isowk, Concat('12-30-', #year_counter))
DECLARE #day_7 TINYINT
SET #day_7 = Datepart(isowk, Concat('12-31-', #year_counter))
TRUNCATE TABLE #temp_weeks
INSERT INTO #temp_weeks
(week_num)
VALUES (#day_1)
INSERT INTO #temp_weeks
(week_num)
VALUES (#day_2)
INSERT INTO #temp_weeks
(week_num)
VALUES (#day_3)
INSERT INTO #temp_weeks
(week_num)
VALUES (#day_4)
INSERT INTO #temp_weeks
(week_num)
VALUES (#day_5)
INSERT INTO #temp_weeks
(week_num)
VALUES (#day_6)
INSERT INTO #temp_weeks
(week_num)
VALUES (#day_7)
DECLARE #max_week TINYINT
SET #max_week = (SELECT Max(week_num)
FROM #temp_weeks)
WHILE ( #week_counter <= #max_week )
BEGIN
INSERT INTO #temp_dates
(year,
week)
VALUES ( #year_counter,
#week_counter )
SET #week_counter = #week_counter + 1
END
SET #year_counter = #year_counter + 1
END
DROP TABLE #temp_weeks;
SELECT i.model,
d.year,
d.week
FROM dbo.iteminformation i
CROSS JOIN #temp_dates d
ORDER BY model,
year,
week
DROP TABLE #temp_dates

Stored procedure to add 30 days using DATEDIFF within while loop condition in Date Dimension table

I want to add 30 consecutive days of data in my Date Dimension table using DATEDIFF() but I am getting blank result. Can you please help me correct the code below to get the desired result?
CREATE TABLE dbo.dateDimension (
DateKey INT NOT NULL
,DateValue DATE NOT NULL
,CYear SMALLINT NOT NULL
,CMonth TINYINT NOT NULL
,CONSTRAINT PK_DimDate PRIMARY KEY ( DateKey )
);
GO
CREATE PROC dbo.dateTest
#StartDate DATETIME
AS
WHILE (DATEDIFF(day, #StartDate, GETDATE()) <=30)
BEGIN
INSERT into dbo.dateDimension
SELECT CAST( YEAR(#StartDate) * 10000 + MONTH(#StartDate) * 100 + DAY(#StartDate) AS INT)
,#StartDate
,YEAR(#StartDate)
,MONTH(#StartDate)
SET #StartDate = DATEADD(d,1,#StartDate)
END;
GO
EXECUTE dbo.dateTest '2010-01-01'
SELECT * FROM dbo.dateDimension
The issue is that this logic:
DATEDIFF(day, #StartDate, GETDATE())
gives 3739 days with your current start date, so its never less than 30. Personally I would simply count it as follows:
DECLARE #StartDate DATETIME = '2010-01-01', #Count INT = 0;
WHILE #Count <= 30 BEGIN
INSERT into dbo.dateDimension
SELECT CAST( YEAR(#StartDate) * 10000 + MONTH(#StartDate) * 100 + DAY(#StartDate) AS INT)
, #StartDate
, YEAR(#StartDate)
, MONTH(#StartDate);
SET #StartDate = DATEADD(d,1,#StartDate);
set #Count = #Count + 1;
END;
SELECT *
FROM dbo.dateDimension;
If you are using SQL Server 2016 or above, this solution will not use a while loop, instead it uses a CTE to generate 30 rows numbered I to 30 and then uses the date to convert to yyyymmdd.
DECLARE #NUM_DAYS INT=30;
DECLARE #STARTDATE DATETIME='2020-01-01';
WITH CTE AS(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS SRL
FROM STRING_SPLIT(REPLICATE(',',#num_days-1), ',') AS A
)
INSERT INTO dbo.dateDimension
SELECT
CONVERT(INT, CONVERT(CHAR(8), DATEADD(DAY, SRL-1, #STARTDATE), 112))
, #STARTDATE
, YEAR(#STARTDATE)
, MONTH(#STARTDATE)
FROM CTE
SELECT * FROM dbo.dateDimension

COUNT monthly sales for every year

I need to check monthly sales (count not sum) by area for long ranges of dates (5+ years), something like an Excel pivot table, currently I'm working it on Pandas but nobody here works with it so I'm trying to generate a view or a stored procedure in SQLServer for anyone who requires it. In this table sales are stored associated to an area and which product was.
I'm able to list and group AREA, SALES, MONTH AND YEAR, but as I mentioned, it would be easier to read if months or years where vertically aligned (there is about 100k records yearly and Excel lags at that point).
CREATE TABLE SALESHS
(
IDAREA INT,
DATEREG [NVARCHAR](50) NOT NULL,
IDPROD [NVARCHAR](50) NOT NULL
);
GO
-- Insert rows into table 'SALESHS'
INSERT INTO SALESHS
(
IDAREA, DATEREG, IDPROD
)
VALUES
(
1, '12/03/2019', 'xplpc'
),
(
1, '15/03/2019', 'ndtlctm'
),
(
2, '12/04/2019', 'wntd'
)
GO
SELECT IDAREA,
COUNT(IDAREA) AS CANT,
DATEREG, --DATE AS DD/MM/YYYY
DATEPART(MM,CAST(DATEREG AS DATETIME)) AS MONTH,
DATEPART(YYYY,CAST(DATEREG AS DATETIME)) AS YEAR,
FROM saleshs
WHERE DATEREG > 201712
GROUP BY DATEREG , idarea
ORDER BY DATEREG
Which returns this:
IDAREA AMOUNT MONTH YEAR PER_PRO
----------------------------------------
1 2 03 2019 201904
2 1 04 2019 201904
Expected results:
IDAREA JAN2019 FEB2019 MAR2019 APR2019
--------------------------------------
1 0 0 2 0
2 0 0 0 1
I know the basics of SQL and I don't expect a full answer either, but anything that could help me build this view it's appreciated. I've tried PIVOT also but I can't count, distinct and sum in the same query.
You can try Conditional Aggregation
SELECT IDAREA,
SUM( CASE WHEN YEAR(CAST(DATEREG AS DATETIME))= 2019 AND
MONTH(CAST(DATEREG AS DATETIME))=1 THEN
1
ELSE
0
END) JAN2019,
SUM( CASE WHEN YEAR(CAST(DATEREG AS DATETIME))= 2019 AND
MONTH(CAST(DATEREG AS DATETIME))=2 THEN
1
ELSE
0
END) FEB2019,
SUM( CASE WHEN YEAR(CAST(DATEREG AS DATETIME))= 2019 AND
MONTH(CAST(DATEREG AS DATETIME))=3 THEN
1
ELSE
0
END) MAR2019,
SUM( CASE WHEN YEAR(CAST(DATEREG AS DATETIME))= 2019 AND
MONTH(CAST(DATEREG AS DATETIME))=4 THEN
1
ELSE
0
END) APR2019
FROM saleshs
WHERE YEAR(CAST(DATEREG AS DATETIME))> 2017
GROUP BY IDAREA
ORDER BY IDAREA
Demo
--Build the column names for Pivot using dynamic SQL
DECLARE #YourChoice date
set #YourChoice = '2017/12/13' --change here to what date you want the earliest
declare #count int = 0
declare #columnstr varchar(max)
declare #columnpivot varchar(max)
declare #onecolumnname varchar(20)
set #columnstr = ''
set #columnpivot = ''
set #onecolumnname = ''
while #count <= DATEDIFF(MONTH,#YourChoice,GETDATE())
begin
set #onecolumnname = concat(cast(datename(month,dateadd(month,#count,#YourChoice)) as varchar(50)),cast(year(dateadd(month,#count,#YourChoice)) as varchar(10)))
set #columnstr = #columnstr + 'coalesce([' + #onecolumnname+ '],0) as '+#onecolumnname+', '
set #columnpivot = #columnpivot + '['+#onecolumnname+'], '
set #count = #count + 1
end
set #columnstr = left(#columnstr,len(#columnstr)-1)
set #columnpivot = '('+left(#columnpivot,len(#columnpivot)-1)+')'
--Pivot time!
declare #str varchar(max)
set #str =
'select IDAREA,' + #columnstr +' from (
select count(s.idarea) as amount,IDAREA,columnname from (
select *,datename(month,cast(substring(datereg,7,4)+''-''+substring(datereg,4,2)+''-''+substring(datereg,1,2) as datetime)) + SUBSTRING(datereg,7,4) as columnname
from SALESHS )s
group by IDAREA,columnname)s1
pivot
(
max(s1.amount)
for s1.columnname in '+#columnpivot+'
) p'
exec (#str)
Test Result 1 ('2017/12/13'):
DB<>Fiddle
Test Result 2 ('2018/12/14'):
DB<>Fiddle
I have created a Dynamic SQL to solve this particular issue. Query can be adjusted on which YEAR to display in the result and which Column to count in the PIVOT Section of the query. Date format in the i have used is below query MM/DD/YYYY.
You can run the code HERE
Below is the SQL Query.
CREATE TABLE SALESHS
( IDAREA INT,
DATEREG date NOT NULL,
IDPROD [NVARCHAR](50) NOT NULL
);
/* Insert rows into table 'SALESHS' */
INSERT INTO SALESHS
( IDAREA, DATEREG, IDPROD)
VALUES
( 1, '03/12/2019', 'xplpc'),
( 1, '03/15/2019', 'ndtlctm'),
( 2, '04/12/2019', 'wntd')
/* Create Calendar Table to capture all the dates for first day of month from start Date to end date */
CREATE TABLE Calendar
(
[CalendarDate] DATE
,[MonthName] AS FORMAT(CONVERT(DATE, DATEADD(m, DATEDIFF(m, 0, CalendarDate), 0)), 'MMM-yyyy')
,[MonthNo] AS FORMAT(CalendarDate,'MM')
,[Year] AS FORMAT(CalendarDate,'yyyy')
,DateKey AS CONCAT(FORMAT(CalendarDate,'yyyy'), FORMAT(CalendarDate,'MM'))
)
DECLARE #Date DATE, #StartDate DATE, #EndDate DATE
SET #Date = '01/01/2012'
SET #StartDate = CONVERT(DATE, DATEADD(m, DATEDIFF(m, 0, #Date), 0)) /* Set Start date to first day of the month for given date */
SET #EndDate = '04/01/2019'
WHILE #StartDate <= #EndDate
BEGIN
INSERT INTO Calendar (CalendarDate)
SELECT #StartDate
SET #StartDate = DATEADD(m, 1, #StartDate)
END
/* Variable to hold unique Months to be used in PIVOT clause */
DECLARE #UniqueMonthsToPivot NVARCHAR(MAX) = N''
/* Extract unique Month names with pivot formattings */
SELECT #UniqueMonthsToPivot = #UniqueMonthsToPivot + ', [' + COALESCE(MonthName, '') + ']'
FROM (SELECT DISTINCT MonthName FROM Calendar) DT
/* Remove first comma and space */
SELECT #UniqueMonthsToPivot = LTRIM(STUFF(#UniqueMonthsToPivot, 1, 1, ''))
/* Variable to hold pivot column names with alias to be used in SELECT Clause */
DECLARE #PivotMonthsToSelect NVARCHAR(MAX) = N''
/* Generate column names list for SELECT list with SUM Function and NULL handling.
YEAR in the where condition can be adjust to select and show only certain year or month in Select list.
Order by CalendarDate is important to define the sequence of columns in the result */
SELECT #PivotMonthsToSelect = #PivotMonthsToSelect + ', SUM(ISNULL([' + COALESCE(MonthName, '') + '], 0)) AS [' + MonthName + ']'
FROM Calendar WHERE Year >= 2012
Order by CalendarDate
/* Variable to hold t-sql query */
DECLARE #SQLStatement NVARCHAR(MAX) = N''
/* Generate dynamic PIVOT query here */
SET #SQLStatement =
N'SELECT IDAREA'
+ #PivotMonthsToSelect +
'FROM (
SELECT *
,CASE WHEN IDAREA IS NULL THEN 0 ELSE 1 END AS IDAREA_Dup
FROM Calendar C
LEFT JOIN SALESHS S ON C.CalendarDate = CONVERT(DATE, DATEADD(m, DATEDIFF(m, 0, DATEREG), 0))
) AA
PIVOT
( SUM(IDAREA_Dup)
FOR MonthName IN ('+ #UniqueMonthsToPivot +')
) P
WHERE IDAREA IS NOT NULL
GROUP BY IDAREA
'
/* Check the generated dynamic t-sql PIVOT query below */
--PRINT (#SQLStatement)
/* Execute the generated dynamic t-sql PIVOT query below */
EXEC (#SQLStatement)

How to efficiently loop through using Sql query

As I have From and To date. Something like below,
BeginDate End Date
1989-01-01 00:00:00.000 2015-12-31 00:00:00.000
I need to loop through until i get the list of all the Date's between those 2 (Begin & End Date's) records. I need to know what will be the efficient way of doing this. I have no clue on how to do this. Any help to this will be highly appreciated.
Thanks
This method uses a generated numbers table and is probably faster than looping.
DECLARE #BeginDate DATETIME = '19890101';
DECLARE #EndDate DATETIME = '20151231';
WITH
E1(N) AS ( SELECT 1 FROM ( VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) DT(N)),
E2(N) AS ( SELECT 1 FROM E1 A, E1 B),
E4(N) AS ( SELECT 1 FROM E2 A, E2 B),
Numbers(N) AS
(
SELECT ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL)) - 1 FROM E4
)
SELECT
N,
DATEADD(D, N, #BeginDate) AS TheDate
FROM Numbers
WHERE N <= DATEDIFF(D, #BeginDate, #EndDate)
You can do this with WHILE loop:
DECLARE #sdt DATE = '1989-01-01'
DECLARE #edt DATE = '2015-12-31'
WHILE #sdt <= #edt
BEGIN
PRINT #sdt
SET #sdt = DATEADD(dd, 1, #sdt )
END
Or with recursive CTE:
DECLARE #sdt DATE = '1989-01-01'
DECLARE #edt DATE = '2015-12-31';
WITH cte
AS ( SELECT #sdt AS sdt
UNION ALL
SELECT DATEADD(dd, 1, sdt)
FROM cte
WHERE DATEADD(dd, 1, sdt) <= #edt
)
SELECT *
FROM cte
OPTION ( MAXRECURSION 10000 )
There is also tally table method as in link provided by #Bridge
Actually the answer is tally tables. But if there is not a big interval the difference will be insignificant.
Something like this should work for your purposes:
DECLARE #sd date = '1989-01-01 00:00:00.000'
, #ed date = '2015-12-31 00:00:00.000'
DECLARE #tt TABLE(
[Date] date
)
WHILE(#sd <= #ed) --Loop which checks each iteration if the date has reached the end
BEGIN
INSERT INTO #tt
SELECT #sd AS Date
SET #sd = DATEADD(dd,1,#sd) --This willl increment the date so you actually advance the loop
END
SELECT * FROM #tt

SQL Server related question

I have this thing that i need to do and some advices will be greatly appreciated.
I have a SQL server table with some phone calls.For each phone call i have the start and end time.
What i need to accomplish: a stored procedure which for a certain period of time, let's say 5 hours at a x interval, lets say 2 minutes returns the number of connected calls.
Something like:
Interval Nr of Calls Connected
01-01-2010 12:00:00 - 01-01-2010 12:05:00 30
01-01-2010 12:05:01 - 01-01-2010 12:10:00 10
.............
Which will be the fastest way to do that? Thank you for your help
This will work for intervals that have calls ...
Declare #datetimestart datetime
Declare #interval int
Set #datetimestart = '2009-01-01 12:00:00'
Set #interval = 5 --in minutes
Select
[start_interval], [end_interval] , count([start_interval]) as [calls]
From
(
Select
DateAdd( Minute,Floor(DateDiff(Minute,#datetimestart,[date])/#interval)*#interval
,#datetimestart) ,
DateAdd( Minute,#interval + Floor(DateDiff(Minute,#datetimestart,[date])/#interval)*#interval
,#datetimestart)
From yourTable
) As W([start_interval],[end_interval])
group by [start_interval], [end_interval]
This will work for all intervals regardless of number of calls..
Declare #datetimestart datetime, #datetimeend datetime, #datetimecurrent datetime
Declare #interval int
Set #datetimestart = '2009-01-01 12:00:00'
Set #interval = 10
Set #datetimeend = (Select max([date]) from yourtable)
SET #datetimecurrent = #datetimestart
declare #temp as table ([start_interval] datetime, [end_interval] datetime)
while #datetimecurrent < #datetimeend
BEGIN
insert into #temp select (#datetimecurrent), dateAdd( minute, #interval, #datetimecurrent)
set #datetimecurrent = dateAdd( minute, #interval, #datetimecurrent)
END
Select
*
From
(
Select
[start_interval],[end_interval], count(d.[start_time])
From #temp t left join yourtable d on d.[start_time] between t.[start_interval] and t.[end_interval]
) As W([start_interval],[end_interval], [calls])
I Altered Gaby's example a little to do What you expected
Declare #datetimeend datetime
,#datetimecurrent datetime
,#interval int
Set #interval = 10
Set #datetimeend = (Select max([end_time]) from Calls)
SET #datetimecurrent = '2010-04-17 14:20:00'
declare #temp as table ([start_interval] datetime, [end_interval] datetime)
while #datetimecurrent < #datetimeend
BEGIN
insert into #temp select (#datetimecurrent), dateAdd( minute, #interval, #datetimecurrent)
set #datetimecurrent = dateAdd( minute, #interval, #datetimecurrent)
END
Select
[start_interval],[end_interval], count(d.id) [COUNT]
From #temp t
left join Calls d on
d.end_time >= t.start_interval
AND d.start_time <= t.end_interval
GROUP BY [start_interval],[end_interval]
used this to create the table and fill it
CREATE TABLE dbo.Calls
(
id int NOT NULL IDENTITY (1, 1),
start_time datetime NOT NULL,
end_time datetime NULL,
caller nvarchar(50) NULL,
receiver nvarchar(50) NULL
) ON [PRIMARY]
GO
ALTER TABLE dbo.Calls ADD CONSTRAINT
PK_Calls PRIMARY KEY CLUSTERED
(
id
) ON [PRIMARY]
GO
DECLARE #I INT
SET #I = 0
WHILE #I < 100
BEGIN
INSERT INTO Calls
(start_time, end_time)
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-10,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-9,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-9,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-8,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-8,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-7,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-7,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-6,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-6,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-5,GETDATE()))
UNION
SELECT
DATEADD(HOUR,-#I,DATEADD(MINUTE,-5,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-4,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-4,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-3,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-3,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-2,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-2,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-1,GETDATE()))
UNION
select
DATEADD(HOUR,-#I,DATEADD(MINUTE,-1,GETDATE()))
,DATEADD(HOUR,-#I,DATEADD(MINUTE,-0,GETDATE()));
SET #I = #I + 1
END
Done in SQL Server 2008
but the script would work in other versions
I would use a Numbers pivot table to get the time intervals and then count all calls which overlapped the interval:
SELECT Intervals.IntervalStart
,Intervals.IntervalEnd
,COUNT(*)
FROM (
SELECT DATEADD(MINUTE, Numbers * 2, #StartTime) AS IntervalStart
,DATEADD(MINUTE, (Numbers + 1) * 2, #StartTime) AS IntervalEnd
FROM Numbers
WHERE Numbers BETWEEN 0 AND (5 * 60 / 2)
) AS Intervals
LEFT JOIN Calls
ON Calls.CallEnd >= Intervals.IntervalStart
AND Calls.CallStart < Intervals.IntervalEnd
GROUP BY Intervals.IntervalStart
,Intervals.IntervalEnd
To get the empty intervals, you would need to LEFT JOIN to this from another "Intervals" derived table.
How about this approach:
select Year(StartTime) as Year, Month(StartTime) as Month, Day(StartTime) as Day, datepart(hh, StartTime) as Hour, datepart(mm, StartTime) / 2 as TwoMinuteSegment, count(*)
from MyTable
where StartDate between '01-01-2010 12:00:00' and '01-01-2010 17:00:00'
group by Year(StartTime), Month(StartTime), Day(StartTime), datepart(hh, StartTime), datepart(mm, StartTime) / 2

Resources