Getting individual dates from a date range using T-SQL - sql-server

I have been asked to create two datasets showing 7 days of dates from a two date range.
Example: I have a date range of StartDate = 2022-12-12 and EndDate = 2022-12-25. I need a query to display the individual dates in between these two dates. I was told to use DATEADD, but cannot for the life figure this out.
Any help would be be helpful, thank you.
SELECT DATEADD(DAY, 7, StartDate) AS WeekOne
I was expecting something like this:
2022-12-12
2022-12-13
2022-12-14
2022-12-15
2022-12-16
2022-12-17
2022-12-18

DECLARE #InStartDate DATE='2022-12-12';
DECLARE #InStopDate DATE='2022-12-25';
WITH GEN AS
(
SELECT #InStartDate AS Start_dated
UNION ALL
SELECT DATEADD(DD,1,G.Start_dated)
FROM GEN AS G
WHERE G.Start_dated< #InStopDate
)
SELECT G.*
FROM GEN AS G
You can use something like this

You need to start by generating a numbers table. It needs enough rows to handle each day between your start and end dates. Something like this:
with Numbers as (
select 0 as n
union all
select n + 1
from Numbers
where n < 365
)
select n
from Numbers
option(maxrecursion 0);
Given the example range, I felt like 365 days (one year) was adequate, but it's easy to tweak that range (as we'll see).
Once you have the Numbers table, you can use DateAdd() to add that amount to the start date:
DECLARE #StartDate date = '20221212';
with Numbers as (
select 0 as n
union all
select n + 1
from Numbers
where n < 365
)
select DATEADD(day, n, #StartDate)
from Numbers
option(maxrecursion 0)
From here it's a simple matter to use the EndDate in a WHERE clause to limit the total rows:
DECLARE #StartDate date = '20221212';
DECLARE #EndDate date = '20221231';
with Numbers as (
select 0 as n
union all
select n + 1
from Numbers
where n < DATEDIFF(day, #StartDate, #EndDate)
)
select DATEADD(day, n, #StartDate)
from Numbers
option(maxrecursion 0)

For SQL Server 2022 and later you can use Generate_Series:
declare #StartDate as Date = '20221212', #EndDate as Date = '20221225';
select DateAdd( day, Value, #StartDate ) as TargetDate
from Generate_Series( 0, DateDiff( day, #StartDate, #EndDate ) );
dbfiddle.

Related

Declare Loop SQL to count values between dates

I have a database of employees and I am trying to count the number of employees I have between specific dates in 10 year increments. I have my syntax incorrect but I am not very proficient at identifying why this will not run.
I need the query to produce the following:
12 employees were hired between 1970 and 1980
19 employees were hired between 1980 and 1990
etc.
I keep getting this error:
Why are you looping?
DECLARE #StartYear date = '19700101',
#EndYear date = '20500101';
;WITH Years(y) AS
(
SELECT #StartYear
UNION ALL
SELECT DATEADD(YEAR, 10, y)
FROM Years
WHERE y < #EndYear
)
SELECT Years.y, EmployeesHired = COUNT(e.emp_num)
FROM Years
LEFT OUTER JOIN dbo.lgemployee AS e
ON e.emp_hire_date >= Years.y
AND e.emp_hire_date < DATEADD(YEAR, 10, Years.y)
GROUP BY Years.y;
If you really want to start with ints, you could say:
DECLARE #StartYear int = 1970,
#EndYear int = 2050;
;WITH Years(y) AS
(
SELECT DATEFROMPARTS(#StartYear, 1, 1)
UNION ALL
SELECT DATEADD(YEAR, 10, y)
FROM Years
WHERE y < DATEFROMPARTS(#EndYear, 1, 1)
)
...
Example db<>fiddle
Lots of general date tips here, especially why you want to avoid BETWEEN:
Dating Responsibly
First, you're using malformed date literals.
1970 isn't a date, '1970-01-01' is a date.
2050-01-01 isn't a date, '2050-01-01' is a date.
Second, you don't need a loop at all. Instead round the emp_hiredate down to the preceding decade start, and then group by it...
SELECT
DATEADD(YEAR, (DATEDIFF(YEAR, '1900-01-01', emp_hiredate)/10)*10, '1900-01-01') AS decade_start,
COUNT(*)
FROM
lgemployee
GROUP BY
DATEADD(YEAR, (DATEDIFF(YEAR, '1900-01-01', emp_hiredate)/10)*10, '1900-01-01')
Or even just use (DATEDIFF(YEAR, '1900-01-01', emp_hiredate)/10)*10 to identify the decade.

How to find out Date/Month interval between #StartDate and #EndDate passed in stored procedure parameters

I am working on a stored procedure where I am dividing the number of rows by interval of month and day repeated in the specified date range.
Interval Month and Day = 7th April and 8th October
Example
For Date range 2014/01/01 and 2014/12/31, 7th April and 8th October are repeated 2 times so I will divide my statement by 2.
For Date range 2014/01/01 and 2015/09/01, 7th April came 2 and 8th October 1 so I will divide my statement by 3.
As others have stated, the question is a bit unclear, but I believe I know what you're trying to do. You are trying to find the number of times that a set of dates (only taking Month/Day into account) happen over a range of dates (set by #StartDate and #EndDate). I think the select count(*) from TableName part of the question is a distraction, as you already know how to do that. Below is an answer on how to get the denominator, which is what you are trying to figure out how to do.
declare #StartDate date = '2014-01-01'
, #EndDate date = '2014-12-31'
, #DenVal int --Denominator Value
create table #dates_of_interest
(
month_nbr tinyint not null
, day_nbr tinyint not null
)
insert into #dates_of_interest
values (4, 7) --7th of April
, (10, 8) --8th of October
; with date_list as
(
--use a Recursive CTE to generate a list of all the dates in the given range.
select #StartDate as dt
union all
select dateadd(d,1,dt) as dt
from date_list
where 1=1
and dt < #EndDate
)
--Get the output of the Recursive CTE along with Month/Day numbes
select dt
, datepart(m,dt) as month_nbr
, datepart(d,dt) as day_nbr
into #list_of_dates
from date_list as dl
option (maxrecursion 32767) --set to max possible levels of recursion (might want to lower this number)
--Set the Denominator to the results of the sum(case/when) AKA countif
set #DenVal =
(
select sum(case when di.month_nbr is null and di.day_nbr is null then 0 else 1 end)
from #list_of_dates as ld
left join #dates_of_interest as di on ld.month_nbr = di.month_nbr
and ld.day_nbr = di.day_nbr
)
Print #DenVal
Both examples of 1/1/2014 - 12/31/2014 and 1/1/2014 - 9/1/2015 come up with the desired results of 2 and 3 respectively. There may be other ways of accomplishing this, but I thought that a Recursive CTE was the best option.

Count number of occurrences of a day between two dates SQL Server

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.

Checking a range of dates in SQL

Following on from a question I put on yesterday, I need to return a range of "available" dates for a laptop rollout "booking system". I want to populate a table of possible available dates a user can book a slot on by checking for each date the total possible number of slots, and subtracting the number of slots already booked.
The logic is as follows:
A technician can build 3 laptops per day.
On any day there may be 1, 2 or 3 technicians available.
A table holds the bookings made
I don't want a table of all possible dates, I want to calculate it on the fly
Relevant tables are:
tl_sb_slotBooking
This contains the bookings already made
tl_sb_availabilityPeriods
This is used to calculate the total number of available slots on a given day
I can bring back a list of dates with a fixed maximum number (in this case 3) of slots:
DECLARE #startDate DATE
DECLARE #endDate DATE
SET #startDate = GETDATE()
SET #endDate = DATEADD(m,3,#startDate)
;
WITH dates(Date) AS
(
SELECT #startdate as Date
UNION ALL
SELECT DATEADD(d,1,[Date])
FROM dates
WHERE DATE < #enddate
)
SELECT Date
FROM dates
EXCEPT
SELECT date
FROM tl_sb_booking
GROUP BY date
HAVING COUNT(date) >= 3
However, the maximum won't always be 3, it changes for each day.
I can find the maximum possible slots for a given day:
DECLARE #myDate DATETIME = '2013-06-22'
SELECT SUM(laptopsPerDay) AS totalSlots
FROM tl_sb_technicianAvailability
WHERE startDate <= #myDate AND endDate >= #myDate
AND availabiltyStateID=3
it will bring back 6 as the total number of slots available for 2013-06-22. (The availabilityStateID field is used to store available/unavailable etc.)
So, the bit I am stuck on is combining the two.
What I want is for each possible date, if the number of slots already booked is less than the number of possible slots for that day, add it to the table being returned (otherwise don't).
Firstly, althought you are only generating a small list, using a CTE to generate a sequential list performs terribly and is best avoided.
For the sake of this I will use the system table Master..spt_values for a sequential list of numbers, but if you are worried about using undocumented system tables then there are other methods in the link above.
The first thing I would do is split the technician availability dates into a row per day, this will allow for technicians who are available for only part of the peiod required (e.g. from your sceen shot of the table if you wanted to query from 18th June to 26th June none of the technicians show would appear as available using the query you have posted):
SELECT Date = DATEADD(DAY, spt.Number, ta.StartDate),
ta.TechnicianID,
ta.LapTopsPerDay
FROM tl_sb_technicianAvailability ta
INNER JOIN Master..spt_values spt
ON spt.Type = 'P'
AND spt.Number BETWEEN 0 AND DATEDIFF(DAY, ta.startDate, ta.EndDate)
This would simply turn:
TechnicianID StartDate EndDate LapTopsPerDay
1 20130620 20130624 3
into
Date TechnicianID LapTopsPerDay
20130620 1 3
20130621 1 3
20130622 1 3
20130623 1 3
20130624 1 3
You can then limit this list to the date range required, and sum up the total laptops than can be done as this is not needed on a technicial level:
WITH ExplodedAvailability AS
( SELECT Date = DATEADD(DAY, spt.Number, ta.StartDate),
ta.TechnicianID,
ta.LapTopsPerDay
FROM tl_sb_technicianAvailability ta
INNER JOIN Master..spt_values spt
ON spt.Type = 'P'
AND spt.Number BETWEEN 0 AND DATEDIFF(DAY, ta.startDate, ta.EndDate)
)
SELECT Date, TotalLaptops = SUM(LapTopsPerDay)
FROM ExplodedAvailability
WHERE Date >= #StartDate
AND Date < #EndDate
GROUP BY Date;
Finally you can LEFT JOIN to the bookings table to get the available slots per day
WITH ExplodedAvailability AS
( SELECT Date = DATEADD(DAY, spt.Number, ta.StartDate),
ta.TechnicianID,
ta.LapTopsPerDay
FROM tl_sb_technicianAvailability ta
INNER JOIN Master..spt_values spt
ON spt.Type = 'P'
AND spt.Number BETWEEN 0 AND DATEDIFF(DAY, ta.startDate, ta.EndDate)
), Availability AS
( SELECT Date, TotalLaptops = SUM(LapTopsPerDay)
FROM ExplodedAvailability
WHERE Date >= #StartDate
AND Date < #EndDate
GROUP BY Date
), Bookings AS
( SELECT Date, SlotsBooked = COUNT(*)
FROM tl_sb_booking
GROUP BY Date
)
SELECT Availability.Date,
Availability.TotalLaptops,
RemainingSlots = Availability.TotalLaptops - ISNULL(Bookings.SlotsBooked, 0)
FROM Availability
LEFT JOIN Bookings
ON Bookings.Date = Availability.Date;
I think what you are after is to add a booking to the next available day, so the query to do this would be:
DECLARE #UserID INT = 1;
WITH ExplodedAvailability AS
( SELECT Date = DATEADD(DAY, spt.Number, ta.StartDate),
ta.TechnicianID,
ta.LapTopsPerDay
FROM tl_sb_technicianAvailability ta
INNER JOIN Master..spt_values spt
ON spt.Type = 'P'
AND spt.Number BETWEEN 0 AND DATEDIFF(DAY, ta.startDate, ta.EndDate)
), Availability AS
( SELECT Date, TotalLaptops = SUM(LapTopsPerDay)
FROM ExplodedAvailability
WHERE Date >= CAST(GETDATE() AS DATE)
GROUP BY Date
), Bookings AS
( SELECT Date, SlotsBooked = COUNT(*)
FROM tl_sb_booking
GROUP BY Date
)
INSERT tl_sb_slotBooking (UserID, Date)
SELECT #UserID, MIN(Availability.Date)
FROM Availability
LEFT JOIN Bookings
ON Bookings.Date = Availability.Date
WHERE Availability.TotalLaptops > ISNULL(Bookings.SlotsBooked, 0)
Should this be of use to anyone, this is the way I ultimately did it:
DECLARE #startDate DATE
DECLARE #endDate DATE
SET #startDate = GETDATE()
SET #endDate = DATEADD(m,3,#startDate)
;
WITH dates(currentDate) AS
(
SELECT #startdate as currentDate
UNION ALL
SELECT DATEADD(d,1,[currentDate])
FROM dates
WHERE currentDate < #enddate
)
SELECT currentDate
FROM dates
WHERE /* slots booked for date */
(
SELECT count([date])
FROM tl_sb_booking
where [date] = currentDate
)
<
/* total slots available */
(
SELECT SUM(laptopsPerDay) AS totalSlots
FROM tl_sb_technicianAvailability
WHERE startDate <= currentDate AND endDate >= currentDate
AND availabiltyStateID=3
)

last friday of a given month in sql server

How do i get the date for last friday of the month in T-SQL?
I will be passing the year and month as parameter,e.g, 201211.
If I pass '201211' as parameter it should return me '20121130' as answer as it's the date of last friday of month of november'12.
The 5 January 1900 was a Friday. This uses that a base date and calculates the last Friday in any given month though you must give it a date during the month rather than just the month itself. Replace the 2012-12-01 in this with a date in your month
SELECT DATEADD(DY,DATEDIFF(DY,'1900-01-05',DATEADD(MM,DATEDIFF(MM,0,'2012-12-01'),30))/7*7,'1900-01-05')
You can also use this to get the last Saturday by replacing the 1900-01-05 WITH 1900-01-06 etc.
This would be much simpler using a calendar table; after creating the appropriate columns for your own needs you can just write this:
select
max([Date])
from
dbo.Calendar
where
YearAndMonth = 201211 and
DayOfWeek = 'Friday'
A calendar table is generally a much better solution for determining dates than using functions because the code is much more readable and you can use your own definition of things like WeekNumber, FinancialQuarter etc. that vary widely between countries and even companies.
I created a scalar function for this:
create function [dbo].[lastDWMonth]
(
#y int
,#m int
,#dw int
)
returns date
as
begin
declare #d date
;with x as
(
select datefromparts(#y,#m,1) d
union all
select dateadd(day,1,d) from x where d < eomonth(datefromparts(#y,#m,1))
)
select
#d = max(d)
from
x
where
datepart(dw,d) = #dw
return #d
end
Declare #d1 datetime = '2019-12-23'
Declare #searchDay int = 2 -- monday
select DATEADD(DAY, #searchDay-DATEPART(WEEKday, DateADD(day,-1, DATEADD(month, DATEDIFF(MONTH, 0, #d1)+1, 0))),DateADD(day,-1, DATEADD(month, DATEDIFF(MONTH, 0, #d1)+1, 0)))
This will give you Date on last Monday of the month, you can change your search by changing value in #searchDay

Resources