Get an interval of dates from a range of dates - sql

I have two dates 21/10/2019 and 26/6/2031, and from these two I need a list of dates with three months interval starting from the first date like this:
22/10/2019 | 21/01/2020
22/01/2020 | 21/04/2020
22/04/2020 | 21/07/2020
22/07/2020 | 21/10/2020
...
22/01/2031 | 21/04/2031
22/04/2031 | 26/06/2031
I've tried using ROW_NUMBER() and DENSE_RANK() and LAG() to group a complete list of dates between the two dates, but I can't seem to figure it out. I think I might need to partition this somehow, but I can't get it right.
If you don't understand, please let me know. I'm pretty new at this :)

You can use a recursive query:
with cte (dt, end_dt) as (
select #start_dt, #end_dt
union all
select dateadd(month, 3, dt), end_dt from cte where dt < end_dt
)
select dt,
case when dateadd(month, 3, dt) < end_dt
then dateadd(day, -1, dateadd(month, 3, dt))
else end_dt
end as end_dt
from cte
order by dt;
If you need to generate more than 100 quarters, you need to add option (maxrecursion 0) at the very end of the query.
Demo on DB Fiddle

This could also be done using a 'tally' or numbers based approach. The upper limit of the tally_cte is 12^5=248,832 (more than recursion can produce and it could be increased as much as needed).
declare
#start_dt datetime='2019-10-21',
#end_dt datetime='2031-06-26'
;with
n(n) as (select * from (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)) v(n)),
tally_cte(n) as (
select row_number() over (order by (select null))
from n n1 cross join n n2 cross join n n3 cross join n n4 cross join n n5)
select t.*, cast(dt.dt as date) start_dt, cast(dateadd(MONTH, 3, dt.dt) as date) end_dt
from tally_cte t
cross apply (select dateadd(month, (n-1)*3, #start_dt) dt) dt
where n<datediff(month, #start_dt, #end_dt)/3;
N start_dt end_dt
1 2019-10-21 2020-01-21
2 2020-01-21 2020-04-21
3 2020-04-21 2020-07-21
...
45 2030-10-21 2031-01-21

Related

Keep last n business days records from today date in SQL Server

How can we keep last n business days records from today date in this table:
Suppose n = 7
Sample Data:
Table1:
Date
----------
2021-11-29
2021-11-30
2021-12-01
2021-12-02
2021-12-03
2021-12-04
2021-12-05
2021-12-06
2021-12-07
2021-12-08
2021-12-09
2021-12-10
2021-12-11
2021-12-12
2021-12-13
Based on this table data we want output like below. It should delete all the rows before the 03-Dec or data for last 7 business days.
Date
-------
2021-12-03
2021-12-06
2021-12-07
2021-12-08
2021-12-09
2021-12-10
2021-12-13
Note: It's fine if we keep data for Saturday, Sunday in between business days.
I tried this query
DECLARE #n INT = 7
SELECT * FROM Table1
WHERE [date] < Dateadd(day, -((#n + (#n / 5) * 2)), Getdate())
but Saturday, Sunday logic doesn't fit here with my logic. Please suggest better approach.
You can get the 7th working day from today as
select top(1) cast(dateadd(d, -n + 1, getdate()) as date) d
from (
select n
, sum (case when datename(dw, dateadd(d, -n + 1, getdate())) not in ('Sunday', 'Saturday') then 1 end) over(order by n) wdn
from (
values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11)
)t0(n)
) t
where wdn = 7
order by n;
Generally using on-the-fly tally for a #n -th day
declare #n int = 24;
with t0(n) as (
select n
from (
values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)
) t(n)
), tally as (
select top(#n + (#n/5 +1)*2) row_number() over(order by t1.n) n
from t0 t1, t0 t2, t0 t3
)
select top(1) cast(dateadd(d, -n + 1, getdate()) as date) d
from (
select n
, sum (case when datename(dw, dateadd(d, -n + 1, getdate())) not in ('Sunday', 'Saturday') then 1 end) over(order by n) wdn
from tally
) t
where wdn = #n
order by n;
You can use CTE to mark target dates and then delete all the others from the table as follows:
; With CTE As (
Select [Date], Row_number() Over (Order by [Date] Desc) As Num
From tbl
Where DATEPART(weekday, [Date]) Not In (6,7)
)
Delete From tbl
Where [Date] Not In (Select [Date] From CTE Where Num<=7)
If the number of business days in the table may be less than 7 and you need to bring the total number of days to 7 by adding days off, try this:
Declare #n Int = 7
; With CTE As (
Select [Date], IIF(DATEPART(weekday, [Date]) In (6,7), 0, 1) As IsBusinessDay
From tbl
)
Delete From tbl
Where [Date] Not In (Select Top(#n) [Date] From CTE Order By IsBusinessDay Desc, [Date] Desc)
If there is only one date for each day, you can simply do this:
SELECT TOP 7 [Date] FROM Table1
WHERE
[Date] < GETDATE() AND DATENAME(weekday, [DATE]) NOT IN ('Saturday', 'Sunday')
ORDER BY
[DATE] DESC

In SQL how to calculate days in a year based on a start date and the number of days lapsed

What would be the SQL to calculate the number of days in each year if I had a start date and the number of days that have lapsed?
For example, the date (ymd) 2013-01-01 and the days lapsed is 1000.
I would like the result to look like this
2013 = 365
2014 = 365
2015 = 270
Can this be written as a function like datediff?
I have tried using a calendar table, but of course, linking to this just gives me 2013 = 1000
My calendar table looks like this.
DATE_ID | DATE | CALENDAR_YEAR | FINANCIAL_YEAR
-----------------------------------------------
20130101 | 2013-01-01 | 2013 | 2013/14
This is what i have tried.
SELECT
D.FISCAL_YEAR, SUM([DAYS]) AS NUMBER_OF_DAYS
FROM [dbo].[FACT] F
LEFT JOIN [dbo].[DIM_DATE] D ON D.DATE_ID = F.DATE_ID
GROUP BY
D.FISCAL_YEAR
The result for this is.
FISCAL_YEAR | NUMBER_OF_DAYS
----------------------------
2013/14 |2820
2014/15 |6635
2015/16 |2409
I would personally build a tally table to do this. Once you build that, you can easly get every date and count the number of days in each year:
DECLARE #YMD date = '20130101',
#Lapsed int = 1000;
--Build a Tally table
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) N(N)),
Tally AS(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 AS I
FROM N N1, N N2, N N3, N N4), --10,000 should be enough
--Build the dates table
Dates AS(
SELECT DATEADD(DAY, T.I, #YMD) AS CalendarDate
FROM Tally T
WHERE T.I <= #Lapsed - 1)
--And count the days
SELECT DATEPART(YEAR, CalendarDate) AS Year,
COUNT(CalendarDate) AS Days
FROM Dates D
GROUP BY DATEPART(YEAR, CalendarDate);
As a function:
CREATE FUNCTION CountDays (#YMD date, #Lapsed int)
RETURNS table
AS RETURN
--Build a Tally table
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) N(N)),
Tally AS(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 AS I
FROM N N1, N N2, N N3, N N4), --10,000 should be enough
--Build the dates table
Dates AS(
SELECT DATEADD(DAY, T.I, #YMD) AS CalendarDate
FROM Tally T
WHERE T.I <= #Lapsed - 1)
--And count the days
SELECT DATEPART(YEAR, CalendarDate) AS Year,
COUNT(CalendarDate) AS Days
FROM Dates D
GROUP BY DATEPART(YEAR, CalendarDate);
GO
SELECT *
FROM (VALUES('20130101',1000),
('20150501',755))V(YMD, Lapsed)
CROSS APPLY dbo.CountDays(V.YMD,V.Lapsed) CD;
One method is a recursive CTE:
with dates as (
select v.d, 1000 - datediff(day, v.d, dateadd(year, 1, v.d)) as days
from (values (datefromparts(2013, 1, 1))) v(d)
union all
select dateadd(year, 1, d), days - datediff(day, d, dateadd(year, 1, d))
from dates
where days > 0
)
select d,
(case when days > 0 then datediff(day, d, dateadd(year, 1, d))
else datediff(day, d, dateadd(year, 1, d)) + days
end)
from dates;
Here is a db<>fiddle.

How to get previous 7 days' data from today in SQL Server

I have a DataEntry Table called GuestAddressData(UserId INT, EDate DateTime) with users data. I need to fetch the count of users for today to previous 7 Days. My Query:
SELECT
row_number() over (order by (SELECT 1)) ID,
count(*) Total,
LEFT(Datename(weekday, Cast(EDate as date)), 3) Day
FROM
CRM0001GuestAddressData
WHERE
EDate >= dateadd(week, datediff(d, -1, getdate()-2)/7, -1)
GROUP BY
Cast(EDate as date)
ORDER BY
Cast(EDate as date)
For example if today is Friday then my expected output is:
ID | TOTAL | DAY
------------------------
1 | 78 | Sat
2 | 23 | Sun
3 | 54 | Mon
4 | 17 | Tues
5 | 56 | Wed
6 | 45 | Thus
7 | 78 | Fri - Today
but this is not correct. How to solve it?
You can "generate" a list of seven numbers and use it to build the desired dates. Then left join with your data to get the counts, including zeros:
WITH datelist(num, a, b) AS (
SELECT num, DATEADD(DAY, -num, CAST(CURRENT_TIMESTAMP AS DATE)), DATEADD(DAY, -num + 1, CAST(CURRENT_TIMESTAMP AS DATE))
FROM (VALUES (0), (1), (2), (3), (4), (5), (6)) AS v(num)
)
SELECT 7 - num AS ID, datelist.a AS Day, COUNT(IDBooking)
FROM datelist
LEFT JOIN T_Bookings ON Opened >= datelist.a AND Opened < datelist.b
GROUP BY datelist.a, datelist.num
ORDER BY datelist.a
SELECT
row_number() over (order by dDate) ID,
cnt,
LEFT(Datename(weekday, dDate), 3) Day
from
(Select cast(EDate as Date) as dDate,
count(*) as cnt
FROM (values (0),(1),(2),(3),(4),(5),(6)) t(v)
inner join
CRM0001GuestAddressData gd on datediff(d, gd.Edate, getdate()) = t.v
WHERE
EDate >= dateadd(d, -6, cast(getdate() as date)) and EDate < dateadd(d,1,cast(getdate() as date))
GROUP BY
Cast(EDate as date)) tmp;
Note: You meant to get 7 days from yesterday, right? Nevermind, corrected based on your sample.
DBFiddle demo
EDIT: Having all days:
SELECT
row_number() over (order by dDate) ID,
cnt,
LEFT(Datename(weekday, dDate), 3) Day
from
(Select dateadd(d,-v,cast(getdate() as date)) as dDate,
count(Edate) as cnt
FROM (values (0),(1),(2),(3),(4),(5),(6)) t(v)
left join
CRM0001GuestAddressData gd on Datediff(d,gd.EDate, getdate()) = t.v
GROUP BY
dateadd(d,-v,cast(getdate() as date))) tmp;
DBFiddle Demo

Adding Missing Dates From SQL Query

I am trying to add missing dates to my query so that my results look like this:
10/22/2018 15
10/21/2018 0
10/20/2018 14
Rather than this:
10/22/2018 15
10/20/2018 14
I want the past 300 days listed, even if the output value is 0.
Here is my query:
SELECT TOP (300)
CAST(createddate as DATE),
count(DISTINCT ID)
FROM table
GROUP BY CAST(createddate as DATE)
ORDER BY CAST(createddate as DATE) DESC
You can use a recursive CTE to generate the data:
WITH dates as (
SELECT MAX(CAST(createddate as date)) as dte, 1 as lev
FROM table
UNION ALL
SELECT DATEADD(day, -1, dte), lev + 1
FROM dates
WHERE lev < 300
)
SELECT COUNT(DISTINCT t.ID)
FROM dates d LEFT JOIN
table t
ON d.dte = CAST(t.createddate as DATE)
GROUP BY d.dte
ORDER BY d.dte DESC
OPTION (MAXRECURSION 0);

Finding missing dates compared to date range

I have one table (A) with date ranges and another (B) with just a set date. There are missing months in B that are within the date range of A. I need to identify the missing months.
A
Person StartDate EndDate
123 1/1/2016 5/1/2016
B
Person EffectiveDate
123 1/1/2016
123 2/1/2016
123 4/1/2016
123 5/1/2016
Expected result would be
123 3/1/2016
I'm using SQL Server 2012. Any assistance would be appreciated. Thanks!
One approach is to generate all values between the two dates. Here is an approach using a numbers table:
with n as (
select row_number() over (order by (select null)) - 1 as n
from master.spt_values
)
select a.person, dateadd(day, n.n, a.startdate) as missingdate
from a join
n
on dateadd(day, n.n, a.startdate) <= day.enddate left join
b
on b.person = a.person and b.effectivedate = dateadd(day, n.n, a.startdate)
where b.person is null;
Try this:
CREATE TABLE #A (Person INT, StartDate DATE, EndDate DATE)
INSERT INTO #A
SELECT '123','1/1/2016', '5/1/2016'
CREATE TABLE #B(Person INT, EffectiveDate DATE)
INSERT INTO #B
SELECT 123 ,'1/1/2016' UNION ALL
SELECT 123 ,'2/1/2016' UNION ALL
SELECT 123 ,'4/1/2016' UNION ALL
SELECT 123 ,'5/1/2016'
;WITH A1
AS(
SELECT PERSON , StartDate, EndDate
FROM #A
UNION ALL
SELECT PERSON ,DATEADD(MM,1,STARTDATE), EndDate
FROM A1
WHERE DATEADD(MM,1,STARTDATE) <= EndDate
)
SELECT PERSON , StartDate
FROM A1
WHERE
NOT EXISTS
(
SELECT 1 FROM #B B1
WHERE B1.Person = A1.PERSON
AND YEAR(B1.EffectiveDate) = YEAR(A1.STARTDATE) AND MONTH(B1.EffectiveDate) = MONTH(A1.STARTDATE)
)
This should work if you are interested in getting missing months
;WITH n
AS (SELECT ROW_NUMBER() OVER(ORDER BY
(
SELECT NULL
)) - 1 AS n
FROM master.dbo.spt_values)
SELECT a.person,
DATEADD(MONTH, n.n, a.startdate) AS missingdate
FROM a a
INNER JOIN n ON DATEADD(MONTH, n.n, a.startdate) <= a.enddate
LEFT JOIN b b ON MONTH(DATEADD(MONTH, n.n, a.startdate)) = MONTH(b.effectivedate) AND YEAR(DATEADD(MONTH, n.n, a.startdate)) = YEAR(b.effectivedate)
WHERE b.person IS NULL;