Date generator to table variable - sql

I read about a way to generate a range of dates: Generate a set or sequence without loops. I'm having trouble with applying the method.
This code successfully generates a table of dates from #Start to #End:
;WITH d([Date]) AS (
SELECT DATEADD(DAY, n-1, #Start)
FROM (
SELECT TOP (DATEDIFF(DAY, #Start, #End) + 1)
ROW_NUMBER() OVER (ORDER BY Number) FROM master..spt_values
) AS x(n)
)
SELECT d.Date
FROM d
I'm fairly new to more advanced SQL so this is quite the black box. But it works. So now I wanted to save the result into a table variable for further processing:
DECLARE #Days TABLE ([Date] DATE NULL, Val INT NULL)
The date will have a value associated with it. However simply inserting into the table seems not to be working. Neither the following or wrapping it in yet another select statement works:
INSERT INTO #Days (Date)
WITH d([Date]) AS (
SELECT DATEADD(DAY, n-1, #Start)
FROM (
SELECT TOP (DATEDIFF(DAY, #Start, #End) + 1)
ROW_NUMBER() OVER (ORDER BY Number) FROM master..spt_values
) AS x(n)
)
SELECT d.Date
FROM d
It seems the 'last statement must be terminated with a semicolon' or an 'incorrect syntax near ;' is thrown depending on the presence of the semicolon.
Do you have any directions? I'm confused like mad.

Your problem is not the CTE, it is the INSERT syntax. As explained in the documentation, the CTE comes before the INSERT:
WITH d([Date]) AS (
SELECT DATEADD(DAY, n-1, #Start)
FROM (
SELECT TOP (DATEDIFF(DAY, #Start, #End) + 1)
ROW_NUMBER() OVER (ORDER BY Number) FROM master..spt_values
) AS x(n)
)
INSERT INTO #Days (Date)
SELECT d.Date
FROM d;

Related

Adding dates in a range

I need to add dates from 01-01-2011 to 31-12-2014 in format: dd-mm-yyyy, how can I do this? I mean something like this:
SET #Date = '01/01/2011';
WHILE #Date <'31/12/2014'
BEGIN
INSERT INTO Calendar(DataKal) VALUES (#Date);
SET #Date = #Date + 1;
END
I am using SQL Server 2014.
If what you are saying is you want to INSERT a row for each date between 2 dates then the best, and by far fastest, method to do this is with a Tally:
DECLARE #StartDate date = '20110101',
#EndDate date = '20141230'; --Seems odd you want to omit 31 December 2014
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT TOP (DATEDIFF(DAY, #StartDate, #EndDate) + 1)
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 AS I
FROM N N1, N N2, N N3, N N4) --10,000 rows
INSERT INTO dbo.Calendar (DataKal)
SELECT DATEADD(DAY, T.I, #StartDate)
FROM Tally T;
You can use a recursive CTE to define the dates and load them:
with cte as (
select convert(date, '2011-01-01') as dte
union all
select dateadd(day, 1, dte)
from cte
where dte < '2014-12-31'
)
insert into calendar (datakal)
select dte
from cte
option (maxrecursion 0);
Note that this inserts the date using the internal format. If you want to see it in a particular format -- such as dd-mm-yyyy -- then you can add a second column. I would suggest adding the column as a computed column:
alter table calendar add dd_mm_yyyy as (convert(varchar(10), datakal, 105));
Here is a db<>fiddle.

OPTION(MAXRECURSION 0) in SQL Server

I am used this function to execute stored procedure,
FUNCTION [dbo].[Test]
(#d1 DATE,
#d2 DATE,
#nd VARCHAR(MAX))
RETURNS TABLE
AS
RETURN
(WITH AllDates AS
(
SELECT #d1 AS DateOf
UNION ALL
SELECT DATEADD (DAY, 1, DateOf­)
FROM AllDates
WHERE DateOf < #d2
)
SELECT COUNT() SumOfDays
FROM AllDates
WHERE EXISTS (SELECT 1
FROM STRING_SPLIT(#nd,' ')
WHERE DATENAME(weekday,dat­eof) = value)
OPTION (MAXRECURSION 0)
)
The result is supposed to know how many of 'Monday Tuesday Saturday' are between Date1 And Date 2
n.b i need to add OPTION(MAXRECURSION 0) in the function but it's not working ,
But I get this error:
Incorrect syntax near the keyword 'OPTION'.
OPTION clause can be used only at the statement level. So you cannot use it within a query expression inside view definitions or inline TVFs etc. The only way to use it in your case is to create the TVF without the OPTION clause and specify it in the query that uses the TVF. We have a bug that tracks request for allowing use of OPTION clause inside any query expression (for example, if exists() or CTE or view).
Here is the that that you can visit to refer more: https://social.msdn.microsoft.com/Forums/sqlserver/en-US/7c7d5fea-38ad-4bc5-9038-a157e640561f/using-option-clause-within-create-function-statement-for-inline-table-functions?forum=transactsql
So in your example, you must specify the OPTION when you call your function:
FUNCTION [dbo].[Test](#d1 DATE, #d2 DATE, #nd VARCHAR(MAX))
RETURNS TABLE
AS
RETURN (
WITH AllDates AS
(
SELECT #d1 AS DateOf
UNION ALL
SELECT DateAdd(day,1,DateOf­)
FROM AllDates
WHERE DateOf<#d2
)
select COUNT() SumOfDays
FROM AllDates
WHERE EXISTS(SELECT 1
FROM STRING_SPLIT(#nd,' ')
WHERE DATENAME(weekday,dat­eof) = value)
)
Now you can use Option at SELECT statement:
SELECT * FROM [dbo].[Test] ( #d1 , #d2, #nd ) OPTION ( MAXRECURSION 0 )
Hope to help, my friend :))
The obvious answer to stop the recursion error would be to get rid of the recursion. If there's no recursion then there's no problem!
This is very simple to do as well, as you simply use a tally instead. This also has a speed benefit, as a Tally is (significantly) faster than a rCTE. I use an inline tally for this:
CREATE FUNCTION dbo.Test (#D1 date, #D2 date, #nd varchar(100)) --There's no need for MAX
RETURNS TABLE
AS
RETURN (WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT TOP (SELECT DATEDIFF(DAY,#D1,#D2)) --It appears you don't include the last day in your logic
--If it should, add +1 to the DATEDIFF value
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 AS I
FROM N N1, N N2, N N3, N N4, N N5), --More than enough rows
Dates AS(
SELECT DATEADD(DAY, T.I, #D1) AS D
FROM Tally T)
SELECT COUNT(D.D) AS SumOfDays
FROM Dates D
WHERE EXISTS(SELECT 1
FROM STRING_SPLIT(#nd,' ')
WHERE DATENAME(weekday,D.D) = value))
GO
SELECT *
FROM dbo.Test('20000101','20200101','Sunday Saturday');
GO
DROP FUNCTION dbo.Test;
I change the length of #nd as there's no need for it to be a MAX. The string 'Sunday Monday Tuesday Wednesday Thursday Friday Saturday' is only 56 characters long, so a varchar(100) is more than enough. You may, however, want to use an nvarchar, to ensure this function works for other languages; as a character may end up being lost.
The option is associated with the outermost select:
RETURN (
WITH AllDates AS(
SELECT #d1 AS DateOf
UNION ALL
SELECT DATEADD (DAY, 1, DateOf­)
FROM AllDates
WHERE DateOf < #d2
)
SELECT COUNT() SumOfDays
FROM AllDates
WHERE EXISTS (SELECT 1
FROM STRING_SPLIT(#nd,' ')
WHERE DATENAME(weekday,dat­eof) = value)
)
OPTION (MAXRECURSION 0)
)

How can I find how many Week Numbers are in a given interval of time in T-SQL

I am a bit stuck here, I want to find all the week numbers in a given interval of time and I can't really figure it out
For example , instead of
- > datepart(week,dateadd(day,-1,#oneSingleDate)) (monday is the first day of the week) ,
I need something like
- > datepart(week,#startDate,#endDate)
Given the interval '2019-01-04'-'2019-01-28' the output needs to be :
WeekNo : 1,2,3,4,5 .
i've used a recursive CTE to generate all the dates in the range, then I've selected the DISTINCT week numbers from them using DATEPART. Then I've concatenated then into your comma-separated string into a variable called #OUT
DECLARE #startDate as date = '20190104';
DECLARE #endDate as date = '2019-01-28';
DECLARE #OUT as nvarchar(max);
WITH CTE AS (SELECT #startDate As X
UNION ALL
SELECT DATEADD(y, 1, X) X FROM CTE where x < #endDate),
CTE2 AS (SELECT DISTINCT datepart(wk, X) wk from cte)
select #out = coalesce(#out + ', ', '') + cast(wk as nvarchar(4)) from cte2
OPTION (MAXRECURSION 0);
select #out;
You can do it as follows:
using :
- [CTE][1] common Table expression
- [Sql recursion union all][2]
- [Concatenation of multiple rows into one line][3]
declare #startDate as date ;
declare #endDate as date ;
set #startDate='2019-01-04';
set #endDate='2019-01-28' ;
DECLARE #weeks VARCHAR(8000) ;
with cte as (select #startDate as mydate,datepart(week,#startDate) w
union all select
dateadd(day,1,mydate),datepart(week,dateadd(day,1,mydate)) from cte
where mydate < #endDate) , cte2 as (select distinct(w) from cte)
select #weeks=COALESCE(#weeks + ', ', '') +cast(w as varchar(2)) from
cte2 OPTION (MAXRECURSION 360) select #weeks [Result]

Is that possible to write the below query using CTE recursion?

create table #temp (date date)
declare #X date
set #X = '2016-7-01'
declare #Y date
set #Y = cast (getdate() as date)
while(#X<=#Y)
begin
if (datename(WEEKDAY,#X) = 'Sunday')
insert into #temp values (#X)
set #X = cast(((cast(#X as datetime))+1)as date)
continue
end
select * from #temp
drop table #temp
Is that possible to write the above query using CTE recursion?
You can use a CTE to create a numbers table. You can then use the numbers table to get your dates like so:
Declare #Startdate Datetime = '2016-07-01'
Declare #EndDate Datetime = '2016-08-29'
;with
N0 as (SELECT 1 as n UNION ALL SELECT 1)
,N1 as (SELECT 1 as n FROM N0 t1, N0 t2)
,N2 as (SELECT 1 as n FROM N1 t1, N1 t2)
,N3 as (SELECT 1 as n FROM N2 t1, N2 t2)
,N4 as (SELECT 1 as n FROM N3 t1, N3 t2)
,nums as (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) as num FROM N4)
SELECT DATEADD(day,num-1,#startdate) as thedate
FROM nums
WHERE num <= DATEDIFF(day,#startdate,#enddate) + 1
and datename(WEEKDAY, DATEADD(day,num-1,#startdate)) = 'Sunday'
Each table (N0 to nums) effectively multiplies the number of rows in the previous 'table', so you end up with 65,536 rows of numbers in nums (you can do less or more by adding or removing table NX as required). Then, use the numbers table to add days to your start date(SELECT DATEADD(day,num-1,#startdate) as thedate) , where the dates returned are in your date range, and the weekday is Sunday.
Also, because the numbers in nums start at 1, we use nums-1 in our select, so as to avoid skipping over the first date in our series, effectively giving us DATEADD(day, 0, #startdate) in our first row.
You can try something like explained here: http://blog.sqlauthority.com/2009/12/29/sql-server-get-date-of-all-weekdays-or-weekends-of-the-year/
DECLARE #StartDate DATETIME
DECALRE #EndDate DATETIME
SET #StartDate = '2016-07-01'
SET #EndDate = GETDATE()
;WITH cte AS (
SELECT
1 AS DayID,
#StartDate AS FromDate,
DATENAME(dw, #StartDate) AS Dayname
UNION ALL
SELECT
cte.DayID + 1 AS DayID,
DATEADD(d, 1, cte.FromDate),
DATENAME(dw, DATEADD(d, 1, cte.FromDate)) AS Dayname
FROM cte
WHERE DATEADD(d, 1, cte.FromDate) < #EndDate
)
SELECT FromDate AS Date, Dayname
FROM cte
WHERE Dayname IN ('Sunday')
I'm thinking there's be a more efficient way to do this by using a numbers table and a factor of 7 (depending on how many dates you need to get, possible pre-calc the first sunday from your start date, then join all numbers that are factors of 7 from a numbers table), but the above works well enough also.
;with cte
as
(
select getdate() as datee
union all
select dateadd(day,1,datee)
from cte
where datediff(day,getdate(),datee)<100
)
select * from cte
where datename(WEEKDAY,datee) = 'Sunday'
You will hit max recursion limit as well ,to avoid..that use something like below..
option ( MaxRecursion 0 )
I would solve this using numbers table if i am not constrained by the need to use recursive cte ,which is also way faster than Recursive cte..
select
dateadd(day,n,getdate()) as datee
from numbers
where n<100 and datename(weekday,dateadd(day,n,getdate()))='sunday'
To learn why you need numbers table,check this link..https://dba.stackexchange.com/questions/11506/why-are-numbers-tables-invaluable

Get all dates between 2 dates in SQL Server stored procedure

I 'm using SQL Server 2014 and I have a table Attendance. This table has 2 columns AttdDate and Status. I want to create a stored procedure that returns list of dates between 2 dates, AttdDate and status. And if the AttdDate is in this list (dates list) status should be true else status should be false.
Any advice? Thanks
CREATE PROCEDURE sp_Attendance #Start DATETIME, #End DATETIME
AS
BEGIN
DECLARE #NumDays INT;
-- This will give you the number of days between your start date and end date.
SELECT #NumDays = DATEDIFF(DAY, #Start, #End) + 1;
WITH CTE AS (
SELECT TOP (#Numdays)
/*
ROW_NUMBER() OVER (ORDER BY a.object_id) will give you an integer from 1 to #NumDays becuase of TOP (#NumDays)
ROW_NUMBER() OVER (ORDER BY a.object_id) - 1 will give you an integer from 0 to #NumDays
DATEADD(DAY, ROW_NUMBER(), #Start) -- This will add the integer from the row number statement to your start date.
i.e.
#Start + 0
#Start + 1
#Start + 2
etc
etc
#Start + #NumDays
*/
DATEADD(DAY, ROW_NUMBER() OVER (ORDER BY a.object_id) - 1, #Start) AS AttdDate
FROM
sys.all_columns a
CROSS JOIN
sys.all_columns b)
SELECT
c.AttdDate,
CASE WHEN a.AttdDate IS NOT NULL THEN 1 ELSE 0 END AS Status
FROM
CTE c
LEFT JOIN
Attendance a
ON c.AttdDate = a.AttdDate;
END;
You can use a recursive query for this:
WITH q(d) AS
(
SELECT #mindate
UNION ALL
SELECT DATEADD(day, 1 d)
FROM q
WHERE d < #maxdate
)
SELECT *
FROM q
OPTION (MAXRECURSION 0)
, however, its performance would be quite poor for longer ranges.
I usually just create a table with all possible dates up to year 2100 in my SQL Server databases:
SELECT *
FROM dates
WHERE d BETWEEN #mindate AND #maxdate