I need to return count on results per day between a time range that will overlap days.
So from 8 PM to 8 AM for every day that starts on M-F return a count for that time period. And do that for the entire year.
This is what I have, and I could do a simple and between if I only wanted on day but I'm not sure how to iterate through days especially when the start is on one day and the end on the next but skip the ones that Start on a Saturday or Sunday.
SELECT TOP (50)
ClientVisit.visittype,
ClientVisit.location_id,
ClientVisit.visittype_id,
Location.location_desc,
Location.location_code,
ClientVisit.timein,
ClientVisit.timeout,
ClientVisit.visit_dateday
FROM
ClientVisit
INNER JOIN
Location ON ClientVisit.location_id = Location.location_id
WHERE
(ClientVisit.visittype Like '%Open Chart%'
OR ClientVisit.visittype LIKE '%Diag%')
AND (Location.location_code = 'Access-505'
OR Location.location_code = 'Access-hosp')
AND (ClientVisit.timein BETWEEN #param1 AND #param2)
Filtering days of week and hours of the day is easy enough. Does this group by get at what you're trying to accomplish for the counts?
SELECT CAST(ClientVisit.timein AS DATE) AS DT, COUNT(*)
FROM ClientVisit INNER JOIN Location
ON ClientVisit.location_id = Location.location_id
WHERE
(ClientVisit.visittype Like '%Open Chart%' OR ClientVisit.visittype LIKE '%Diag%')
AND (Location.location_code = 'Access-505' OR Location.location_code = 'Access-hosp')
-- Use date params rather than datetime
AND CAST(ClientVisit.timein AS DATE) BETWEEN #param1 AND #param2
-- M-F assuming ##DATEFIRST is Sunday (7)
AND DATEPART(weekday, ClientVisit.timein) BETWEEN 2 AND 6
-- time of day. won't include the instant of 8:00:00am
AND ( DATEPART(hour, ClientVisit.timein) BETWEEN 8 AND 23
OR DATEPART(hour, ClientVisit.timein) BETWEEN 0 AND 7)
GROUP BY CAST(ClientVisit.timein AS DATE);
If you need to treat the hours from 8PM to 8AM as a single shift then you can adjust the times prior to so that times after midnight are treated as part of the preceeding day:
WITH AdjustedVisit AS (
SELECT *, DATEADD(hour, -8, timein) AS adjustedin FROM ClientVisit)
-- Use date params rather than datetime
WHERE CAST(timein AS DATE) BETWEEN #param1 AND #param2
)
SELECT CAST(v.adjustedin AS DATE) AS DT, COUNT(*)
FROM AdjustedVisit AS v INNER JOIN Location AS l
ON v.location_id = l.location_id
WHERE
(v.visittype Like '%Open Chart%' OR v.visittype LIKE '%Diag%')
AND (l.location_code = 'Access-505' OR l.location_code = 'Access-hosp')
-- M-F assuming ##DATEFIRST is Sunday (7)
AND DATEPART(weekday, v.adjustedin) BETWEEN 2 AND 6
-- time of day. won't include the instant of 8:00:00am
AND DATEPART(hour, v.adjustedin) BETWEEN 12 AND 23
GROUP BY CAST(v.adjustedin AS DATE);
Related
I have my getdate() = '2022-03-21 09:24:34.313'
I'd like to build Start Month and End Month dates intervals with SQL language (SQL server) , with the following screen :
You can use EOMONTH function and DATEADD function to get the data you want.
But, the best approach would be to use a calendar table and map it against the current date and get the data you want.
DECLARE #DATE DATE = getdate()
SELECT DATEADD(DAY,1,EOMONTH(#DATE,-1)) AS MonthM_Start, EOMONTH(#DATE) AS MonthM_End,
DATEADD(DAY,1,EOMONTH(#DATE,-2)) AS MonthOneBack_Start, EOMONTH(#DATE,-1) AS MonthOneBack_End,
DATEADD(DAY,1,EOMONTH(#DATE,-3)) AS MonthTwoBack_Start, EOMONTH(#DATE,-2) AS MonthTwoBack_End,
DATEADD(DAY,1,EOMONTH(#DATE,-4)) AS MonthThreeBack_Start, EOMONTH(#DATE,-3) AS MonthThreeBack_End
MonthM_Start
MonthM_End
MonthOneBack_Start
MonthOneBack_End
MonthTwoBack_Start
MonthTwoBack_End
MonthThreeBack_Start
MonthThreeBack_End
2022-03-01
2022-03-31
2022-02-01
2022-02-28
2022-01-01
2022-01-31
2021-12-01
2021-12-31
You can use a recursive CTE to avoid having to hard-code an expression for each month boundary you need, making it very easy to handle fewer or more months by just changing a parameter.
Do you really need the end date for processing? Seems more appropriate for a label, since date/time types can vary - meaning the last day of the month at midnight isn't very useful if you're trying to pull any data from after midnight on the last day of the month.
This also shows how to display the data for each month even if there isn't any data in the table for that month.
DECLARE #number_of_months int = 4,
#today date = DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1);
;WITH m(s) AS
(
SELECT #today UNION ALL SELECT DATEADD(MONTH, -1, s) FROM m
WHERE s > DATEADD(MONTH, 1-#number_of_months, #today)
)
SELECT MonthStart = m.s, MonthEnd = EOMONTH(m.s)--, other cols/aggs
FROM m
--LEFT OUTER JOIN dbo.SourceTable AS t
--ON t.datetime_column >= m
--AND t.datetime_column < DATEADD(MONTH, 1, m);
Output (without the join):
MonthStart
MonthEnd
2022-03-01
2022-03-31
2022-02-01
2022-02-28
2022-01-01
2022-01-31
2021-12-01
2021-12-31
Example db<>fiddle
But, as mentioned in a comment, you could easily store this information in a calendar table, too, and just outer join to that:
SELECT c.TheFirstOfMonth, c.TheLastOfMonth --, other cols/aggs
FROM dbo.CalendarTable AS c
LEFT OUTER JOIN dbo.SourceTable AS t
ON t.datetime_column >= c.TheFirstOfMonth
AND t.datetime_column < c.TheFirstOfNextMonth
WHERE c.FirstOfMonth >= DATEADD(MONTH, -4, GETDATE())
AND c.FirstOfMonth < GETDATE();
I am trying to select records from today and the same day of each week for the last 4 weeks.
Today (Tuesday)
Last Tuesday
The Tuesday before that
The Tuesday before that
I need this to be tied to current date because I am going to run this query every day so I don't want to use a between or something where I manually specify the date range.
Everything I have found or tried so far has pulled the last month of data but not the last 4 weeks of the same weekday.
select *
from table
where thedatecolumn >= DATEADD(mm, -1, GETDATE())
This works but pulls everything from the last month.
If today's date is 7/10/2019 I need
Data from 7/10/2019
Data from 7/3/2019
Data from 6/26/2019
Data from 6/19/2019
Every day I will run this query, so I need it to be dynamic based on the current date.
I believe you want to look back 21 days and then filter those dates that have the same day of week:
select * from table
where thedatecolumn >= DATEADD(DAY, -21, CAST(GETDATE() AS DATE))
and DATEPART(WEEKDAY, thedatecolumn) = DATEPART(WEEKDAY, GETDATE())
You Can try using a recursive cte which starts today and repeatedly substracts 7 days - so you ensure you always land on the same weekday. Following an example:
WITH cteFromToday AS(
SELECT 0 AS WeeksBack, GETDATE() AS MyDate
UNION ALL
SELECT WeeksBack + 1 AS WeeksBack, DATEADD(d, -7, MyDate) AS MyDate
FROM cteFromToday
)
SELECT TOP 5 *
FROM cteFromToday
OPTION ( MaxRecursion 0 );
This is quite simple. Substitute CURRENT_TIMESTAMP here for any given date.
SELECT CONVERT(DATE,CURRENT_TIMESTAMP) AS Today,
DATEADD(DAY,-7,CONVERT(DATE,CURRENT_TIMESTAMP)) AS LastWeek ,
DATEADD(DAY,-14,CONVERT(DATE,CURRENT_TIMESTAMP)) AS TwoWeeksAgo,
DATEADD(DAY,-21,CONVERT(DATE,CURRENT_TIMESTAMP)) AS ThreeWeeksAgo
SO, if you want to get data for a set of ranges for one entire day with those dates:
SELECT something
WHERE
datetimecolumn >= CONVERT(DATE,CURRENT_TIMESTAMP) AND datetimecolumn < DATEADD(DAY,1, CONVERT(DATE,CURRENT_TIMESTAMP)) -- Todays range,
OR datetimecolumn >= DATEADD(DAY,-7,CONVERT(DATE,CURRENT_TIMESTAMP)) AND datetimecolumn < DATEADD(DAY,1,DATEADD(DAY,-7,CONVERT(DATE,CURRENT_TIMESTAMP)))-- LastWeek ,
OR datetimecolumn >= DATEADD(DAY,-14,CONVERT(DATE,CURRENT_TIMESTAMP)) AND datetimecolumn < DATEADD(DAY,1,DATEADD(DAY,-14,CONVERT(DATE,CURRENT_TIMESTAMP)))-- TwoWeeksAgo,
OR datetimecolumn >= DATEADD(DAY,-21,CONVERT(DATE,CURRENT_TIMESTAMP)) AND datetimecolumn < DATEADD(DAY,1, DATEADD(DAY,-21,CONVERT(DATE,CURRENT_TIMESTAMP))) -- ThreeWeeksAgo
In SQL Server, how many 8 day periods do I have between this two dates?
#date1 = '2016/11/08'
#date2 = '2017/02/10'
Manually I have four 8 day periods between those dates
Declare #date1 date = '2016/11/08'
Declare #date2 date = '2017/02/10'
Select count(*)
From (
Select Top (DateDiff(DD,#date1,#date2)+1) D=cast(DateAdd(DD,Row_Number() Over (Order By (Select null))-1,#date1) as Date)
From master..spt_values n1, master..spt_values n2
) A
Where DatePart(DAY,D)=8
Returns
4
Here's an alternate approach using the standard SQL Date functions:
declare #StartDate datetime = '2016-11-08'
, #EndDate datetime = '2023-10-09' --'2017-02-10'
, #dayOfInterest int = 8 --in case you decided you were interested in a different date
--if you don't care whether or not start date is before or after end date; swap their values...
/*
select #StartDate = min(d)
, #EndDate = max(d)
from (select #StartDate d union select #EndDate) x
*/
select
case when #StartDate <= #EndDate then
--if the end date is after or on the start date, we count the number of eighths in between
datediff(month,#StartDate,#EndDate) --count the number of months between the two dates
+ case when datepart(dd,#StartDate)<=#dayOfInterest then 0 else -1 end --if our range excludes the nth this month, exclude it
+ case when datepart(dd,#EndDate)<#dayOfInterest then 0 else 1 end --if our range includes the nth on the last month, include it
else
0 --if the end date is before the start date; we return 0
end
CountTheDays --give the output column a name
Caution
NB: For the 8th this will work perfectly.
For dates after 28th it isn't reliable (you'd be better off going with John Cappelletti's solution), as my logic would add 1 day per month, regardless of whether that month contained that day (i.e. if counting the number of 30ths between 1st Jan and 1st March you'd get 2 instead of 1, as my logic assumes Feb 30th to be a valid date.
I have to get/create date from the user input of week of month (week number in that month - 1st,2nd,3rd,4th and last) and day of week (sunday,monday..) in SQL server.
Examples:
4th Sunday of every month, Last Friday of every month, First Monday etc.
I was able to do it easily in .net but SQL server does seem limited in the date functions.
I am having to use lot of logic to get the date. To calculate the date using the above two parameters I had to use lot of datepart function.
Any suggestions on how to come up with the optimal SQL query for such a function?
I created a function other day for another OP GET Month, Quarter based on Work Week number
This function takes the current year as default it can be further modified to take Year as a parameter too.
an extension to that function can produce the results you are looking for ....
WITH X AS
(
SELECT TOP (CASE WHEN YEAR(GETDATE()) % 4 = 0 THEN 366 ELSE 365 END)-- to handle leap year
DATEADD(DAY
,ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1
, CAST(YEAR(GETDATE()) AS VARCHAR(4)) + '0101' )
DayNumber
From master..spt_values
),DatesData AS(
SELECT DayNumber [Date]
,DATEPART(WEEKDAY,DayNumber) DayOfTheWeek
,DATEDIFF(WEEK,
DATEADD(WEEK,
DATEDIFF(WEEK, 0, DATEADD(MONTH,
DATEDIFF(MONTH, 0, DayNumber), 0)), 0)
, DayNumber- 1) + 1 WeekOfTheMonth
FROM X )
SELECT * FROM DatesData
WHERE DayOfTheWeek = 6 -- A function would expect these two parameters
AND WeekOfTheMonth = 4 -- #DayOfTheWeek and #WeekOfTheMonth
Here is a general formula:
declare #month as datetime --set to the first day of the month you wish to use
declare #week as int --1st, 2nd, 3rd...
declare #day as int --Day of the week (1=sunday, 2=monday...)
--Second monday in August 2015
set #month = '8/1/2015'
set #week = 2
set #day = 2
select dateadd(
day,
((7+#day) - datepart(weekday, #month)) % 7 + 7 * (#week-1),
#month
)
You can also find the last, 2nd to last... etc with this reverse formula:
--Second to last monday in August 2015
set #month = '8/1/2015'
set #week = 2
set #day = 2
select
dateadd(
day,
-((7+datepart(weekday, dateadd(month,1,#month)-1)-#day)) % 7 - 7 * (#week-1),
dateadd(month,1,#month)-1
)
I have a need to create a gross requirements report that takes how much supply and demand of a item in inventory from a start date onwards and 'buckets' it into different weeks of the year so that material planners know when they will need a item and if they have enough stock in inventory at that time.
As an example, today’s date (report date) is 8/27/08. The first step is to find the date for the Monday of the week the report date falls in. In this case, Monday would be 8/25/08. This becomes the first day of the first bucket. All transactions that fall before that are assigned to week #0 and will be summarized as the beginning balance for the report. The remaining buckets are calculated from that point. For the eighth bucket, there is no ending date so any transactions after that 8th bucket start date are considered week #8.
WEEK# START DATE END DATE
0.......None..........8/24/08
1.......8/25/08.......8/31/08
2.......9/1/08.........9/7/08
3.......9/8/08.........9/14/08
4.......9/15/08.......9/21/08
5.......9/22/08.......9/28/08
6.......9/29/08.......10/5/08
7.......10/06/08.....10/12/08
8.......10/13/08......None
How do I get the week #, start date, end date for a given date?
I've always found it easiest and most efficient (for SQL Server) to construct a table with one row for every week into the future through your domain horizon; and join to that (with a "WHERE GETDATE() >= MONDATE AND NOT EXISTS (SELECT 1 FROM table WHERE MONDATE < GETDATE())".
Anything you try to do with UDF's will be much less efficient and I find more difficult to use.
You can get Monday for any given date in a week as:
DATEADD(d, 1 - DATEPART(dw, #date), #date)
and you can write a stored procedure with the following body
-- find Monday at that week
DECLARE #currentDate SMALLDATETIME
SELECT #currentDate = DATEADD(d, 1 - DATEPART(dw, #date), #date)
-- create a table and insert the first record
DECLARE #weekTable TABLE (Id INT, StartDate SMALLDATETIME, EndDate SMALLDATETIME)
INSERT INTO #weekTable VALUES (0, NULL, #currentDate)
-- increment the date
SELECT #currentDate = DATEADD(d, 1, #currentDate)
-- iterate for 7 more weeks
DECLARE #id INT
SET #id = 1
WHILE #id < 8
BEGIN
INSERT INTO #weekTable VALUES (#id, #currentDate, DATEADD(d, 6, #currentDate))
SELECT #currentDate = DATEADD(ww, 1, #currentDate)
SET #id = #id + 1
END
-- add the last record
INSERT INTO #weekTable VALUES (8, #currentDate, NULL)
-- select the values
SELECT Id 'Week #', StartDate 'Start Date', EndDate 'End Date'
FROM #weekTable
When I pass
#date = '20080827'
to this procedure, I get the following
Week # Start Date End Date
0 NULL 2008-08-24 00:00:00
1 2008-08-25 00:00:00 2008-08-31 00:00:00
2 2008-09-01 00:00:00 2008-09-07 00:00:00
3 2008-09-08 00:00:00 2008-09-14 00:00:00
4 2008-09-15 00:00:00 2008-09-21 00:00:00
5 2008-09-22 00:00:00 2008-09-28 00:00:00
6 2008-09-29 00:00:00 2008-10-05 00:00:00
7 2008-10-06 00:00:00 2008-10-12 00:00:00
8 2008-10-13 00:00:00 NULL
--SQL sets the first day of the week as sunday and for our purposes we want it to be Monday.
--This command does that.
SET DATEFIRST 1
DECLARE
#ReportDate DATETIME,
#Weekday INTEGER,
#NumDaysToMonday INTEGER,
#MondayStartPoint DATETIME,
#MondayStartPointWeek INTEGER,
#DateToProcess DATETIME,
#DateToProcessWeek INTEGER,
#Bucket VARCHAR(50),
#DaysDifference INTEGER,
#BucketNumber INTEGER,
#NumDaysToMondayOfDateToProcess INTEGER,
#WeekdayOfDateToProcess INTEGER,
#MondayOfDateToProcess DATETIME,
#SundayOfDateToProcess DATETIME
SET #ReportDate = '2009-01-01'
print #ReportDate
SET #DateToProcess = '2009-01-26'
--print #DateToProcess
SET #Weekday = (select DATEPART ( dw , #ReportDate ))
--print #Weekday
--print DATENAME(dw, #ReportDate)
SET #NumDaysToMonday =
(SELECT
CASE
WHEN #Weekday = 1 THEN 0
WHEN #Weekday = 2 THEN 1
WHEN #Weekday = 3 THEN 2
WHEN #Weekday = 4 THEN 3
WHEN #Weekday = 5 THEN 4
WHEN #Weekday = 6 THEN 5
WHEN #Weekday = 7 THEN 6
END)
--print #NumDaysToMonday
SET #MondayStartPoint = (SELECT DATEADD (d , -1*#NumDaysToMonday, #ReportDate))
--print #MondayStartPoint
SET #DaysDifference = DATEDIFF ( dd , #MondayStartPoint , #DateToProcess )
--PRINT #DaysDifference
SET #BucketNumber = #DaysDifference/7
--print #BucketNumber
----Calculate the start and end dates of this bucket------
PRINT 'Start Of New Calc'
print #DateToProcess
SET #WeekdayOfDateToProcess = (select DATEPART ( dw , #DateToProcess ))
print #WeekdayOfDateToProcess
SET #NumDaysToMondayOfDateToProcess=
(SELECT
CASE
WHEN #WeekdayOfDateToProcess = 1 THEN 0
WHEN #WeekdayOfDateToProcess = 2 THEN 1
WHEN #WeekdayOfDateToProcess = 3 THEN 2
WHEN #WeekdayOfDateToProcess = 4 THEN 3
WHEN #WeekdayOfDateToProcess = 5 THEN 4
WHEN #WeekdayOfDateToProcess = 6 THEN 5
WHEN #WeekdayOfDateToProcess = 7 THEN 6
END)
print #NumDaysToMondayOfDateToProcess
SET #MondayOfDateToProcess = (SELECT DATEADD (d , -1*#NumDaysToMondayOfDateToProcess, #DateToProcess))
print #MondayOfDateToProcess ---This is the start week
SET #SundayOfDateToProcess = (SELECT DATEADD (d , 6, #MondayOfDateToProcess))
PRINT #SundayOfDateToProcess
The problem I see with the one bucket at a time approach is that its hard to make it scale,
If you join into a user defined function you will get better performance, you could use this a a starting point
Why not use a combination of DATEPART(year, date-column) and DATEPART(week, date-column) and group by these values. This works if the week in DATEPART is aligned on Mondays as ISO 8601 requires. In outline:
SELECT DATEPART(year, date_column) AS yyyy,
DATEPART(week, date_column) AS ww,
...other material as required...
FROM SomeTableOrOther
WHERE ...appropriate filters...
GROUP BY yyyy, ww -- ...and other columns as necessary...