I have a table that where each record has start and end dates. I need to return a record for each day between start and end date fields (including the start and end dates). Using MS SQL Server.
Example:
Current data
Data required:
Looking for recommendations.
Thanks.
You can use recursive cte :
with cte as (
select id, startdate, enddate, startdate as date
from table t
union all
select id, startdate, enddate, dateadd(day, 1, date)
from cte c
where date < enddate
)
select *
from cte c
option (maxrecursion 0);
Related
I have the following table in sql server database environment.
the format of start date MM/DD/YYYY.
I need the result to be like the following table.
based on start date column the record should segregated to each month in the period between start date and end date
You can use a recursive CTE:
with cte as (
select id, startdate as dte, enddate
from t
union all
select id,
dateadd(day, 1, eomonth(dte)),
enddate
from t
where eomonth(dte) < enddate
)
select id, dte,
lead(dte, 1, enddate) over (partition by id order by dte)
from cte;
Thank you Gordon Linoff
Using CTE I have got the following result
My code
WITH cte
AS (SELECT 1 AS id,
Cast('2010-01-20' AS DATE) AS trg,
Cast('2010-01-20' AS DATE) AS strt_dte,
Cast('2010-03-15' AS DATE) AS end_dte
UNION ALL
SELECT id,
Dateadd(day, 1, Eomonth (trg)),
strt_dte,
end_dte
FROM cte
WHERE Eomonth(trg) < end_dte)
SELECT id,
trg,
strt_dte,
end_dte,
Lead (trg, 1, end_dte)
OVER (
partition BY id
ORDER BY trg) AS lead_result
FROM cte
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...!
I am trying to add an extra date column in the select statement using a certain where condition.
Below is my current table:
table
I want to add an extra Date column which is add all date between >=Start and <= End-2.
output
Getting error with this query:
SELECT
*, temp_Date AS Date
FROM
Mytable
WHERE
Date >= Start AND Date <= End - 2
Thanks in advance.
Consider this statement as dummy data:
CREATE TABLE MyTable
(
id int not null,
startDate date not null,
endDate date not null,
val int not null
)
insert into MyTable
values
(10,'20171106','20171112',7),
(10,'20171106','20171112',6),
(10,'20171106','20171112',5),
(10,'20171106','20171112',0),
(10,'20171106','20171112',2)
Using recursive CTE you select each tuple as your starting date and increment that date until it reaches the enddate like this:
;WITH rc AS (
SELECT id, startDate, endDate, val
, startDate AS temp_date
FROM MyTable
UNION ALL
SELECT id, startDate, endDate, val
, DATEADD(DAY,1,temp_date)
FROM rc
WHERE DATEADD(DAY,1,temp_date) <= enddate
)
SELECT *
FROM rc
You should be aware the recursion in SQL-Server is expensive and slow on larger data. Also remember to hint the maximum recursive loop amounts as the default is 100. Example:
OPTION (MAXRECURSION 0)
The 0 would be unlimited recursions, with the risk of running infinitely.
As I read you are using a data warehouse and as such it should have a time or date dimension. In such case a simple join would do the work:
SELECT id, startDate, endDate, val
, date_sid AS temp_date
FROM MyTable AS m
INNER JOIN DimDate AS dd
ON dd.date_sid >= startDate
AND dd.date_sid <= endDate
Please consider not using reserved keys for column names (like start, end or value)
Try this:
Select M.*
, Temp_date = Datediff(day, M.start, dateadd(day,-2,M.end))
from MyTable M
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.
I'm currently looking for a SQL solution for the following problem:
SQLFiddle as guidance:
I have a list of not-nullable startdates and nullable enddates. Based on this list I need the total gap time between a given start and enddate.
Based on the SQLFiddle
If I would only have situation 1 in my database the result should be 2 days.
If I would have situation 2 and 3 in my database the result should be 1 day.
I have been pondering this for a couple of days now... any help would be much appreciated!
Regards,
Kyor
Notes: I'm running SQL 2012 ( should any special new features be required )
The best solution will be to create 'Dates' table and start from there, otherwise solution will be unmaintainable. For each date in specified range you can check whether it is covered by ranges in 'dateranges' table and get a count of dates that are not.
Something like this:
SELECT COUNT(*)
FROM
Dates d
WHERE
d.Date BETWEEN #start AND #end
AND NOT EXISTS
(SELECT *
FROM dateranges r
WHERE d.date BETWEEN r.startdate and ISNULL(r.enddate, d.date)
)
CREATE TABLE Dates (
dt DATETIME NOT NULL PRIMARY KEY);
INSERT INTO Dates VALUES('20081204');
INSERT INTO Dates VALUES('20081205');
INSERT INTO Dates VALUES('20090608');
INSERT INTO Dates VALUES('20090609');
-- missing ranges
SELECT DATEADD(DAY, 1, prev) AS start_gap,
DATEADD(DAY, -1, next) AS end_gap,
DATEDIFF(MONTH, DATEADD(DAY, 1, prev),
DATEADD(DAY, -1, next)) AS month_diff
FROM (
SELECT dt AS prev,
(SELECT MIN(dt)
FROM Dates AS B
WHERE B.dt > A.dt) AS next
FROM Dates AS A) AS T
WHERE DATEDIFF(DAY, prev, next) > 1;
-- existing ranges
SELECT MIN(dt) AS start_range,
MAX(dt) AS end_range
FROM (
SELECT dt,
DATEDIFF(DAY, ROW_NUMBER() OVER(ORDER BY dt), dt) AS grp
FROM Dates) AS D
GROUP BY grp;
DROP TABLE Dates;