CTE query With 2 conditions not working? - sql-server

I was born in 1978/12/22
I wanted a query (in CTE!) to display me all my birthdays (datepart day = 22)
declare #t0 datetime = '1978/12/22';
declare #t1 datetime = getdate();
with CTEE (val,day1)
AS
(
SELECT #t0,DATEPART(day,#t0)
UNION all
SELECT DATEADD(day,1,val) , DATEPART(day,day1) from ctee where( DATEADD(day,1,val) <=#t1) and DATEPART(day,day1)=22
)
select val,day1 from CTEE OPTION (MAXRECURSION 20000)
How ever this seems to return me 1 row ( why???)
If I remove and DATEPART(day,day1)=22 so it gives me all the days im alive.
The solution I found is this:
declare #t0 datetime = '1978/12/22';
declare #t1 datetime = getdate();
with CTEE (val,day1)
AS
(
SELECT #t0,DATEPART(day,#t0)
UNION all
SELECT DATEADD(day,1,val) , DATEPART(day,day1) from ctee where( DATEADD(day,1,val) <=#t1)
)
select val,day1 from CTEE where day1=22 OPTION (MAXRECURSION 20000)
But my question is why in the first query it didn't work?

Because the second record is 1978-12-23.
That doesn't meet the DATEPART(day,day1)=22 condition, so that recursive step returns no results, this terminates the recursion as documented in SQL Server Books Online
The termination check is implicit; recursion stops when no rows are
returned from the previous invocation.

Related

Why does a recursive cte not return duplicates?

I happened to encounter the following sql statement
DECLARE #SDate DATETIME
DECLARE #TDate DATETIME
SET #SDate = '2011-9-01 00:00:00.000'
SET #TDate = '2011-10-01 00:00:00.000'
;WITH CTE AS
(
SELECT #SDate Date
UNION ALL
SELECT Date + 1 FROM CTE WHERE Date + 1 <= #TDate
)
SELECT Date FROM CTE
To my knowledge, this statement should return quite a lot of duplicates as each iteration in the recursion increments the entire column by 1 and the result should be a UNION ALL of all the intermediate tables.
However to my surprise the resulting table contains no duplicates, just all the distinct dates from 2011-9-01 to 2011-10-01. Why is this the case?
Don't think of recursive CTEs as actually being "recursive". They are inductive.
The anchor portion of the CTE runs. Then the "recursive" part keeps adding rows -- based on the most recently added rows. So, the first iteration adds the first date. The second iteration takes the just-added date and adds one to it.
The third iteration only considers the second (last) date, not both.
The terminology is not great, but they do not generate duplicates by reprocessing a given row more than once.
Did you test this?
DECLARE #SDate DATETIME
SET #SDate = '2011-9-01 00:00:00.000'
select #SDate;
select #SDate + 1;

SQL function to populate a simple date table

I need to populate a table with simple (Year, month, days) columns for upto 10 years and I am roughly taking every month to have 30 days.It looks like as below.
I have written the below code to populate the table but I have error on the second 'while' it say 'Expecting '(',or select'. Any clue why?
Simple Date table
declare #month varchar(20)
set #month ='1'
declare #day varchar(20)
set #day='1'
declare #sql nvarchar(1000)
while(#Year <=10)
(
while(#month<=12)
(
while(#day<= #day+30)
(
insert into simple_table values(#Year,#month,#day)
#day=#day+1
)
#month+1
)
#year = #year+1
)
)
With the help of an ad-hoc tally table and a Cross Join (or two)
Your sample was a little unclear. This assumes the Day column is not 1-30 but 1-3600 for 10 years. I'm assuming you are building some sort of amortization schedule of 30/360
Select Year = 'Year'+cast(Y as varchar(25))
,Month = M
,Day = Row_Number() over (Order by Y,M,D)
From (Select Top (10) Y=Row_Number() Over (Order By (Select NULL)) From master..spt_values) Y
Cross Join (Select Top (12) M=Row_Number() Over (Order By (Select NULL)) From master..spt_values) M
Cross Join (Select Top (30) D=Row_Number() Over (Order By (Select NULL)) From master..spt_values) D
Returns
Seems like it would be a lot easier to use one loop instead of 3.
Declare #D DateTime;
Set #D = GetDate();
while #D < DateAdd(Year, 10, GetDate())
Begin
Insert Into simple_table(Year, Month, Day) Select Year(#D), Month(#D), Day(#D)
Set #D = DateAdd(Day, 1, #D)
End
Basically, just use a date as your loop variable, increment one day each time through the loop. Additional benefit, you get exactly the right number of days in each month (including leap years).

SQL Server stored procedure to add days to a date

I need to write a stored procedure that given a date and a number of working days, adds those given days to that date, and returns the new date, without counting the non-working days and the weekends. the non-working day are stored in another table.
It's my second stored procedure so I'm not quite familiar with the lexic, so, sorry in advance if you find obvious mistakes.
So far I've gotten to this:
CREATE PROCEDURE DateAdd
(#GivenDate DATE, #DaysToAdd int)
DECLARE #ReturnDate DATE,
DECLARE #Counter int,
DECLARE #NextDate DATE
AS
SET #Counter = 0
SET #ReturnDate = #GivenDate
SET #NextDate = #GivenDate
GO
WHILE (#Counter < #DaysToAdd)
#Counter + 1
IF(datepart(weekday, #FechaVariable) !=6 &&
datepart(weekday, #FechaVariable) != 7)
IF(#TODO-- call the query and check it with #NextDate)
#FechaRetorno = DateAdd(dd, 1, #FechaRetorno)
ELSE IF #NextDate = DateAdd(dd, 1, #NextDate)
EN IF
END WHILE
-- I don't know where to put this query, or how to call ir from the IF
SELECT Date
FROM non_working_days
WHERE Date = $Variable
RETURN #FechaRetorno
A Tally/Calendar table would to the trick as well, but you can to do this with an ad-hoc tally table. Also, this approach would be faster than an recursive cte.
One additonal option is that you can exclude HOLIDAYS by adding the following to the WHERE clause.
and D not in ('2017-12-25','2018-01-01')
The SQL
Declare #Date date = '2017-04-01'
Declare #Days int = 5
Select D
From (
Select D,RN=Row_Number() over (Order by D)
From (Select Top ((#Days*2)+10) D=DateAdd(DAY,-1+Row_Number() Over (Order By Number),#Date) From master..spt_values ) A
Where DateName(WEEKDAY,D) not in ('Saturday','Sunday')
) A
Where RN=#Days
Returns
2017-04-07
Here. Assuming the other table is called OtherTable and the column of dates to avoid is called DatesToAvoid, this will add one day at a time to your date, and if that date is not a weekend or in the DatesToAvoid, it will decrement #DaysToAdd. Once #DaysToAdd reaches 0, it stops.
CREATE PROCEDURE DateAddsp(#GivenDate DATE, #DaysToAdd int)
AS
BEGIN
WHILE #DaystoAdd > 0
BEGIN
SET #GivenDate = DATEADD(DAY,1,#GivenDate)
SET #DaysToAdd = CASE
WHEN #GivenDate IN (SELECT DatesToAvoid FROM OtherTable) OR DATEPART(DW,#GivenDate) IN (1,7) /* Saturday or Sunday*/
THEN #DaysToAdd
ELSE #DaysToAdd + 1
END
END
RETURN DATEADD(DW, #DaysToAdd, #GivenDate);
END
You're are overcomplicating things. The function which you need to use is dateadd: https://learn.microsoft.com/en-us/sql/t-sql/functions/dateadd-transact-sql
Try something along the lines of the following:
CREATE PROCEDURE DateAddsp(#GivenDate DATE, #DaysToAdd int)
AS
BEGIN
RETURN DATEADD(DW, #DaysToAdd, #GivenDate);
END
You cannot use dateadd as the name for the stored proc, as it is a reserved word in SQL Server - it is the name of the function which I just utilized above.
Instead of a procedure, this is an in-line table-valued function to get the result of adding working days to a date.
create function dbo.udf_add_working_days (#Date date, #Days int)
returns table with schemabinding as return (
with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, days as (
select top (1000)
[Date]=convert(date,dateadd(day,row_number() over(order by (select 1))-1,#date))
from n as deka cross join n as hecto cross join n as kilo
order by [Date]
)
, working_days as (
select top (#Days)
[Date]
from days
where datename(weekday,[date]) not in ('Saturday','Sunday')
/* -- put your non working days table info here and uncomment this clause
and not exists (
select 1
from dbo.HolidayTable h
where days.[Date] = h.HolidayDate
)
--*/
order by [Date]
)
select top 1 [date]
from working_days
order by [Date] desc
);
go
and you would call it like so:
select [date]
from dbo.udf_add_working_days('20170401',10)
rextester demo: http://rextester.com/ITXB6884
returns:
+------------+
| date |
+------------+
| 2017-04-14 |
+------------+
Or you can call it using dates from a query using cross apply()
select ...
, x.Date as NewWorkDate
from t
dbo.udf_add_working_days (t.[Date], t.[NumberOfDays]) as x
Reference on inline table valued functions
When is a SQL function not a function? "If it’s not inline, it’s rubbish." - Rob Farley
Inline Scalar Functions - Itzik Ben-Gan
Scalar functions, inlining, and performance: An entertaining title for a boring post - Adam Machanic
TSQL User-Defined Functions: Ten Questions You Were Too Shy To Ask - Robert Sheldon

SQL Server CTE For Each Date

I am stuck at this point where I need to get a report for a screen in my project.
The user input is start date and end date...
I need the "closed task count" for each day between those values. If there are no tasks on some dates, the count should return "0". Here I am so far, but I really don't understand what I am doing wrong. Please help!
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
ALTER PROCEDURE APP.GET_TASK_ENTRY_ACTIVE_GRAPH
(
#START_DATE DATETIME,
#END_DATE DATETIME
)
AS
BEGIN
SET NOCOUNT ON
CREATE TABLE #TMP_TASK_VALS
(
DATE_VALUE DATETIME,
VAL INT
)
INSERT INTO #TMP_TASK_VALS
(
DATE_VALUE, VAL
)
SELECT CONVERT(DATETIME, TASK_CLOSING_DATE), COUNT(1) FROM APP.TASK_ENTRIES (NOLOCK)
WHERE TASK_CLOSING_DATE BETWEEN #START_DATE AND #END_DATE
GROUP BY TASK_CLOSING_DATE
--ORDER BY TASK_CLOSING_DATE DESC
--SELECT * FROM #TMP_TASK_VALS
;WITH CTE_DAILY(DAY) AS
(
SELECT #START_DATE AS DAY
UNION ALL
SELECT DAY + 1 FROM CTE_DAILY
WHERE DAY < #END_DATE
)
SELECT CTE_DAILY.DAY, COUNT(VAL) FROM CTE_DAILY WITH (NOLOCK) LEFT JOIN #TMP_TASK_VALS WITH (NOLOCK) ON #TMP_TASK_VALS.DATE_VALUE = CTE_DAILY.DAY
GROUP BY CTE_DAILY.DAY
DROP TABLE #TMP_TASK_VALS
END
GO
/*
exec APP.GET_TASK_ENTRY_ACTIVE_GRAPH '2015-08-10', '2015-08-16'
*/
The result is like, I have all the dates continuosly, but the value (count) is all zero.
Cheers.
I observed that you are converting TASK_CLOSING_DATE to datetime in first query, but not while using BETWEEN.I see some datatype mismatch. Please try to convert while joining with CTE too.
EDIT: As per OP's feedback the issues is with conversion of date and datetime fields.
OP's Comment : Converting my DATETIME to DATE totally solved this

SQL Query for showing dates not stored in the table

I have a table in which i am storing dates and other information.
I wanna display the records for the dates which are not stored in the table.
Eg.. i have dates 01/01/2012[dd/mm/yyyy] , 03/01/2012 , 06/01/2012.
I wanna show the output for the dates 02/01/2012 ,04/05/2012 , 05/01/2012.
Query for this please in SQLServer2008
You can use the DATEADD function. For example, to add 1 day to current date:
SELECT DATEADD(dd, 1, GETDATE())
You can find more information on MSDN.
;WITH MYCTE AS
(
SELECT CAST('1900-01-01' AS DATETIME) DateValue
UNION ALL
SELECT DateValue + 1
FROM MYCTE
WHERE DateValue + 1 < '3550-12-31'
)
SELECT DateValue, B.SomeColumn
FROM MYCTE A
LEFT JOIN MyTable B ON A.DateValue = B.DateValue
OPTION (MAXRECURSION 0)
I got the date range cte from here (and it takes 4 seconds to generate a table of 250000 rows)
This code displays all dates from 2011 year missing in your table:
create table #dates (d datetime);
declare #start_period datetime;
set #start_period='01.01.2011';
declare #end_period datetime;
set #end_period='01.01.2012';
declare #d datetime;
set #d=#start_period
while(#d<#end_period)
begin
insert into #dates (d) values (#d)
SET #d=#d+1
end
select d from #dates where d not in (select <date> from <your_table>)

Resources