Every distinct Date between DateA and Date B -TSQL - sql

I'm searching for a query like a calendar giving me back the distinct Dates between "Date A" and Date "A -49 days".
Date A is the a variable. If I look on the Query on Monday to Sunday it will give me back
the Date of the Sunday in the previous Week
the Date of the Sunday in the Week before the previous week
2 Weeks before the Previous Week
5 Weeks before the Previous Week
For Example: I started the query in '2022-01-23'
a_end: '2022-01-16' a_beginn: '2021-12-05' and every date between
b_end:'2022-01-09' b_beginn: '2021-11-29' and every date between
etc.

You could use a recursive CTE :
WITH T(d) AS (
SELECT CAST('2022-01-01' AS date)
UNION ALL
SELECT DATEADD(day, -1, d)
FROM T
WHERE d >= DATEADD(day, -49, '2022-01-01')
)
SELECT d
FROM T
-- OPTION (MAXRECURSION 1000)
If you have more than 100 days to generate you will need to set the MAXRECURSION query hint which is limited to 100 by default (0 means no limit). Beware of infinite loops with this setting though.

You can generate a dynamic calendar table as in this example:
with FindPrevSunday as (
select
dateadd(week,datediff(week, '1900-01-07', getdate()), '1900-01-07') PrevSunday
),
JustFourRows as (
select 1 as x union all select 1 as x union all
select 1 as x union all select 1 as x
),
LotsOfRows as (
select Dte=dateadd(day, -Row_number() over (order by a.x)+1, (select PrevSunday from FindPrevSunday))
from
JustFourRows a --4
cross Join
JustFourRows b --16
cross join
JustFourRows c --64
cross join
JustFourRows d -- 256
)
select Dte
from LotsOfRows
cross join
FindPrevSunday PrevS
where Dte between dateadd(day,-48, Prevs.PrevSunday) and PrevSunday
'1900-01-07' is a fixed reference point; known to be Sunday; datediff(week always brings whole/complete weeks; and we use the cross joins to quickly 'generate' rows corresponding to dates in the calendar; then we assign dates, and then filter for the limit we are interested in. This example can generate up to 256 days, but you can add more cross joins, if you wish.

Related

Finding Active Clients By Date

I'm having trouble writing a recursive function that would count the number of active clients on any given day.
Say I have a table like this:
Client
Start Date
End Date
1
1-Jan-22
2
1-Jan-22
3-Jan-22
3
3-Jan-22
4
4-Jan-22
5-Jan-22
5
4-Jan-22
6-Jan-22
6
7-Jan-22
9-Jan-22
I want to return a table that would look like this:
Date
NumActive
1-Jan-22
2
2-Jan-22
2
3-Jan-22
3
4-Jan-22
4
5-Jan-22
4
6-Jan-22
3
7-Jan-22
3
8-Jan-22
3
9-Jan-22
4
Is there a way to do this? Ideally, I'd have a fixed start date and go to today's date.
Some pieces I have tried:
Creating a recursive date table
Truncated to Feb 1, 2022 for simplicity:
WITH DateDiffs AS (
SELECT DATEDIFF(DAY, '2022-02-02', GETDATE()) AS NumDays
)
, Numbers(Numbers) AS (
SELECT MAX(NumDays) FROM DateDiffs
UNION ALL
SELECT Numbers-1 FROM Numbers WHERE Numbers > 0
)
, Dates AS (
SELECT
Numbers
, DATEADD(DAY, -Numbers, CAST(GETDATE() -1 AS DATE)) AS [Date]
FROM Numbers
)
I would like to be able to loop over the dates in that table, such as by modifying the query below for each date, such as by #loopdate. Then UNION ALL it to a larger final query.
I'm now stuck as to how I can run the query to count the number of active users:
SELECT
COUNT(Client)
FROM clients
WHERE [Start Date] >= #loopdate AND ([End Date] <= #loopdate OR [End Date] IS NULL)
Thank you!
You don't need anything recursive in this particular case, you need as a minimum a list of dates in the range you want to report on, ideally a permanent calendar table.
for purposes of demonstration you can create something on the fly, and use it like so, with the list of dates something you outer join to:
with dates as (
select top(9)
Convert(date,DateAdd(day, -1 + Row_Number() over(order by (select null)), '20220101')) dt
from master.dbo.spt_values
)
select d.dt [Date], c.NumActive
from dates d
outer apply (
select Count(*) NumActive
from t
where d.dt >= t.StartDate and (d.dt <= t.EndDate or t.EndDate is null)
)c
See this Demo Fiddle

How can I get the count to display zero for months that have no records

I am pulling transactions that happen on an attribute (attribute ID 4205 in table 1235) by the date that a change happened to the attribute (found in the History table) and counting up the number of changes that occurred by month. So far I have
SELECT TOP(100) PERCENT MONTH(H.transactiondate) AS Month, COUNT(*) AS Count
FROM hsi.rmObjectInstance1235 AS O LEFT OUTER JOIN
hsi.rmObjectHistory AS H ON H.objectID = O.objectID
WHERE H.attributeid = 4205) AND Year(H.transaction date) = '2020'
GROUP BY MONTH(H.transactiondate)
And I get
Month Count
---------------
1 9
2 4
3 11
4 14
5 1
I need to display a zero for months June - December instead of excluding those months.
One option uses a recursive query to generate the dates, and then brings the original query with a left join:
with all_dates as (
select cast('2020-01-01' as date) dt
union all
select dateadd(month, 1, dt) from all_dates where dt < '2020-12-01'
)
select
month(d.dt) as month,
count(h.objectid) as cnt
from all_dates d
left join hsi.rmobjecthistory as h
on h.attributeid = 4205
and h.transaction_date >= d.dt
and h.transaction_date < dateadd(month, 1, d.dt)
and exists (select 1 from hsi.rmObjectInstance1235 o where o.objectID = h.objectID)
group by month(d.dt)
I am quite unclear about the intent of the table hsi.rmObjectInstance1235 in the query, as none of its column are used in the select and group by clauses; it it is meant to filter hsi.rmobjecthistory by objectID, then you can rewrite this as an exists condition, as shown in the above solution. Possibly, you might as well be able to just remove that part of the query.
Also, note that
top without order by does not really make sense
top (100) percent is a no op
As a consequence, I removed that row-limiting clause.

