If I have a table with a StartDate column and an EndDate column can I produce a query that returns a set including every day in the range. I could use a table variable and do some procedural code but I'd like to know if there's a way to do it in a query.
E.g. StartDate = 1/1/2010, EndDate = 1/5/2010, result would be:
1/1/2010
1/2/2010
1/3/2010
1/4/2010
1/5/2010
...for every row in the table that has the StartDate and EndDate columns.
*I'm on SQL 2005
SQL Server 2005+:
WITH dates AS (
SELECT t.startdate 'date'
FROM TABLE t
WHERE t.startdate = '1/1/2010'
UNION ALL
SELECT DATEADD(dd, 1, t.date)
FROM dates t
WHERE DATEADD(dd, 1, t.date) <= (SELECT t.enddate FROM TABLE t WHERE t.enddate = '1/5/2010'))
SELECT ...
FROM TABLE t
JOIN dates d ON d.date = t.date
If your dates are no more than 2047 days apart:
SELECT DATEADD(day,
n.number,
(SELECT t.startdate FROM TABLE t WHERE t.startdate = '1/1/2010')
)
FROM (SELECT DISTINCT number
FROM MASTER.dbo.SPT_VALUES
WHERE name IS NULL) n
WHERE DATEADD(day, n.number, (SELECT t.startdate FROM TABLE t WHERE t.startdate = '1/1/2010')) <= (SELECT t.endate FROM TABLE t WHERE t.endate = '1/5/2010')
with DateList as
(
select cast('1/1/2010' as datetime) DateValue
union all
select DateValue + 1
from DateList
where DateValue + 1 >= '1/1/2010' AND DateValue +1 <= '1/5/2010'
)
select CONVERT(varchar, DateValue, 101)
from DateList
OPTION (MAXRECURSION 0)
Related
I have to create a query to return results for a multi-axis chart. I need to count the number of Ids created for each date between 2 dates. I tried this:
DECLARE #StartDate datetime2(7) = '11/1/2020',
#EndDate datetime2(7) = '2/22/2021'
;WITH Date_Range_T(d_range) AS
(
SELECT DATEADD(DAY, DATEDIFF(DAY, 0, #EndDate) - #StartDate, 0)
UNION ALL SELECT DATEADD(DAY, 1, d_range)
FROM Date_Range_T
WHERE DATEADD(DAY, 1, d_range) < #EndDate
)
SELECT d_range, COUNT(Id) as Total
FROM Date_Range_T
LEFT JOIN [tbl_Support_Requests] on ([tbl_Support_Requests].CreatedDate Between #StartDate AND #EndDate)
GROUP BY d_range ORDER BY d_range ASC
Of course, the problem is with the ;WITH which returns the error
Operand type clash: datetime2 is incompatible with int.
The above works if I give it a specific number of days from the current date like:
;WITH Date_Range_T(d_range) AS
(
SELECT DATEADD(DAY, DATEDIFF(DAY, 0, GETDATE()) - 6, 0)
UNION ALL SELECT DATEADD(DAY, 1, d_range)
FROM Date_Range_T
WHERE DATEADD(DAY, 1, d_range) < GETDATE()
)
Which returns:
The problem is that I cannot figure out how to substitute the date range.
Improving on Dale K's answer, I suggest you use a tally table or function, as this is usually more performant.
I have used Itzik Ben-Gan's well-known one below:
DECLARE #StartDate date = '2020-11-01', #EndDate date = '2021-02-22';
WITH
L0 AS ( SELECT 1 AS c
FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),
(1),(1),(1),(1),(1),(1),(1),(1)) AS D(c) ),
L1 AS ( SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B ),
L2 AS ( SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B ),
Nums AS ( SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS rownum
FROM L2 )
Date_Range_T (d_range) AS (
SELECT TOP(DATEDIFF(day, #StartDate, #EndDate) + 1)
DATEADD(day, rownum - 1, #StartDate) AS d_range,
DATEADD(day, rownum, #StartDate) AS d_rangeNext
FROM Nums
)
SELECT d_range, COUNT(Id) AS Total
FROM Date_Range_T
LEFT JOIN tbl_Support_Requests R
ON R.CreatedDate >= T.d_range AND R.CreatedDate < T.d_rangeNext
GROUP BY d_range
ORDER BY d_range ASC
No need to reinvent the wheel - there are many examples of recursive CTE calendar tables out there, similar to below.
DECLARE #StartDate date = '01-Nov-2020', #EndDate date = '22-Feb-2021';
WITH Date_Range_T (d_range) AS (
SELECT #StartDate AS d_range
UNION ALL
SELECT DATEADD(DAY, 1, d_range)
FROM Date_Range_T
WHERE DATEADD(DAY, 1, d_range) < #EndDate
)
SELECT d_range, COUNT(Id) AS Total
FROM Date_Range_T
LEFT JOIN tbl_Support_Requests R ON R.CreatedDate = d_range
GROUP BY d_range
ORDER BY d_range ASC
-- Set to the max number of days you require
OPTION (MAXRECURSION 366);
Comments:
Why use a datetime2 for a date?
Do you definitely want < the end date or <=?
Are you familiar with how between works - its not always intuitive.
Alias all tables for better readability.
Semi-colon terminate all statements.
Consistent casing makes the query easier to read.
Use an unambiguous date format for date strings.
I'm having trouble to display the last 12 months for each record, can anyone help?
Right now I can only display one month for each record.
DECLARE #DateEnd as DATETIME = DATEADD(month,((YEAR(getdate())-1900)*12) + MONTH(getdate())-1,-1)
-- SET #DateEnd = '20191130'
DECLARE #Frequency_List table (FREQUENCY_ID char(3)); INSERT into #Frequency_List values ('118') -- ('111'),('118'),('110') -- 'MTD','QTD','YTD'
DECLARE #Entity_List table (ENTITY_NAME char(50)); INSERT into #Entity_List values
('F1000'),('R2202'),('R528'),('R810'),('R567'),('R402I'),('R508'),('F1000'),('A950A'),('R557'),('R559'),('R560'),('TBNOBL'),('ALTACORP'),('R590RVME'),('Z490'),('R5070'),('R591'),('R710')
select P.PORTF_CODE, F.EXT_NAME, REPLACE(CONVERT(VARCHAR(10), AKRE.BEGIN_DATE, 102), '.', '-') as 'BEGIN_DATE', REPLACE(CONVERT(VARCHAR(10), AKRE.END_DATE, 102), '.', '-') as 'END_DATE',
ISNULL(AKRE.PTF_END_OAD,0) 'PTF End Duration', ISNULL(AKRE.BMK_END_OAD,0) 'BMK End Duration', (ISNULL(AKRE.PTF_END_OAD,0)-ISNULL(AKRE.BMK_END_OAD,0)) 'Diff End Duration',
ISNULL(AKRE.PTF_END_OAS,0)*10000 'PTF End Spread', ISNULL(AKRE.BMK_END_OAS,0)*10000 'BMK End Spread', (ISNULL(AKRE.PTF_END_OAS,0)-ISNULL(AKRE.BMK_END_OAS,0))*10000 'Diff End Spread',
((ISNULL(AKRE.PTF_END_OAD,0)*(ISNULL(AKRE.PTF_END_OAS,0)*10000))-(ISNULL(AKRE.BMK_END_OAD,0)*(ISNULL(AKRE.BMK_END_OAS,0)*10000))) 'DIF_END_DTS'
from BISAMDW..ATTX_KEY_RATES_EFFECTS AKRE
left join BISAMDW..PORTFOLIO P on P.PORTF_ID = AKRE.PORTF_ID
left join BISAMDW..ATTR_INSTRUMENT AI on AI.ATINS_ID = AKRE.ATINS_ID
left join [BISAMDW].[dbo].[UD_GROUP] GRP on AKRE.USER_DEFINED_GROUP_ID=GRP.USER_DEFINED_GROUP_ID
left join BISAMDW..T_FREQUENCY F on F.FREQUENCY_ID = AKRE.FREQUENCY_ID
where AKRE.END_DATE = #DateEnd and P.PORTF_NAME in ( select ENTITY_NAME from #Entity_List)
and AKRE.PORTF_CONFIG_ID in ( 1 )
and AKRE.FREQUENCY_ID in (select FREQUENCY_ID from #Frequency_List)
and AKRE.PTF_RETURN is NOT null
and GRP.EXT_CODE in ('BARCLAYS','MASTER_2016')
order by 1,2
The result for the columns of start month and end month in sample resultshould be something like this:
MonthStartDate MonthEndDate
2011-04-01 2011-04-30
2011-05-01 2011-05-31
2011-06-01 2011-06-30
2011-07-01 2011-07-31
2011-08-01 2011-08-31
2011-09-01 2011-09-30
2011-10-01 2011-10-31
2011-11-01 2011-11-30
2011-12-01 2011-12-31
....
Thank you!
If what you want is generate a list of month starts and ends for the last 12 months (plus the current month), then one option is to use a recursive query:
with cte as (
select
dateadd(
year,
-1,
datefromparts(year(getdate()), month(getdate()), 1)
) monthStartDate
union all
select dateadd(month, 1, monthStartDate)
where monthStartDate < dateadd(month, -1, getdate())
)
select monthStartDate, eomonth(monthStartDate) monthEndDate from cte
If you are going to use this data several times, then you should store the results of this query in a separate table, which you can then use directly in your queries. This is called a calendar table.
Personally, I would suggest creating a Calendar Table (there are 100's of examples on how to create these), if you simply need the start and end dates of each month. Then getting the start and end dates from that table is trivial:
SELECT MIN(C.CalendarDate) AS MonthStart,
MAX(C.CalendarDate) AS MonthEnd
FROM dbo.Calendar C
WHERE C.CalendarDate >= '20110101'
AND C.CalendarDate < '20200601'
GROUP BY C.CalendarYear,
C.CalendarMonth;
Alternatively, you can use a Tally to generate these on the fly:
DECLARE #StartDate date = '20110101',
#EndDate date = '20200601';
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(MONTH, #StartDate, #EndDate)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 AS I
FROM N N1, N N2, N N3, N N4, N N5)
SELECT DATEADD(MONTH, T.I, #StartDate) AS MonthStart,
EOMONTH(DATEADD(MONTH, T.I, #StartDate)) AS MonthEnd
FROM Tally T;
I have the challenge that I have a table with start and end date of an event:
Event Start End
A 01Jan2018 01Mar2018
B 01Feb2018 01Apr2018
I would like to have a table as output with a group by of active events in a month:
Year Month count_active_events
2018 1 1
2018 2 2
2018 3 2
2018 4 1
Can anyone think of a SQL statement that makes this request feasible?
THX
Lazloo
One method generates the dates and then does the calculation using a correlated subquery or apply:
with dates as (
select cast('2018-01-01' as date) as dte
union all
select dateadd(month, 1, dte)
from dates
where dte < '2018-04-01'
)
select d.dte,
(select count(*)
from t
where t.start <= d.dte and t.end >= d.dte
) as num_active
from dates d;
Try this:
declare #tbl table([Event] char(1), [Start] date , [End] date);
insert into #tbl Values
('A' , '01Jan2018', '01Mar2018'),
('B' , '01Feb2018', '01Apr2018');
declare #start date,
#end date;
select #start = min([Start]), #end = max([End]) from #tbl;
with cte as(
select #start dt from #tbl
union all
select dateadd(month, 1, dt) from cte
where dateadd(month, 1, dt) <= #end
)
select datepart(month, c.dt), datepart(year, c.dt), count(distinct [Event]) from cte c
left join #tbl t on c.dt between t.Start and t.[End]
group by datepart(month, c.dt), datepart(year, c.dt)
You need apply with recursive cte to generate start event dates :
with tt1 as (
select event, cast(start as date) start, cast([end] as date) [end]
from table
union all
select event, dateadd(month, 1, start), [end]
from tt1
where start < [end]
)
select year(tt.dt), month(tt.dt), tt1.cnt
from table t cross apply
( values (start), ([end])
) tt (dt) outer apply
( select count(*) cnt
from tt1
where year(tt1.start) = year(tt.dt) and
month(tt1.start) = month(tt.dt)
) tt1;
I have a list in a table with a StartDate (not null) and an EndDate (null) (both of type date without time).
An active entry is one with StartDate <= querydate and EndDate with null or > querydate.
So my query would basically be like
SELECT *
FROM MyDataTable
WHERE StartDate <= mydate
AND (EndDate IS NULL OR EndDate >= mydate )
For a statistic in a diagram, I want to know which ones are active in a specific month for the last months. I loop (programatically) over the year and month (2017-10, 2017-11, 2017-12, 2018-1, ... )
How can I select all active entries using year and month of the program loop but ignoring the day part?
If you want actives for the entire month:
SELECT *
FROM MyDataTable
WHERE StartDate <= '2017-10-01' AND
(EndDate IS NULL OR EndDate >= '2017-11-01');
If you want actives at any time during the month:
SELECT *
FROM MyDataTable
WHERE StartDate < '2017-11-01' AND
(EndDate IS NULL OR EndDate > '2017-10-01');
I think you can achieve this by using only months by using 'DATEDIFF ( datepart , startdate , enddate )':
DECLARE #startDate DATE = '2017-11-01';
DECLARE #numberOfMonth INT;
SELECT #numberOfMonth = datediff(month, #startDate, GETDATE());
SELECT *
FROM MyDataTable
WHERE datediff(month, StartDate, GETDATE()) >= #numberOfMonth
AND (EndDate IS NULL OR datediff(month, EndDate, GETDATE()) <=
#numberOfMonth)
I am trying to get the 30 days report of users, that will return date and total count of users as count created on that date and i did it with this query
Select count(*) As [Count] ,
(SELECT CONVERT(date, AddDate)) As [Date]
from Users
WHERE AddDate >= (SELECT DateAdd(month, -1, Convert(date, GetDate())))
Group By CONVERT(date, AddDate)
it give me only those dates on which any user is created, but i want to show all 30 days either if it has count 0.
Same Case i want to do with monthly report.
i am getting months in which users are created , now i want to change it to get last 12 months from this month and their total users count. For this i am using this query
Select count(*) As [Count] ,
(Select DATEPART( month , DateAdd( month , DATEPART(mm,AddDate) , -1 ) )) as Month
from Users
WHERE AddDate >= (SELECT DateAdd(YEAR, -1, Convert(date, GetDate())))
Group By DATEPART(mm,AddDate)
Using a Calendar CTE:
With NumberSequence ( Number ) as
(
Select 1 as Number
union all
Select Number + 1
from NumberSequence
where Number <= 30
)
, CalendarCTE as
(
select cast(dateadd(dd, -30 + Number,getdate()) as Date) as CalDate
from Numbersequence
)
select CalDate, count(U1.*) as CountUsers
from CalendarCTE
left join Users U1
on CalDate = convert(date, U1.AddDate)
group by CalDate
As I mentioned in comment, You need a Calendar table and Left Join
SELECT Count(u.adddate) AS [Count],
c.dates AS [Date]
FROM calendar_table C
LEFT JOIN users U
ON c.dates = CONVERT(DATE, adddate)
WHERE c.dates >= Dateadd(month, -1, CONVERT(DATE, Getdate()))
GROUP BY c.dates
To generate/create a calendar table or dates check out the below questions
How to generate a range of dates in SQL Server
Generate Dates between date ranges
How to create a Calender table for 100 years in Sql
Try this script :
WITH CTEDates
AS
(
SELECT CAST(GetDate() as date) AS [date]
UNION ALL
SELECT DATEADD(dd, 1, [date])
FROM CTEDates
WHERE DAY([date]) <= 30
)
Select count(*) As [Count] ,CONVERT(date, AddDate) As [Date]
from CTEDates
LEFT JOIN Users ON CTEDates.date=CONVERT(date, AddDate)
WHERE AddDate >= DateAdd(month, -1, GetDate())
Group By CONVERT(date, AddDate)
DECLARE #StartDate Datetime
DECLARE #EndDate Datetime
CREATE TABLE #tMyCalanderDate (dtDate Datetime Primary key)
SELECT #StartDate = '01-Sep-2016'
SELECT #EndDate = '30-Sep-2016'
WHILE #StartDate <= #EndDate
BEGIN
INSERT INTO #tMyCalanderDate (dtDate)
SELECT #StartDate
SELECT #StartDate = DATEADD(day,1,#StartDate)
END
SELECT count(A.UserID) As [Count] , B.dtDate As [Date]
FROM Users AS A
RIGHT JOIN #tMyCalanderDate AS B ON CONVERT(date, A.AddDate) = CONVERT(date, B.dtDate)
WHERE CONVERT(date, A.AddDate) BETWEEN #StartDate AND #EndDate
Group By CONVERT(date, B.dtDate)
You can use a CTE to get a thirty day calendar. Then left join your Users table to it.
DECLARE #CurrentTime DATETIME = GETDATE()
;WITH CTE AS
(
SELECT CONVERT(DATE, #CurrentTime) AS [Date]
UNION ALL
SELECT DATEADD(dd, -1, Date)
FROM CTE
WHERE DATEADD(dd, 29, Date) > #CurrentTime
)
SELECT COUNT(U.AddDate) AS [Count]
, CTE.[Date] AS [Date]
FROM CTE
LEFT JOIN users U
ON CTE.Date = CONVERT(Date, AddDate)
GROUP BY CTE.Date
You can use a similar CTE to get the twelve month calendar and use the same joins to get the count.
HTH.