Recursive CTE Returns Value Outside of Range - sql

While searching for a method to create date ranges I came across a problem with the following query:
DECLARE #StartDate DateTime = '2000-01-01 01:00';
DECLARE #EndDate DateTime = '2020-01-01 00:00';
with Dates as (
select
#StartDate fromDate
UNION ALL
(Select
fromDate = dateadd(day, 1, #EndDate)
from
Dates
where
fromDate >= #StartDate AND
fromDate < #EndDate ))
Select * from Dates
OPTION (MAXRECURSION 0);
The following query returns two rows, one of which is outside of the range,
fromDate
-----------------------
2000-01-01 01:00:00.000
2020-01-02 00:00:00.000
I am aware that there is a way to fix this issue by changing the second half of the query to be different, for example:
Select * from Dates Where fromDate <= #EndDate
I have the following questions:
what is wrong with the query as is?
Why is it that this query returns two values, one of which lies outside the range provided?
This is using Microsoft SQL Server 2008 R2

Because your Recursive CTE add one day from fromDate
Select
fromDate = dateadd(day, 1, #EndDate)
but your condition filter fromDate
where
fromDate >= #StartDate AND
fromDate < #EndDate ))
If you want to do make calendar table by Recursive CTE.
you can try this.
with Dates as (
select #StartDate fromDate,#EndDate endDate
UNION ALL
Select
fromDate = dateadd(day, 1, fromDate),endDate
from
Dates
where
dateadd(day, 1, fromDate) <= #EndDate
)
Select * from Dates
OPTION (MAXRECURSION 0);

The simple answer for #1 / #2: You are selecting DATEADD(day, 1, #endDate) -- which, by definition, is 1 day 'over' your date range.
The real answer: To get the result you want, change:
(Select
fromDate = dateadd(day, 1, #EndDate)
To:
(Select
fromDate = dateadd(day, 1, fromDate)

Related

Establishing Date Parameters by Date:Hour instead of just Date

I have the current declared date parameters and "DATE" expression shown below. It provides the data in terms of date only, excluding the 'by hour' detail. I would like to change the below code to add the hour detail to the "DATE" field.
I'm an amateur in terms of SQL, so any suggestions help.
Here is the code...
Declare #Start_Date datetime, #End_Date datetime
set #Start_Date = DATEADD(DAY, DATEDIFF(DAY, '19000101', GETDATE()), '19000101')
set #End_Date = DATEADD(s,-1,DATEADD(dd, DATEDIFF(d,0,GETDATE())+7,0))
;
--Common Table Express1on (CTE) to get dates*****
With DateSequence( [Date] ) as
( Select #Start_Date as [Date]
union all
Select dateadd(day, 1, [Date])
from DateSequence
where Date < #End_Date)
Thanks in advance!
-Matt
Just add 1 hour instead of 1 day:
with DateHourSequence as (
select #Start_Date as yyyymmddhh
union all
select dateadd(hour, 1, yyyymmddhh)
from DateHourSequence
where yyyymmddhh < #End_Date
)

compute start date of sub-date-range from given main date range in sql server

input is a main date range which is stored in myTbl.StartDate and myTbl.EndDate columns values.
for a given constant value as divider, I need to compute start date of sub-date-ranges gained from dividing main date range to sub-date-ranges with constant value length.
examples are best explanations:
for main date range between '2017-1-1' to '2017-1-22' and const_val = 6 output is:
2017-1-1
2017-1-7 (1 + 6)
2017-1-13 (7 + 6)
2017-1-19 (13 + 6)
its noticable that the SQL user is not administrator(I can't use system tables)
You can use a recursive cte to do this:
DECLARE #startDate DATE = '20170101' ,
#endDate DATE = '20170122' ,
#Const_val INT = 6;
WITH cte
AS ( SELECT #startDate AS DateVal
UNION ALL
SELECT DATEADD(DAY, #Const_val, cte.DateVal)
FROM cte
WHERE DATEADD(DAY, #Const_val, cte.DateVal) <= #endDate
)
SELECT *
FROM cte;
Produces:
DateVal
==========
2017-01-01
2017-01-07
2017-01-13
2017-01-19
The first part of the cte gets the first date:
SELECT #startDate AS DateVal
Then it adds 6 days with the UNION ALL until the WHERE condition is met.
You can try the following:
DECLARE #StartDate DATE = CONVERT(date, '2017-01-01');
DECLARE #EndDate DATE = CONVERT(date, '2017-01-22');
DECLARE #FixDateDiff int = 6;
WITH cte AS (
SELECT #StartDate AS MyDate
UNION ALL
SELECT DATEADD(d, 1, MyDate)
FROM cte
WHERE DATEADD(d, 1, MyDate) <= CONVERT(date, #EndDate)
)
SELECT MyDate
FROM cte
WHERE DATEDIFF(d, #StartDate, MyDate)%#FixDateDiff = 0
However, instead of the fixed dates I assigned to the variables, simply replace the variables with your StartDate and EndDate column and extend the cte accordingly.

Forward dependencies #startdate #enddate=#startdate + 1 Week

Im looking for a way if the user selects
#startdate = '12-01-2015'
then
#enddate = #startdate + 1 Week
this is for my parameter, but please be aware my report is a matrix and uses each week for a sample of data so the parameter needs to automatically select
its enddate from all startdates selected
i.e
startdates in ( '12-01-2015', '19-01-2015')
then endate in ( '18-01-2015', '26-01-2015')
this is what im using for my startdate parameter
select distinct
CAST(startdate AS DATE) as Startdate
FROM [Calendar]
where DatePart(YEAR,startdate) >= 2014
order by 1 asc
Use DATEADD
#startdate = '12-01-2015'
then
#enddate = DATEADD(wk, 1, #startdate)
or if startdate variable has DATE/DATETIME type
#startdate = '12-01-2015'
then
#enddate = #startdate + 7; -- Implicit cast add 7 days
EDIT:
select distinct
CAST(startdate AS DATE) as Startdate,
DATEADD(wk, 1, CAST(startdate AS DATE)) as EndDate
FROM [Calendar]
where DatePart(YEAR,startdate) >= 2014
order by 1 asc

Calculate last days of months for given period in SQL Server

Is it possible to do in SQL: for example I have period where #s_date = '20130101' and #e_date = '20130601' and I want to select all last days of months in this period.
This is example of result:
20130131
20130228
20130331
20130430
20130531
Thanks.
The easiest option is to have a calendar table, with a last day of the month flag, so your query would simply be:
SELECT *
FROM dbo.Calendar
WHERE Date >= #StartDate
AND Date <= #EndDate
AND EndOfMonth = 1;
Assuming of course that you don't have a calendar table you can generate a list of dates on the fly:'
DECLARE #s_date DATE = '20130101',
#e_date DATE = '20130601';
SELECT Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY Object_ID) - 1, #s_date)
FROM sys.all_objects;
Then once you have your dates you can limit them to where the date is the last day of the month (where adding one day makes it the first of the month):
DECLARE #s_date DATE = '20130101',
#e_date DATE = '20130601';
WITH Dates AS
( SELECT Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY Object_ID) - 1, #s_date)
FROM sys.all_objects
)
SELECT *
FROM Dates
WHERE Date <= #e_Date
AND DATEPART(DAY, DATEADD(DAY, 1, Date)) = 1;
Example on SQL Fiddle
You can run the following query and then adjust it by using your table details:
declare #s_date as datetime= '20130101'
declare #e_date as datetime= '20131020'
SELECT DateAdd(m, number, '1990-01-31')
FROM master.dbo.spt_values
WHERE 'P' = type
AND DateAdd(m, number, #s_date) < #e_date
example for 20130101 :
select CONVERT(VARCHAR(8),
dateadd(day, -1, dateadd(month, 1,
convert(datetime, '20130101',112))), 112)
result :
20130131
Try this query
WITH sample
AS (SELECT Cast('2013-04-01' AS DATETIME) Date
UNION ALL
SELECT Dateadd(day, 1, date) dt
FROM sample
WHERE date < Cast('2013-05-05' AS DATETIME))
SELECT *
FROM sample
Fiddle
EOMONTH(#date) is the function you need.
Here is the help page https://learn.microsoft.com/en-us/sql/t-sql/functions/eomonth-transact-sql?view=sql-server-2017
This query gets the las 50 End Of Months.
The original query used as an example is from here.
https://dba.stackexchange.com/a/186829
WITH cte AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) - 1 AS [Incrementor]
FROM [master].[sys].[columns] sc1
CROSS JOIN [master].[sys].[columns] sc2
)
SELECT top 50 EOMONTH(DATEADD(Month, -1 * cte.[Incrementor], GETDATE()))
FROM cte
WHERE EOMONTH(DATEADD(Month, -1 * cte.[Incrementor], GETDATE())) < GETDATE();

Compare current date with stored datetime using month an year only

Using SQL Server 2005 I have a field that contains a datetime value.
What I am trying to do is create 2 queries:
Compare to see if stored datetime is of the same month+year as current date
Compare to see if stored datetime is of the same year as current date
There is probably a simple solution but I keep hitting brick walls using various samples I can find, any thoughts?
Thanks in advance.
Compare the parts of the date:
WHERE YEAR( columnName ) = YEAR( getDate() )
While the other answers will work, they all suffer from the same problem: they apply a transformation to the column and therefore will never utilize an index on that column.
To search the date without a transformation, you need a couple built-in functions and some math. Example below:
--create a table to hold our example values
create table #DateSearch
(
TheDate datetime not null
)
insert into #DateSearch (TheDate)
--today
select getdate()
union all
--a month in advance
select dateadd(month, 1, getdate())
union all
--a year in advance
select dateadd(year, 1, getdate())
go
--declare variables to make things a little easier to see
declare #StartDate datetime, #EndDate datetime
--search for "same month+year as current date"
select #StartDate = dateadd(month, datediff(month, 0, getdate()), 0), #EndDate = dateadd(month, datediff(month, 0, getdate()) + 1, 0)
select #StartDate [StartDate], #EndDate [EndDate], TheDate from #DateSearch
where TheDate >= #StartDate and TheDate < #EndDate
--search for "same year as current date"
select #StartDate = dateadd(year, datediff(year, 0, getdate()), 0), #EndDate = dateadd(year, datediff(year, 0, getdate()) + 1, 0)
select #StartDate [StartDate], #EndDate [EndDate], TheDate from #DateSearch
where TheDate >= #StartDate and TheDate < #EndDate
What the statement does to avoid the transformations, is find all values greater-than or equal-to the beginning of the current time period (month or year) AND all values less-than the beginning of the next (invalid) time period. This solves our index problem and also mitigates any issues related to 3ms rounding in the DATETIME type.
SELECT * FROM atable
WHERE
YEAR( adate ) = YEAR( GETDATE() )
AND
MONTH( adate ) = MONTH( GETDATE() )
It sounds to me like DATEDIFF is exactly what you need:
-- #1 same month and year
SELECT *
FROM your_table
WHERE DATEDIFF(month, your_column, GETDATE()) = 0
-- #2 same year
SELECT *
FROM your_table
WHERE DATEDIFF(year, your_column, GETDATE()) = 0
The datepart function lets you pull the bits you need:
declare #d1 as datetime
declare #d2 as datetime
if datepart(yy, #d1) = datepart(yy, #d2) and datepart(mm, #d1) = datepart(mm, #d2) begin
print 'same'
end
You can use something like this
a)
select *
from table
where MONTH(field) = MONTH(GetDATE())
and YEAR(field) = YEAR(GetDATE())
b)
select *
from table
where YEAR(field) = YEAR(GetDATE())