SQL: "Cannot construct data type date" when comparing two dates

I'm having an issue in a query where SQL Server is throwing the error
Cannot construct data type date, some of the arguments have values which are not valid
when comparing two date objects that themselves are valid.
If I remove the where clause, it resolves without error, but the moment I try to compare them with any relational or equality operator it errors again.
Minimum query to reproduce the issue is as follows:
with Years as
(
select
YEAR(getdate()) + 1 Year,
DATEFROMPARTS(YEAR(getdate()) + 1, 1, 1) FirstOfTheYear,
0 YearOffset
union all
select
Year - 1,
DATEFROMPARTS(Year - 1, 1, 1),
YearOffset + 1
from Years
where YearOffset < 5
),
Months as
(
select 1 Month
union all
select Month + 1
from Months
where Month < 12
),
Days as
(
select 1 Day
union all
select Day + 1
from Days
where Day < 31
),
Dates as
(
select cast(DATEFROMPARTS(Year, Month, Day) as date) Date
from Years
cross join Months
cross join Days
where DAY(EOMONTH(FirstOfTheYear, Month - 1)) >= Day
)
select Dates.Date, cast ('2019-10-01' as date), CAST ('2019-10-11' as date)
from Dates
where Date = cast ('2019-10-01' as date) -- Comment this line out and the error goes away, occurs with any date construction pattern
--where Dates.[Date] >= datefromparts(2019, 10, 01) and Dates.[Date] <= DATEFROMPARTS(2019, 10, 11)
order by date
Commenting out the where clause returns results as expected, confirming that it is specifically the comparison that is triggering this issue.
Additionally, manually creating a handful of dates (first of the year, 2015-2019, the October dates in the query) and querying against that does not cause the error to show.
Edit: I want to emphasize that the code is already handling February and leap years correctly. The output of the Dates CTE is valid and outputs the full range without error. It is only when I reference the date in the where clause that it throws the error
Edit2: I was able to resolve my issue by switching to a different date generation pattern (adding a day, day by day, in a recursive), but I still am curious what causes this error.
The point of a couple of the other answers is that attacking the issue in the manner you are is not necessarily the most efficient way of generating a date's table. Most of the time when constrained with SQL server people will lead someone to use a Tally table for this purpose. Doing so will remain a SET based operation rather than requiring looping or recursion. Which means the recursion limit you mentioned in one of your comments simply doesn't apply.
A Tally table is a set of numeric values that you can then use to generate or produce the values you want. In this case that is approximately 1827 days (5 years + 1 day) but can differ by leap years. The leap years and February are likely the issues within your code. Anyway to generate a tally table you can start with 10 values then cross join till you get to an acceptable number of combinations. 3 cross joins will bring you to 10,000 values and ROW_NUMBER() - 1 can be used to generate a 0 based increment. After which you can use DATEADD() to actually create the dates:
;WITH cteTen AS (
SELECT n FROM (VALUES (0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) T(n)
)
, cteTally AS (
SELECT
N = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1
FROM
cteTen t10
CROSS JOIN cteTen t100
CROSS JOIN cteTen t1000
CROSS JOIN cteTen t10000
)
, cteStartOfNextYear AS (
SELECT
StartOfNextYear = s.[Date]
,NumOfDaysBetween = DATEDIFF(DAY,DATEADD(YEAR,-5,s.[Date]),s.[Date])
FROM
(VALUES (DATEFROMPARTS(YEAR(GETDATE()) + 1, 1, 1))) s([Date])
)
, cteDates AS (
SELECT
[Date] = DATEADD(DAY,- t.N, s.StartOfNextYear)
FROM
cteStartOfNextYear s
INNER JOIN cteTally t
ON t.N <= NumOfDaysBetween
)
SELECT *
FROM
cteDates
ORDER BY
[Date]
Per our conversation, I see why you would think that EOMONTH() would take care of the issue but it is an order of operations sort of. So the DATEFROMPARTS() portion is analyzed across the entirety of the dataset prior to interpreting the where clause. So it is trying to build the date of 29,30 of Feb. etc. before it is limiting it to the number of days defined by EOMONTH() where clause
I have no idea why you are using code like that to generate days. Why not start at the first date and just add one date at a time?
In any case Feb 29 or 30 or 31 is going to cause an error. You can fix this approach by changing the dates subquery:
Dates as (
select try_convert(date, concat(year, '-' month, '-', day)) as Date
from Years y cross join
Months m cross join
Days
where try_convert(date, concat(year, '-' month, '-', day)) and
DAY(EOMONTH(FirstOfTheYear, Month - 1)) >= Day
)
You're asking DATEFROMPARTS to convert invalid combinations of dates and times. That's what is throwing the error - not your CAST statement.
See Using T-SQL DATEFROMPARTS to return NULL instead of throw error to find your problem dates in general.
Your query creates dates including February 29th, 30th and 31st, as well as the 31st of April, June, September and November.
If you just want all the dates from 2015 through 2020, you can count off a bunch of days and add to a base date. SQL Server will handle the month issues for you:
-- Create up to 16 million integers
WITH N AS (SELECT 0 AS N FROM (VALUES (0),(1),(2),(3),(4),(5),(6),(7)) T(n))
, M AS (SELECT 0 AS N FROM N A, N B, N C, N D, N E, N F, N G, N H)
, Z AS (SELECT ROW_NUMBER() OVER (ORDER BY A.N) AS N FROM M A)
-- Filter only the integers you need; add to a start date
SELECT CAST(DATEADD(DAY, N-1, '2015-01-01') AS DATE) FROM Z
WHERE N < DATEDIFF(DAY, '2015-01-01', '2020-01-01')

Finding the first and last business day for every month sql

I am trying to find the first and last business day for every month since 1986.
Using this, I can find the first day of any given month using, but just that month and it does not take into consideration whether it is a business day or not. To make it easier for now, business day is simply weekdays and does not consider public holiday.
SELECT DATEADD(s,0,DATEADD(mm, DATEDIFF(m,0,getdate()),0))
But I am not able to get the correct business day, so I created a calendar table consisting of all the weekdays and thought that I can extract the min(date) from each month, but I am currently stuck.
Date
---------------
1986-01-01
1986-01-02
1986-01-03
1986-01-06
...and so on
I have tried to get the first day of every month instead, but it does not take into account whether the day is a weekend or not. It just simply give the first day of each month
declare #DatFirst date = '20000101', #DatLast date = getdate();
declare #DatFirstOfFirstMonth date = dateadd(day,1-day(#DatFirst),#DatFirst);
select DatFirstOfMonth = dateadd(month,n,#DatFirstOfFirstMonth)
from (select top (datediff(month,#DatFirstOfFirstMonth,#DatLast)+1)
n=row_number() over (order by (select 1))-1
from (values (1),(1),(1),(1),(1),(1),(1),(1)) a (n)
cross join (values (1),(1),(1),(1),(1),(1),(1),(1)) b (n)
cross join (values (1),(1),(1),(1),(1),(1),(1),(1)) c (n)
cross join (values (1),(1),(1),(1),(1),(1),(1),(1)) d (n)
) x
I am wondering if anyone can perhaps shed some light as to how can I best approach this issue.
If you already have your calendar table with all available dates, then you just need to filter by weekday.
SET DATEFIRST 1 -- 1: Monday, 7: Sunday
SELECT
Year = YEAR(T.Date),
Month = MONTH(T.Date),
FirstBusinessDay = MIN(T.Date),
LastBusinessDay = MAX(T.Date)
FROM
Calendar AS T
WHERE
DATEPART(WEEKDAY, T.Date) BETWEEN 1 AND 5 -- 1: Monday, 5: Friday
GROUP BY
YEAR(T.Date),
MONTH(T.Date)
You should use the query to mark these days on your calendar table, so it's easy to access them afterwards.
This is how you can mix it up with the generation of the calendar table (with recursion).
SET DATEFIRST 1 -- 1: Monday, 7: Sunday
declare
#DatFirst date = '20000101',
#DatLast date = getdate();
;WITH AllDays AS
(
SELECT
Date = #DatFirst
UNION ALL
SELECT
Date = DATEADD(DAY, 1, D.Date)
FROM
AllDays AS D
WHERE
D.Date < #DatLast
),
BusinessLimitsByMonth AS
(
SELECT
Year = YEAR(T.Date),
Month = MONTH(T.Date),
FirstBusinessDay = MIN(T.Date),
LastBusinessDay = MAX(T.Date)
FROM
AllDays AS T
WHERE
DATEPART(WEEKDAY, T.Date) BETWEEN 1 AND 5 -- 1: Monday, 5: Friday
GROUP BY
YEAR(T.Date),
MONTH(T.Date)
)
SELECT
*
FROM
BusinessLimitsByMonth AS B
ORDER BY
B.Year,
B.Month
OPTION
(MAXRECURSION 0) -- 0: Unlimited
If you got already a table with all the weekdays only:
select min(datecol), max(datecol)
from BusinessOnlyCalendar
group by year(datecol), month(datecol)
But you should expand your calendar to include all those calculations you might do on date, like FirstDayOfWeek/Month/Quarter/Year, WeekNumber, etc.
When you got a column in your calendar indicating business day yes/no, it's a simple:
select min(datecol), max(datecol)
from calendar
where businessday = 'y'
group by year(datecol), month(datecol)

SQL Query to list records for last 7 days

I need query to list all the records for the date, incase if records are not present then query should list 0
SELECT Count(C.ConversionStatusID) Visits, CONVERT(VARCHAR(10),ActionDate,110) ActionDate
FROM Conversion C
WHERE C.ConversionStatusID = 2 AND
ActionDate Between DateAdd(day,-7,GetDate()) AND GETDATE()
GROUP BY CONVERT(VARCHAR(10),ActionDate,110)
Order BY CONVERT(VARCHAR(10),ActionDate,110) DESC
Expected output should always result 7 records, Sample result should be as below
ActionDate Visits
01-09-2015 1
01-08-2015 5
01-07-2015 0
01-06-2015 0
01-05-2015 3
01-04-2015 8
01-03-2015 0
Thanks in advance
you need to have date table for the last 7 days, you can then do left join
with cte (value, n)
as
(
select DATEADD(DAY, DATEDIFF(day,0,getdate()),0) as value,1 as n
UNION ALL
SELECT DATEADD(day, -1, value) as value, n+1
from cte
where n < 7
)
select CONVERT(VARCHAR(10),cte.value,110) as ActionDate , Count(C.ConversionStatusID) Visits
from cte
left join Conversion C
ON CONVERT(VARCHAR(10),C.ActionDate,110) = CONVERT(VARCHAR(10),cte.value,110)
and C.ConversionStatusID = 2
GROUP BY CONVERT(VARCHAR(10),cte.value,110)
Order BY CONVERT(VARCHAR(10),cte.value,110) DESC
Something like this will work, but you're going to have to play around with what should be a date and what should be a datetime. It's not at all clear where or when you want a time component. In particular, your WHERE clause seems to contradict the rest of your query since it doesn't strip the time. For example, if GETDATE() is January 12 at 2 PM, is January 11 at 1 PM one day ago or two? What about January 5 at 1 PM, then, because the WHERE clause is stripping that off.
If a date is 00:00 to 23:59 (although the WHERE clause is still off):
;WITH Dates ([Date]) AS (
SELECT CAST(DATEADD(DAY,-1,GETDATE()) AS DATE) UNION
SELECT CAST(DATEADD(DAY,-2,GETDATE()) AS DATE) UNION
SELECT CAST(DATEADD(DAY,-3,GETDATE()) AS DATE) UNION
SELECT CAST(DATEADD(DAY,-4,GETDATE()) AS DATE) UNION
SELECT CAST(DATEADD(DAY,-5,GETDATE()) AS DATE) UNION
SELECT CAST(DATEADD(DAY,-6,GETDATE()) AS DATE) UNION
SELECT CAST(DATEADD(DAY,-7,GETDATE()) AS DATE))
SELECT D.Date,
COALESCE(Count(C.ConversionStatusID),0) Visits
FROM Dates D
LEFT JOIN Conversion C
ON D.[Date] = CAST(C.ActionDate AS Date)
WHERE C.ConversionStatusID = 2 AND
ActionDate Between DateAdd(day,-7,GetDate()) AND GETDATE()
GROUP BY CONVERT(VARCHAR(10),ActionDate,110)
Order BY CONVERT(VARCHAR(10),ActionDate,110) DESC
You could get away with not using a CTE if you had a Numbers or tally table, but for something this small it really won't matter that much.