Find all instances of a specific day of the week between two dates in SQL - sql

I'm trying to find all instances of a day of a week between two given dates. The day of the week can change. I've seen a similar question posted on here, but it doesn't seem to work for variables
SET DATEFIRST 1;
DECLARE #earliestStartDate DATETIME = '2016-08-01 00:00:00.000';
DECLARE #latestStartDate DATETIME = '2016-09-30 00:00:00.000';
DECLARE #weeklyCoursesStartDay INT = 1;
DECLARE #maxCourses INT = 30;
CREATE TABLE #Dates(CourseDate DATETIME);
WITH CTE(dt)
AS
(SELECT #earliestStartDate
UNION ALL
SELECT DATEADD(day, #weeklyCoursesStartDay, dt) FROM CTE--SELECT DATEADD(day, #weeklyCoursesStartDay, dt) FROM CTE
WHERE dt < #latestStartDate)
INSERT INTO #Dates(CourseDate)
SELECT TOP(#maxCourses) dt
FROM CTE
WHERE DATEPART(DW, dt) = #weeklyCoursesStartDay
OPTION (MAXRECURSION 0);
SELECT * FROM #Dates
DROP TABLE #Dates
This returns the below, as expected.
2016-08-01 00:00:00.000
2016-08-08 00:00:00.000
2016-08-15 00:00:00.000
2016-08-22 00:00:00.000
2016-08-29 00:00:00.000
2016-09-05 00:00:00.000
2016-09-12 00:00:00.000
2016-09-19 00:00:00.000
2016-09-26 00:00:00.000
But when the #weeklyCourseStartDay is changed to say, 5 (indicating a friday) it only returns two results:
2016-08-26 00:00:00.000
2016-09-30 00:00:00.000

The line
SELECT DATEADD(day, #weeklyCoursesStartDay, dt) FROM CTE
should actually be:
SELECT DATEADD(day, 1, dt) FROM CTE
This means that the recursive CTE will generate every day between #earliestStartDate and #latestStartDate, and, at a later stage, the WHERE DATEPART(DW, dt) = #weeklyCoursesStartDay clause will make sure only the days with the correct week day will remain.

Related

Query based on day-of-week/time range

I'm on SQL Server 2014. I have a table with columns like this:
id (int, PK, identity)
effectiveDate (datetime)
expirationDate (datetime)
1
2022-07-06 18:00:00.000
2022-07-06 23:00:00.000
2
2022-07-08 22:00:00.000
2022-07-09 02:00:00.000
I need to select rows where the current date/time (GETDATE()) lands within the day-of-week/time range represented by these datetimes, beginning on the effectiveDate. So think of row 1 as the range Wednesday 18:00 -> Wednesday 23:00 and row 2 as Friday 22:00 -> Saturday 02:00. (Keep in mind the day-of-week/time range can span multiple days, as in the 2nd row.)
Examples:
GETDATE() = 2022-07-06 19:30:00.000 (i.e Wednesday at 19:30)
Selects row 1
GETDATE() = 2022-07-30 01:00:00.000 (i.e. Saturday at 01:00)
Selects row 2
GETDATE() = 2022-06-30 19:00:00.000 (i.e. Wednesday at 19:00 which matches row 1 on the day-of-week/time range, but is before the effective date)
Selects nothing
I'm not quite sure how to attack this. Maybe we could date adjust each row's effectiveDate and expirationDate as many weeks forward as needed to place the effectiveDate before GETDATE() (assuming the effectiveDate <= GETDATE()). Any thoughts?
The DATEPART with weekday is the key to checking the weekday range. There are two cases. For example, Mon to Wed is different than Wed to Mon. Mon to Wed is easy with a test of Mon<=day<= Wed. For Wed to Mon, it becomes day < Mon or day > Wed. However, this is the same as NOT Mon<=day<=Wed. The query might not be exactly what you need, but should be a good start.
with TestData as (
select *
from (
values
(1, CAST('2022-07-06 18:00:00.000' as datetime), CAST('2022-07-06 23:00:00.000' as datetime)),
(2, CAST('2022-07-08 22:00:00.000' as datetime), CAST('2022-07-09 02:00:00.000' as datetime)),
(3, CAST('2022-07-09 22:00:00.000' as datetime), CAST('2022-07-11 10:00:00.000' as datetime))
) t (id, effectiveDate, expirationDate)
), TestSamples as (
select *
from (
values
(CAST('2022-07-06 19:30:00.000' as datetime)),
(CAST('2022-07-30 01:00:00.000' as datetime)),
(CAST('2022-07-31 01:00:00.000' as datetime)),
(CAST('2022-06-30 19:00:00.000' as datetime)),
(CAST('2022-08-04 19:00:00.000' as datetime))
) t (testDate)
), WeekDayRange as (
select id, effectiveDate, expirationDate,
datename(weekday, effectiveDate) as WDNAME1, datename(weekday, expirationDate) as WDNAME2,
datepart(weekday, effectiveDate) as WD1, datepart(weekday, expirationDate) as WD2
from TestData
) select t.testDate, datename(weekday, t.testDate) as [WDNAME], datepart(weekday, t.testDate) as [WD], r.*
from TestSamples t
left join WeekDayRange r
on (r.WD1 <= r.WD2 and datepart(weekday, t.testDate) BETWEEN r.WD1 and r.WD2)
or (r.WD1 > r.WD2 and not datepart(weekday, t.testDate) BETWEEN r.WD2 and r.WD1)
where t.testDate > r.effectiveDate
testDate WDNAME WD id effectiveDate expirationDate WDNAME1 WDNAME2 WD1 WD2
----------------------- --------- ----------- ----------- ----------------------- ----------------------- --------- --------- ----------- -----------
2022-07-06 19:30:00.000 Wednesday 4 1 2022-07-06 18:00:00.000 2022-07-06 23:00:00.000 Wednesday Wednesday 4 4
2022-07-30 01:00:00.000 Saturday 7 2 2022-07-08 22:00:00.000 2022-07-09 02:00:00.000 Friday Saturday 6 7
2022-07-31 01:00:00.000 Sunday 1 3 2022-07-09 22:00:00.000 2022-07-11 10:00:00.000 Saturday Monday 7 2
I think I got this working with a bit of math:
DECLARE #myGetDate DATETIME;
SET #myGetDate = '2022-07-30 01:00:00.000';
WITH AdjustedWeeklyDates AS (
SELECT
ID,
DATEADD(WEEK, DATEDIFF(day, effectiveDate, #myGetDate) / 7, effectiveDate) AS adjustedEffectiveDate,
DATEADD(WEEK, DATEDIFF(day, effectiveDate, #myGetDate) / 7, expirationDate) AS adjustedExpirationDate
FROM
dbo.myTable
WHERE
effectiveDate <= #myGetDate
)
SELECT
mt.ID, mt.effectiveDate, mt.expirationDate, awd.adjustedEffectiveDate, awd.adjustedExpirationDate
FROM dbo.myTable mt
INNER JOIN AdjustedWeeklyDates awd ON mt.ID = awd.ID
WHERE
awd.adjustedEffectiveDate <= #myGetDate
AND awd.adjustedExpirationDate > #myGetDate
To explain:
DATEDIFF(day, effectiveDate, #myGetDate) returns the number of days between the effectiveDate and the current date. So for example, say it was 20 days ago.
/ 7 gets the number of weeks as an int, since the DATEDIFF returns an int. This also results in the floor of the quotient. So, with our example 20 days / 7, the quotient is about 2.86, but this will result in an even 2
DATEADD adds the number of weeks to bring us up to or before the current date/time. We add the same number of weeks to the expiration date, resulting in the same range as the original effective/expiration dates, which may or may not extend around the current date.
Finally the check for effectiveDate <= #myGetDate guarantees the current date is after or equal to the effective date.

Select Between two dates in sql

We are facing problem when we'r trying to select records from a table between two dates.
Table structure/datatypes:
Slno (int, notnull, IsIdentity(yes))
Name (string(50))
StartDate (datetime)
EndDate (datetime)
Sample Data:
Slno Name StartDate EndDate
1 ABC 2017-02-17 00:00:00.000 2017-02-25 00:00:00.000
2 ABD 2017-02-15 00:00:00.000 2017-02-25 00:00:00.000
3 ABD 2017-02-17 00:00:00.000 2017-02-17 00:00:00.000
4 ABD 2017-02-14 00:00:00.000 2017-02-18 00:00:00.000
5 ABD 2017-02-17 00:00:00.000 2017-02-20 00:00:00.000
We tried running the below sql query:
select * from dbo.XYZ where (getdate() between StartDate and EndDate)
The output is:
Slno Name StartDate EndDate
1 ABC 2017-02-17 2017-02-25
2 ABD 2017-02-17 2017-02-20
We are not getting other three rows. What am I doing wrong?
SELECT *
FROM dbo.Table
WHERE EndDate >= GETDATE()
  AND StartDate < DATEADD(DAY, 1, GETDATE());
This will use your defined indexes as much as possible.
See bad habits to kick mishandling date range queries (Aaron Bertrand) for more on this.
Try this one, but as Todd commented why time is not included in output if data type is datetime?
SELECT *
FROM dbo.XYZ
WHERE StartDate <= GETDATE()
AND EndDate >= GETDATE()
EDIT:
Looks like you are storing date, not datetime, so try below:
SELECT *
FROM dbo.XYZ
WHERE StartDate <= CAST(GETDATE() AS DATE)
AND EndDate >= CAST(GETDATE() AS DATE)
EDIT:
Not really sure how else I can help, are you on SQL Server, Oracle etc? Below script will show you how date and datetime data types look like (SQL Server), and this is what confusing me the most as you are claiming your data type is datetime but output looks like date.
SELECT
cast(GETDATE() as datetime),
cast(GETDATE() as date)
Getdate() means currentdate and time. So the above query return 2 rows.
You can refer this query:
declare #minDate datetime
declare #maxDate datetime
select #minDate = MIN(StartDate) from dbo.XYZ
select #maxDate = MAX(EndDate) from dbo.XYZ
select * from dbo.XYZ where #minDate<= GetDate() OR #maxDate >= GetDate().
It will return all rows.

SQL - creating a list of custom dates between two dates

I am having trouble compiling a query than can do the following:
I have a table which has a startDate and endDate [tblPayments]
I have a column which stores a specific paymentDay [tblPayments]
Data
paymentID startDate endDate paymentDay
1 2016-01-01 2016-12-31 25
2 2015-06-01 2016-06-30 16
I am trying to generate a SELECT query which will split this specific table into separate lines based on the amount of months between these two dates, and set the paymentDay as the day for these queries
Example Output
paymentID expectedDate
1 2016-01-25
1 2016-02-25
1 2016-03-25
1 2016-04-25
1 2016-05-25
1 2016-06-25
1 2016-07-25
1 2016-08-25
1 2016-09-25
1 2016-10-25
1 2016-11-25
1 2016-12-25
2 2015-06-16
2 2015-07-16
2 2015-08-16
2 2015-09-16
2 2015-10-16
2 2015-11-16
2 2015-12-16
2 2016-01-16
2 2016-02-16
2 2016-03-16
2 2016-04-16
2 2016-05-16
I have found a query which will select the months between these dates but its adapting it to my table above, and multiple startDates and endDates I am struggling with
spliting the months
declare #start DATE = '2015-01-01'
declare #end DATE = '2015-12-31'
;with months (date)
AS
(
SELECT #start
UNION ALL
SELECT DATEADD(MM,1,date)
from months
where DATEADD(MM,1,date)<=#end
)
select Datename(MM,date) from months
This query is limited to just one startDate and endDate, so I haven't expanded it to change the DAY of the date.
Use a date table and a simple inner join
DECLARE #tblPayments table (paymentID int identity(1,1), startDate date, endDate date, paymentDay int)
INSERT #tblPayments VALUES
('2016-01-01', '2016-12-31', 25),
('2015-06-01', '2016-06-30', 16)
;WITH dates AS -- Build date within the range of startDate and endDate
(
SELECT MIN(startDate) AS Value, MAX(endDate) AS MaxDate FROM #tblPayments
UNION ALL
SELECT DATEADD(DAY, 1, Value), MaxDate
FROM dates WHERE DATEADD(DAY, 1, Value) <= MaxDate
)
SELECT pay.paymentID, dates.Value AS expectedDate
FROM
#tblPayments pay
INNER JOIN dates ON
dates.Value BETWEEN pay.startDate AND pay.endDate
AND DAY(dates.Value) = paymentDay
OPTION (maxrecursion 0)
I would create an in memory calendar table and then perform a simple query by joining to that:
-- Create a table with all the dates between the min and max dates in the
-- data table
DECLARE #Calendar TABLE
(
[CalendarDate] DATETIME
)
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SELECT #StartDate = MIN(startdate), #EndDate = MAX(enddate) FROM YourDataTable
WHILE #StartDate <= #EndDate
BEGIN
INSERT INTO #Calendar (CalendarDate)
SELECT #StartDate
SET #StartDate = DATEADD(dd, 1, #StartDate)
END
-- Join to return only dates between the start and end date that match the Payment Day
SELECT D.PaymentId, C.CalendarDate FROM YourDataTable D
INNER JOIN #Calendar C ON C.CalendarDate BETWEEN D.StartDate AND D.EndDate
AND DATEPART(day, C.CalendarDate) = D.PaymentDay

Convert Date Range to Individual Days

A table called VolumeRequest stores the volume requests by accounts for a date range.
AccountId StartDate EndDate DailyVolume
670 2013-07-01 00:00:00.000 2013-07-31 00:00:00.000 10
670 2013-07-01 00:00:00.000 2013-07-31 00:00:00.000 1050
670 2013-07-10 00:00:00.000 2013-07-10 00:00:00.000 -350
670 2013-07-24 00:00:00.000 2013-07-26 00:00:00.000 -350
673 2013-06-01 00:00:00.000 2013-07-31 00:00:00.000 233
I need to display the requests on daily basis where volume is summed by day by account for a given date range like for month of July the report is like below. The date start and end dates of the volume requests need to be trimmed for the given report dates
AccountId Date Volume
670 2013-07-01 00:00:00.000 1060
670 2013-07-02 00:00:00.000 1060
.
.
670 2013-07-10 00:00:00.000 710
.
.
670 2013-07-24 00:00:00.000 710
670 2013-07-25 00:00:00.000 710
670 2013-07-26 00:00:00.000 710
.
.
670 2013-07-31 00:00:00.000 1060
673 2013-07-01 00:00:00.000 233
.
.
673 2013-07-31 00:00:00.000 233
Right now I am using table Variables and loops to achieve it which I know is not a good way to code.
DECLARE #sDate DATETIME, #eDate DATETIME , #volume DECIMAL (10, 4), rstartdate DATETIME, #renddate DATETIME , #loopcount INT
SET #sdate = '4/1/2013'
SET #edate = '4/30/2013'
DECLARE #VolumeRequest TABLE
(
ID INT IDENTITY (1, 1) PRIMARY KEY,
Aid INT,
Startdate DATETIME,
Enddate DATETIME,
volume DECIMAL (14, 4)
)
DECLARE #DailyRequest TABLE
(
ID INT IDENTITY (1, 1) PRIMARY KEY,
Accountid INT,
ReadDate DATETIME,
Volume DECIMAL (14, 4)
)
INSERT INTO #VolumeRequest
SELECT Accountid,
( CASE
WHEN #sdate > startdate THEN #sdate
ELSE startdate
END ),
( CASE
WHEN #edate < enddate THEN #edate
ELSE enddate
END ),
dailyvolume
FROM VolumeRequest
WHERE Startdate <= #edate
AND Enddate >= #sdate
AND isnull (deprecated, 0) != 1
--loop to breakdown the volume requests into daily requests
SET #loopcount = 1
WHILE #loopcount <= (SELECT MAX(ID)
FROM #VolumeRequest)
BEGIN
SELECT #volume = volume,
#rstartdate = Startdate,
#renddate = Enddate
FROM #VolumeRequest
WHERE ID = #loopcount
WHILE #rstartdate <= #renddate
BEGIN
INSERT INTO #DailyRequest
SELECT #currentaid,
#rstartdate,
#volume
SET #rstartdate = DATEADD(day, 1, #rstartdate)
END
SET #LoopCount = #LoopCount + 1
END
I am looking for ways which don't involve loops or cursors. I found a Similar Question. The answers there didn't help me.
I like to use a Dates table such as
CREATE TABLE #Dates(
DateId INT,
CalendarDate DATETIME)
filled with dates for whatever range you need. I use this table to join to tables such as VolumeRequest to retrieve the output you requested.
SELECT
v.AccountId,
d.CalendarDate,
SUM(v.DailyVolume)
FROM
#Dates d INNER JOIN
VolumeRequest v ON
d.CalendarDate >= v.StartDate AND
d.CalendarDate <= v.EndDate
group by
d.CalendarDate,
v.AccountId
to fill the #Dates table, I use something like this:
declare #startdate datetime = '6/1/13', #enddate datetime = '7/31/13'
create table #Dates(CalendarDate datetime)
insert into #Dates(CalendarDate)
select
dateadd(dd, rid-1, #startdate) as calendardate
from (
select
ROW_NUMBER() over(order by o.object_id) as rid
From
sys.objects o cross apply
sys.objects o2
) dates
where
dateadd(dd, rid-1, #startdate) >= #startdate and dateadd(dd, rid-1, #startdate) <= #enddate
Modify to meet your date range needs.
SQLFiddle demo
Using WITH clause and recursion we generate Days table with all days between MIN and MAX dates.
Then generate table Accounts with distinct AccountID.
Finally JOIN all these tables and group all with SUM.
WITH MINMAX as
( SELECT MIN(StartDate) as MinDate,
MAX(EndDate) as MaxDate
from T
),
DAYS as
( SELECT MinDate as D from MINMAX
UNION ALL
SELECT D+1 as D FROM DAYS WHERE D+1<=
(
SELECT MaxDate FROM MINMAX
)
),
Accounts as
(
select distinct AccountID from T
)
select A.AccountId,Days.D,sum(T.DailyVolume) from Days
CROSS JOIN Accounts A
JOIN T on A.AccountID=T.AccountID
AND
Days.D between T.StartDate and T.EndDate
GROUP BY A.AccountId,Days.D
ORDER BY A.AccountId,Days.D
OPTION (MAXRECURSION 10000)

How to count between no of days between two dates month wise in SQL Server?

I have Two Date in SQL sever which overlap in two month i want to find how many days over lap in each month.
For example:
Start date is : 26-Sep-2012
End Date is : 10-Oct-2012
Sept- 5 days
October - 10 days
along with the list of date of each month.
declare #start datetime;
declare #end datetime;
set #start = '20120926';
set #end = '20121010';
SELECT (DAY(DATEADD (m, 1, DATEADD(d, 1 - DAY(#start), #start))-1)
- DAY(#start) + 1) AS DaysLeftStart,
DAY(#end) AS DaysEnd
Fiddle: http://sqlfiddle.com/#!3/d41d8/4441/0
DECLARE #start DATETIME, #end DATETIME;
SELECT #start = '20120926', #end = '20121010';
;WITH c(d) AS
(
SELECT TOP (DATEDIFF(DAY, #start, #end)+1)
DATEADD(DAY, ROW_NUMBER() OVER (ORDER BY name)-1, #start)
FROM sys.all_columns
)
SELECT
[date] = DATEADD(MONTH, DATEDIFF(MONTH, 0, d), 0),
[days] = COUNT(*)
FROM c GROUP BY DATEDIFF(MONTH, 0, d)
UNION ALL SELECT d, NULL FROM c;
Results:
date days
----------------------- -----
2012-09-01 00:00:00.000 5
2012-10-01 00:00:00.000 10
2012-09-26 00:00:00.000 NULL
2012-09-27 00:00:00.000 NULL
2012-09-28 00:00:00.000 NULL
2012-09-29 00:00:00.000 NULL
2012-09-30 00:00:00.000 NULL
2012-10-01 00:00:00.000 NULL
2012-10-02 00:00:00.000 NULL
2012-10-03 00:00:00.000 NULL
2012-10-04 00:00:00.000 NULL
2012-10-05 00:00:00.000 NULL
2012-10-06 00:00:00.000 NULL
2012-10-07 00:00:00.000 NULL
2012-10-08 00:00:00.000 NULL
2012-10-09 00:00:00.000 NULL
2012-10-10 00:00:00.000 NULL
Here you can find some detail about creating a calendar table. You can use it to perform such query:
SELECT CalendarMonth, count(*) day_num
FROM dbo.Calendar
WHERE CalendarDate between #start_date and #end_date;