SQL Server 2008 - Enumerate multiple date ranges - sql

How can I enumerate multiple date ranges in SQL Server 2008? I know how to do this if my table contains a single record
StartDate EndDate
2014-01-01 2014-01-03
;WITH DateRange
AS (
SELECT #StartDate AS [Date]
UNION ALL
SELECT DATEADD(d, 1, [Date])
FROM DateRange
WHERE [Date] < #EndDate
)
SELECT * FROM DateRange
OUTPUT
2014-01-01, 2014-01-02, 2014-01-03
I am however lost as how to do it if my table contains multiple records. I could possibly use the above logic in a cursor but want to know if there is a set based solution instead.
StartDate EndDate
2014-01-01 2014-01-03
2014-01-05 2014-01-06
DESIRED OUTPUT:
2014-01-01, 2014-01-02, 2014-01-03, 2014-01-05, 2014-01-06

Well, let's see. Define the ranges as a table. Then generate the full range of dates from the first to the last date. Finally, select the dates that are in the range:
with dateranges as (
select cast('2014-01-01' as date) as StartDate, cast('2014-01-03' as date) as EndDate union all
select '2014-01-05', '2014-01-06'
),
_dates as (
SELECT min(StartDate) AS [Date], max(EndDate) as enddate
FROM dateranges
UNION ALL
SELECT DATEADD(d, 1, [Date]), enddate
FROM _dates
WHERE [Date] < enddate
),
dates as (
select [date]
from _dates d
where exists (select 1 from dateranges dr where d.[date] >= dr.startdate and d.[date] <= dr.enddate)
)
select *
from dates
. . .
You can see this work here.

You could grab the min and max dates first, like so:
SELECT #startDate = MIN(StartDate), #endDate = MAX(EndDate)
FROM YourTable
WHERE ...
And then pass those variables into your date range enumerator.
Edit... Whoops, I missed an important requirement. See the accepted answer.

As GordonLinoff mentioned, you should:
Store your ranges in a table
Generate a range of dates that encompasses your ranges
Filter down to only those dates that fall within the range
The following query builds up a collection of numbers, and then uses that to quickly generate all of the dates that fall within each range.
-- Create a table of digits (0-9)
DECLARE #Digits TABLE (digit INT NOT NULL PRIMARY KEY);
INSERT INTO #Digits(digit)
VALUES (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
WITH
-- Store our ranges in a common table expression
CTE_DateRanges(StartDate, EndDate) AS (
SELECT '2014-01-01', '2014-01-03'
UNION ALL
SELECT '2014-01-05', '2014-01-06'
)
SELECT DATEADD(DAY, NUMBERS.num, RANGES.StartDate) AS Date
FROM
(
-- Create the list of all 3-digit numbers (0-999)
SELECT D3.digit * 100 + D2.digit * 10 + D1.digit AS num
FROM #Digits AS D1
CROSS JOIN #Digits AS D2
CROSS JOIN #Digits AS D3
-- Add more CROSS JOINs to #Digits if your ranges span more than 999 days
) NUMBERS
-- Join to our ranges table to generate the dates and filter them
-- down to those that fall within a range
INNER JOIN CTE_DateRanges RANGES
ON DATEADD(DAY, NUMBERS.num, RANGES.StartDate) <= RANGES.EndDate
ORDER BY
Date
The date creation is done by joining our number list with our date ranges, using the number as a number of days to add to the StartDate of the range. We then filter out any results where the generated date for a given range falls beyond that range's EndDate. Since we're adding a non-negative number of days to the StartDate to generate the date, we know that our date will always be greater-than-or-equal-to the StartDate of the range, so we don't need to include StartDate in the WHERE clause.
This query will return DATETIME values. If you need a DATE value, rather than a DATETIME value, you can simply cast the value in the SELECT clause.
Credit goes to Itzik Ben-Gan for the digits table.

Related

Count # of Saturdays given a date range

I have a datetime field and a net field. The Sat Count field is done by =IIf(DatePart("w",Fields!DespatchDate.Value)=7,1,0)
I want to total the count of the Saturdays given a starting date and end date (typically a month).
I tried =Sum(IIf(DatePart("w",Fields!DespatchDate.Value)=7,1,0) but the total is wrong.
I also want to count Saturdays for rest of the month, e.g there's a missing 3rd Saturday in the picture.
I also want to do a total of the Net for Saturdays.
Can you point me in the direction. I can do it in SQL or in SSRS
Considering that we do not have any Input or desired output provided, I am assuming that You just want to count Saturdays in a given range:
Select COUNT(*), SUM(Net)
FROM table
WHERE Day# = 7 AND Date BETWEEN '2021-02-16' AND '2021-02-23'
Assuming you want to count saturdays even if it is not part of your dataset, what you need to do is pad out all your dates for the given range and then join it to your base data set.
This would ensure that it accounts for ALL days of the week regardless of a dispatch event occuring on that date / day.
Below is some SQL code that might help you make a start.
declare #startdate date = '2021-02-01'
declare #enddate date = '2021-02-28'
if OBJECT_ID ('tempdb..#dates') is not null
drop table #dates
;WITH mycte AS
(
SELECT CAST(#startdate AS DATETIME) DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue + 1 < #enddate
)
SELECT DateValue into #dates
FROM mycte
OPTION (MAXRECURSION 0)
select
d.DateValue
, datepart(weekday,d.DateValue) as day_no
,case when datepart(weekday,d.DateValue) = 7 then isnull(t.net,0) else 0 end as sat_net
,case when datepart(weekday,d.DateValue) = 1 then isnull(t.net,0) else 0 end as sun_net
from #dates d
left join your_table t
on d.DateValue = t.some_date
drop table #dates
Since I don't know what your required output is, I cannot summarise this any further. But you get the idea!

SQL Cross Join getting all dates between date range

I have a table with the following structure:
ID: StartDate: EndDate
I want to show all dates in the date range for each ID.
Eg
ID = 1: StartDate = 01/01/2018: EndDate = 03/01/2018
ID: 1 01/01/2018
ID: 1 02/01/2018
ID: 1 03/01/2018
I think i need to use a cross join but im unsure how to create this for multiple rows?
Here is the CTE for SQL Server, the syntax is somewhat different:
declare #startdate date = '2018-01-01';
declare #enddate date = '2018-03-18';
with
dates as (
select #startdate as [date]
union all
select dateadd(dd, 1, [date]) from dates where [date] < #enddate
)
select [date] from dates
So i ended up using a date table and just cross referencing that
select *
from Date d
inner join WorkingTable w
on d.Date >= w.StartDate
and d.date < w.EndDate
In standard SQL you can use a recursive CTE:
with recursive dates as (
select date '2018-01-01' as dte
union all
select dte + interval '1 day'
from dates
where dte < date '2018-01-03'
)
select dte
from dates;
The exact syntax (whether recursive is needed and date functions) differ among databases. Not all databases support this standard functionality.
Now got this for only one id..,
create table #dateTable(id int, col1 date, col2 date)
insert into #dateTable values(1,'05-May-2018','08-May-2018') ,(2,'05-May-2018','05-May-2018')
select *from #dateTable
with cte(start, ends) as(
select start = (select top 1 col1 from #dateTable), ends = (select top 1 col2 from #dateTable)
union all
select DATEADD(dd,1,start),ends from cte where start <> ends
)select start from cte option (maxrecursion 10)
I'm still working... I update soon...!

sql query group by date range

I have table with below data -
StartDate EndDate Amount
2/1/2016 4/30/2016 2.265
2/1/2016 12/31/2099 16.195
5/1/2016 12/31/2099 37.75
I am trying to write a query to sum the amount on date range and give me below result
StartDate EndDate Amount
2/1/2016 4/30/2016 18.46
5/1/2016 12/31/2099 53.945
The result needs to be distinct date range with amount summed for that date range. As in above example, row 2 has dates that overlap row 1 and 3. So the row 2 amount needs to be added in row 1 and row 3.
I am writing this query on sql server 2012, Please advise on what approach I should take.
Below is query to generate sample data
SELECT * INTO #tmp_GridResults_1
FROM (
SELECT N'2016-02-01 00:00:00.000' AS [StartDate], N'2016-04-30 00:00:00.000' AS [EndDate], N'2.265' AS [Amount] UNION ALL
SELECT N'2016-02-01 00:00:00.000' AS [StartDate], N'2099-12-31 00:00:00.000' AS [EndDate], N'16.195' AS [Amount] UNION ALL
SELECT N'2016-05-01 00:00:00.000' AS [StartDate], N'2099-12-31 00:00:00.000' AS [EndDate], N'37.75' AS [Amount] ) t;
SELECT [StartDate], [EndDate], [Amount]
FROM #tmp_GridResults_1
I don't think you'll be able to group with both StartDate and EndDate. However, if you use just StartDate and Amount you can try this:
select StartDate,sum(Amount) as Amount from #tmp_GridResults_1 group by StartDate
This will give you the grouping of Amount by StartDate.
From my previews comment i think you want to group by StartDate, in that case you can use:
SELECT
mt.StartDate,
SUM(Amount) AS 'Amount'
FROM MyTable mt
WHERE mt.StartDate BETWEEN
'2016-05-18 00:00:00.000' AND '2016-06-20 00:00:00.000' --your date range
GROUP BY StartDate
However you need to specify in your question how you want to group them, because with 2 date fields you can group the following ways:
GROUP BY StartDate --groups records that have the same StartDate
GROUP BY EndDate --groups records that have the same EndDate
GROUP BY StartDate, EndDate --groups records that have same StartDate and EndDate
Also with 2 date fields your date range can vary a lot.
WHERE StartDate BETWEEN #sd AND #ed --will get records whose start date is inside the provided range
WHERE EndDate BETWEEN #sd AND #ed --will get records whose end date is inside the provided range
WHERE StartDate >= #sd AND EndDate <= #ed -- will get records that started and ended inside the provided date range
WHERE StartDate BETWEEN #sd AND #ed OR EndDate BETWEEN #sd AND #ed
--this last one will get records whose StartDate or EndDate are inside the provided date range
based on this you can build the query that you need, but with the provided data in your question its still ambiguous what you want to achieve, you need to be more specific and provide more data in order to produce an exact answer to your question.
One more thing to add is that if you take into account time it will not group them if the date is the same but time is different i.e.
'2016-05-18 00:00:00.000'
'2016-05-18 01:01:01.001'
--these dates will not be grouped
Below query will give the required result :-
select StartDate,EndDate,SUM(Amount) over (partition by StartDate) AS Amount into #t1
from table_name
select StartDate,EndDate,SUM(Amount) over (partition by EndDate) AS Amount into #t2
from table_name
select * from (
select distinct P.StartDate,P.EndDate,P.Amount from (
select StartDate,MIN(EndDate) EndDate,Amount from #t1
group by StartDate,Amount) P
JOIN (
select MIN(StartDate) StartDate,EndDate,Amount from #t2
group by EndDate,Amount) Q on P.StartDate=Q.StartDate OR P.EndDate=Q.EndDate
where P.Amount>=Q.Amount
UNION
select distinct P.StartDate,P.EndDate,P.Amount from (
select MIN(StartDate) StartDate,EndDate,Amount from #t2
group by EndDate,Amount) P
JOIN (
select StartDate,MIN(EndDate) EndDate,Amount from #t1
group by StartDate,Amount) Q on P.StartDate=Q.StartDate OR P.EndDate=Q.EndDate
where P.Amount>=Q.Amount
) A
Just Replace table_name by the table for which you want result, Let me know in case of any confusion

Total number of days between two dates by Year

I am currently working on a query to calculate total no of days between date ranges by year
Table:
Start Date End Date
01/01/2013 04/30/2014
11/01/2014 05/31/2015
06/01/2015 12/31/2015
My expected result.
2013 - 365
2014 - 180
2015 - 365
I can do this in multiple steps using temp table. Is there any simple way to do this calculation.
Thanks
Okay so try this out:
SELECT * INTO #yourTable
FROM
(
SELECT CAST('01/01/2013' AS DATE),CAST('04/30/2014' AS DATE) UNION ALL
SELECT '11/01/2014','05/31/2015' UNION ALL
SELECT '06/01/2015','12/31/2015'
) A(StartDate,EndDate);
DECLARE #MaxEndDate DATE = (SELECT MAX(EndDate) FROM #yourTable);
WITH CTE_Dates
AS
(
SELECT MIN(StartDate) dates
FROM #yourTable
UNION ALL
SELECT DATEADD(Day,1,dates)
FROM CTE_Dates
WHERE dates < #MaxEndDate
)
SELECT YEAR(dates) yr,COUNT(DATES) cnt
FROM #yourTable A
CROSS APPLY(SELECT dates FROM CTE_Dates WHERE dates BETWEEN A.startDate AND A.EndDate) CA
GROUP BY YEAR(dates)
OPTION (MAXRECURSION 0)
PRINT DATEDIFF(DAY, '1/1/2013', '4/30/2014')
This will give you what you are looking for. Just repeat for all the dates that you want.
This gives the number of times the midnight boundary is crossed between the two dates. You may decide to need to add one to this if you're including both dates in the count - or subtract one if you don't want to include either date.

Generate missing dates + Sql Server (SET BASED)

I have the following
id eventid startdate enddate
1 1 2009-01-03 2009-01-05
1 2 2009-01-05 2009-01-09
1 3 2009-01-12 2009-01-15
How to generate the missing dates pertaining to every eventid?
Edit:
The missing gaps are to be find out based on the eventid's. e.g. for eventid 1 the output should be 1/3/2009,1/4/2009,1/5/2009.. for eventtype id 2 it will be 1/5/2009, 1/6/2009... to 1/9/2009 etc
My task is to find out the missing dates between two given dates.
Here is the whole thing which i have done so far
declare #tblRegistration table(id int primary key,startdate date,enddate date)
insert into #tblRegistration
select 1,'1/1/2009','1/15/2009'
declare #tblEvent table(id int,eventid int primary key,startdate date,enddate date)
insert into #tblEvent
select 1,1,'1/3/2009','1/5/2009' union all
select 1,2,'1/5/2009','1/9/2009' union all
select 1,3,'1/12/2009','1/15/2009'
;with generateCalender_cte as
(
select cast((select startdate from #tblRegistration where id = 1 )as datetime) DateValue
union all
select DateValue + 1
from generateCalender_cte
where DateValue + 1 <= (select enddate from #tblRegistration where id = 1)
)
select DateValue as missingdates from generateCalender_cte
where DateValue not between '1/3/2009' and '1/5/2009'
and DateValue not between '1/5/2009' and '1/9/2009'
and DateValue not between '1/12/2009'and'1/15/2009'
Actually what I am trying to do is that, I have generated a calender table and from there I am trying to find out the missing dates based on the id's
The ideal output will be
eventid missingdates
1 2009-01-01 00:00:00.000
1 2009-01-02 00:00:00.000
3 2009-01-10 00:00:00.000
3 2009-01-11 00:00:00.000
and also it has to be in SET BASED and the start and end dates should not be hardcoded
Thanks in adavnce
The following uses a recursive CTE (SQL Server 2005+):
WITH dates AS (
SELECT CAST('2009-01-01' AS DATETIME) 'date'
UNION ALL
SELECT DATEADD(dd, 1, t.date)
FROM dates t
WHERE DATEADD(dd, 1, t.date) <= '2009-02-01')
SELECT t.eventid, d.date
FROM dates d
JOIN TABLE t ON d.date BETWEEN t.startdate AND t.enddate
It generates dates using the DATEADD function. It can be altered to take a start & end date as parameters. According to KM's comments, it's faster than using the numbers table trick.
Like rexem - I made a function that contains a similar CTE to generate any series of datetime intervals you need. Very handy for summarizing data by datetime intervals like you are doing.
A more detailed post and the function source code are here:
Insert Dates in the return from a query where there is none
Once you have the "counts of events by date" ... your missing dates would be the ones with a count of 0.