I have a table in SQL Server which have following attributes.
ProjectID || Start_Date || End Date || Duration(Days)
1 10-Jan-2013 5
2 02-FEB 2013 16
3 26-Mar-2013 50
. . .
I want to find start dates based upon the qualified days (Monday-Friday). For example for End Date: 10 January start date will be 04 January as 5 and 6 January are Saturday and Sunday.
I want to know how this could be possible in T-SQL (Function,Custom T-SQL Block). Any guidance and help is highly appreciated.
This should do it:
WITH tblProjects2 AS (
SELECT ProjectId, DATEADD(DAY, -Duration, EndDate) AS StartDate FROM tblProjects
)
SELECT ProjectId,
CASE WHEN DATENAME(DW, StartDate) = 'Sunday' THEN DATEADD(day, -2, StartDate)
WHEN DATENAME(DW, StartDate) = 'Saturday' THEN DATEADD(day, -1, StartDate)
ELSE StartDate END AS ProperStartDate
FROM tblProjects2
The approach is rather simple - when your new date falls on a weekend, subtract 1 or 2 days, depending on whether it's Saturday or Sunday respectively.
Test case structure for tblProjects:
CREATE TABLE [dbo].[tblProjects](
[ProjectId] [int] NULL,
[StartDate] [date] NULL,
[EndDate] [date] NULL,
[Duration] [int] NULL
)
Test case data for same:
INSERT INTO tblProjects VALUES (1, NULL, '10-Jan-2013', 5);
INSERT INTO tblProjects VALUES (2, NULL, '02-FEB-2013', 16);
INSERT INTO tblProjects VALUES (3, NULL, '26-Mar-2013', 50);
EDIT - Same functionality, using a function:
CREATE FUNCTION dbo.getStartDate(#EndDate Date, #Duration int)
RETURNS DATE
AS
BEGIN
DECLARE #newDate DATE;
SET #newDate = DATEADD(day, -#Duration, #EndDate);
RETURN (CASE
WHEN DATENAME(DW, #newDate) = 'Sunday' THEN DATEADD(day, -2, #newDate)
WHEN DATENAME(DW, #newDate) = 'Saturday' THEN DATEADD(day, -1, #newDate)
ELSE #newDate END)
END;
Then you can rewrite the above query like this:
SELECT ProjectId, dbo.getStartDate(EndDate, Duration) AS StartDate
FROM tblProjects
A bit harder to read, but slightly faster
SELECT ProjectId,
DATEADD(d, -Duration - CASE DATEDIFF(d, +Duration, EndDate) % 7
WHEN 5 THEN 1
WHEN 6 THEN 2
ELSE 0 END
, EndDate)
FROM tblProjects
Related
How can I get the date of specific day ? Like if I have Thursday or month number ?
If I give 12 for instance I want to get the date of 12th day of this month. Or if I give 'Sun' or 'Sat' is it possible to get the dates of these days ?
DATEFROMPARTS function can construct a date from day, month and year.
DATEPARTS does the opposite - gives you the day, month, year, hour, etc. of a date. Or you can use functions like YEAR, MONTH and DAY.
You can deconstruct the value returned by GETDATE function and construct whatever date you want. Here is for example how to get the date for 12th day of the current month:
select DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 12)
Converting 'Sun' or 'Sat' to date is a bit more difficult. First, they aren't quite deterministic. If today is Friday, "Sunday this week" means "next Sunday" in some parts of the world and "last Sunday" in others. You should implement your own logic based on the value returned by DATEPART(dw, GETDATE()) (which will give you the day of the week).
To find the weekday of the current month
DECLARE #daynumber INT = 12
SELECT datename(weekday, dateadd(d, #daynumber - 1, getdate()))
To find the dates of the current month of a given weekday
DECLARE #dayname char(3) = 'sat'
;WITH CTE as
(
SELECt TOP
(datediff(D, eomonth(getdate(), -1),eomonth(getdate())))
dateadd(d,row_number()over(ORDER BY 1/0),
eomonth(getdate(),-1))date
FROM
(values(1),(2),(3),(4),(5),(6))x(x),
(values(1),(2),(3),(4),(5),(6))y(x)
)
SELECT day(date) monthday, date
FROM CTE
WHERE left(datename(weekday, date),3) = #dayname
select sysdatetime(); --2018-12-13 16:29:56.0560574
---If I give 12 for instance I want to get the date of 12th day of this month.
declare #numDate int = 12;
select dateadd(m, datediff(m,0,getdate()),#numDate - 1 ); --2018-12-12 00:00:00.000
--Or if I give 'Sun' or 'Sat' is it possible to get the dates of these days ?
declare #text nvarchar(20) = 'Sunday';
declare #dateStart date = dateadd(month, datediff(month, 0, sysdatetime()), 0),
#days int =( select (DAY(dateadd(dd,-1,DATEADD(m,1,cast(2018 as varchar(4)) + '-' + cast(12 as varchar(2)) +'-01')))));
declare #dateEnd date = DATEADD(day,#days-1,#dateStart);
;WITH CTE (Dates,EndDate) AS
(
SELECT #dateStart AS Dates,#dateEnd AS EndDate
UNION ALL
SELECT DATEADD(day,1,Dates),EndDate
FROM CTE
WHERE DATEADD(day,1,Dates) <= EndDate
)
SELECT CTE.Dates, DATENAME(DW, CTE.Dates)
FROM CTE
where DATENAME(DW, CTE.Dates) = #text;
Result:
Dates,Day
2018/12/2,Sunday
2018/12/9,Sunday
2018/12/16,Sunday
2018/12/23,Sunday
2018/12/30,Sunday
-- Here is how to get week day name to week day number
DECLARE #T TABLE (Dow INT, NameOfDay VARCHAR(15), ShortName CHAR(3));
WITH Days AS
(
SELECT TOP 7
ROW_NUMBER() OVER(PARTITION BY object_id ORDER BY object_id) AS RowNo
FROM
sys.all_columns
)
INSERT INTO #T
SELECT
RowNo,
DATENAME(WEEKDAY, RowNo - 1),
LEFT(DATENAME(WEEKDAY, RowNo - 1), 3)
FROM
Days
SELECT
*
FROM
#T;
-- Here is how to get start of period
SELECT
DATEADD(DAY, DATEDIFF(DAY, 0, GETDATE()), 0) AS StartOfDay,
DATEADD(WEEK, DATEDIFF(WEEK, 0, GETDATE()), 0) AS StartOfWeek,
DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0) AS StartOfMonth,
DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0) AS StartOfYear;
-- An example
WITH
StartPeriods AS
(
SELECT DATEADD(WEEK, DATEDIFF(WEEK, 0, GETDATE()), 0) AS StartOfWeek
),
SelectedDay AS
(
SELECT
Dow - 1 AS Dow,
(SELECT StartOfWeek FROM StartPeriods) AS StartOfWeek
FROM
#T
WHERE
ShortName = 'Wed'
)
SELECT
DATEADD(DAY, Dow, StartOfWeek)
FROM
SelectedDay;
I have this table where I am specifying Holidays in week. I want to calculate total working days between 2 specific dates using these fields.
CREATE TABLE [tbl_Shift](
[OffDay1] [nvarchar](25) NOT NULL CONSTRAINT [DF_tbl_Shift_OffDay1] DEFAULT (N'Sunday'),
[IsAlternateOffDay2] [bit] NULL,
[OffDay2] [nvarchar](25) NULL
)
INSERT INTO [tbl_Shift] VALUES ('Sunday', 'True', 'Saturday')
I have this query written but I am not able to get correct days. It should give 23 days as there are 2 holidays in each week and 31 days in total but I'm getting 26 days.
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SET #StartDate = '2018/05/01'
SET #EndDate = '2018/05/31'
SELECT
(DATEDIFF(dd, #StartDate, #EndDate+1)) -(DATEDIFF(wk, #StartDate, #EndDate))
-(CASE WHEN IsAlternateOffday2 = 1 THEN 1 END) FROM HRM.tbl_Shift
this will give 23:
SELECT
(DATEDIFF(dd, #StartDate, #EndDate+1)) -(DATEDIFF(wk, #StartDate, #EndDate))
-ISNULL((CASE WHEN IsAlternateOffday2 = 1 THEN (DATEDIFF(wk, #StartDate, #EndDate)) END), 0)
FROM HRM.tbl_Shift
This subtracts 2 days for every weekend. Check your server config if Sunday is first or last day of the week. This can throw things off by a weekend.
SELECT DATEPART(WEEKDAY,'20180506') --Checks if Sunday is Day 1 or Day 7
DECLARE #start DATETIME = '20180501'
DECLARE #end DATETIME = '20180531'
SELECT DATEDIFF(DAY,#start,#end+1) - (DATEDIFF(WEEK,#start,#end+1)*2)
UPDATE:
Use COALESCE to replace NULLS with an alternative value.
DECLARE #offdate DATETIME = NULL
SELECT COALESCE(#offdate,GETDATE())
Try This
DECLARE #StartDate DATE ='2018-05-01',
#EndDate DATE ='2018-05-31'
;WITH CTE
AS
(
SELECT DATEADD(DD,Number-1,#StartDate) MOnthDates,
DATENAME(DW,DATEADD(DD,Number-1,#StartDate)) As DayNAmes,
CASE WHEN DATENAME(DW,DATEADD(DD,Number-1,#StartDate)) IN ('Saturday','Sunday') THEN 0 ELSE 1 END WeekDays
FROM master.dbo.spt_values
WHERE [Type]='P'
AND Number Between 1 AND 10000
)
SELECT COUNT(WeekDays) AS WeekDaysCount
FROM CTE
WHERE WeekDays<>0
AND MOnthDates Between #StartDate AND DATEADD(DAY,1,#EndDate)
Result
WeekDaysCount
-------------
23
Demo:http://rextester.com/TOLYT35075
This was what I created after reviewing the answers. I needed the number of days per month for several months. The below will provide that, and insert it into the #MonthDayCount table. Note: I named a field Month, even though that is a SQL Server defined term; you can change it if needed.
You must enter the first day of the month and the last for it to count the first and last month correctly. If you entered 1/2/2022, it would result in January being short one day.
DECLARE #StartDate DATE = '1/01/2021'--The start of the first month the number of days are needed for.
DECLARE #EndDate DATE = '1/31/2021'--The end of the first month the number of days are needed for.
DECLARE #FinalDate DATE = '12/31/2022'--This is the last month that will be inserted. Includes this month.
IF OBJECT_ID('tempdb..#MonthDayCount', 'U') IS NOT NULL
DROP TABLE #MonthDayCount;
CREATE TABLE #MonthDayCount
(
[Month] VARCHAR(MAX),
[# of WeekDays] INT,
[EOM] DATE
)
WHILE #StartDate <= #FinalDate
BEGIN
INSERT INTO
#MonthDayCount
VALUES (
DATENAME(m,#StartDate),
(
DATEDIFF(dd, #StartDate, #EndDate) + 1)
-(DATEDIFF(wk, #StartDate, #EndDate) * 2)
-(CASE WHEN DATENAME(dw, #StartDate) = 'Sunday' THEN 1 ELSE 0 END)
-(CASE WHEN DATENAME(dw, #EndDate) = 'Saturday' THEN 1 ELSE 0 END
),
#EndDate
)
SET #StartDate = DATEADD(m,1,#StartDate)
SET #EndDate = EOMONTH(#EndDate,1)
END
MS SQL Server query to find count of all the working days (non Saturdays and Sundays) in current month.
Works in SQL Server, Azure Synapse Analytics
Note:
Change the getdate() as per your need.
SELECT Day(Eomonth(Getdate())) - ( Datediff(d, Dateadd(d, Datediff(d, -1,
Dateadd(month, Month(
Getdate()) - 1 + (
Year(Getdate()
) - 1900 )
*
12, 6)) / 7 * 7, -1),
Dateadd(m, 1, Dateadd(
month,
Month(Getdate())
- 1 +
(
Year(Getdate()) - 1900 )
*
12, 6
)
)) / 7 +
Datediff(d, Dateadd(d, Datediff(d, -1,
Dateadd(
month,
Month(Getdate()) - 1 + (
Year(Getdate()
) - 1900 )
*
12, 7)) / 7 * 7, -1), Dateadd(m, 1,
Dateadd(month, Month(Getdate()) - 1 +
(
Year(Getdate()) - 1900 ) *
12, 7)
)) / 7 ) AS Num_Working_Days
I use this query to get the last business date excluding weekends:
DECLARE #LastBizDate as DATETIME
SELECT #LastBizDate = (DATEADD(DAY, CASE DATENAME(WEEKDAY, GETDATE())
WHEN 'Saturday' THEN -1
WHEN 'Sunday' THEN -2
ELSE -1 END, DATEDIFF(DAY, 0, GETDATE())))
SELECT #LastBizDate AS 'Last_Business_Date'
But how can I exclude the holidays?
I have this tbl_Holidays table with sample data:
-------------------------
|Holiday_Date |
|-----------------------|
|2017-04-14 00:00:00.000|
|2017-05-01 00:00:00.000|
|2017-10-18 00:00:00.000|
|2017-12-25 00:00:00.000|
-------------------------
Note: no need to consider that when the holiday falls on Sunday, then Monday will be considered Off. Again, no need to consider that.
Thank you in advance.
I think you need a loop to do this.
After excluding saturday and sunday, you need to check whether the date is in the holiday table. Again you need to check the date (calculated after excluding from holiday table )falls on sunday and saturday.
DECLARE #last_wrk_day DATETIME
,#today DATETIME
DECLARE #TblHoliday AS TABLE (Holiday_Date DATE)
INSERT INTO #TblHoliday
VALUES ('2017-04-14 00:00:00.000')
,('2017-05-01 00:00:00.000')
,('2017-10-18 00:00:00.000')
,('2017-12-25 00:00:00.000')
,('2017-04-13 00:00:00.000')
SET #today = '2017-04-10 00:00:00.000' --getdate()
SET #last_wrk_day = dateadd(day, - 1, #today)
WHILE (
(
SELECT TOP 1 1
FROM #TblHoliday
WHERE Holiday_Date = #last_wrk_day
) = 1
OR DATEPART(dw, #last_wrk_day) IN (1, 7)
)
BEGIN
SET #last_wrk_day = dateadd(day, - 1, #last_wrk_day)
END
SELECT #last_wrk_day
OUTPUT
2017-04-13 00:00:00.000
Can I find the date of a day that is on which dates the Saturdays and Sundays of a specific month fall? For e.g consider the month of JANUARY-2017. The following dates are weekend days:
7/1/2017 - Saturday
14/1/2017 - Saturday
21/1/2017 - Saturday
28/1/2017 - Saturday
1/1/2017 - Sunday
8/1/2017 - Sunday
15/1/2017 - Sunday
22/1/2017 - Sunday
29/1/2017 - Sunday
I want a SQL Server query for this such that when I pass in month and year as input, I should get back all the above dates (only dates of Saturday and Sunday) as output
I do not wish to use any user defined function and want to finish it in a single SELECT statement
Note: As already noted by another user in the comments, this query depends upon your server settings, namely DATEFIRST. If you need alterations to the query because of different settings, just tell me and I can change it around for you.
Using a CTE as dummy data...
/* Ignore this part...*/
WITH CTE AS
(
SELECT CAST('01/01/2017' AS DATE) AS [Date]
UNION ALL
SELECT DATEADD(DAY,1,[Date])
FROM CTE
WHERE DATE <= '12/31/2017'
)
/*Your actual SELECT statement would look like this, from your own table of course*/
SELECT
[Date]
,CASE DATEPART(dw,[Date])
WHEN 1 THEN 'Sunday'
WHEN 2 THEN 'Monday'
WHEN 3 THEN 'Tuesday'
WHEN 4 THEN 'Wednesday'
WHEN 5 THEN 'Thursday'
WHEN 6 THEN 'Friday'
WHEN 7 THEN 'Saturday'
END
FROM CTE
WHERE DATEPART(dw,[Date]) IN (1,7)
AND MONTH([Date]) = 12--<month>
AND YEAR([Date]) = 2017--<year>
OPTION (MAXRECURSION 0) -- You won't need this line if you're querying a real table
;
If running that works for you, then your real query would probably look something like this:
SELECT
[Date]
,CASE DATEPART(dw,[Date])
WHEN 1 THEN 'Sunday'
WHEN 2 THEN 'Monday'
WHEN 3 THEN 'Tuesday'
WHEN 4 THEN 'Wednesday'
WHEN 5 THEN 'Thursday'
WHEN 6 THEN 'Friday'
WHEN 7 THEN 'Saturday'
END
FROM < the table you want >
WHERE DATEPART(dw,[Date]) IN (1,7) -- Only Sundays and Saturdays
AND MONTH([Date]) = < the month you want >
AND YEAR([Date]) = < the year you want >
;
If you want to generate the data, then a CTE is the way to go. If you're passing parameters, it would look something like this:
DECLARE
#MONTH INT
,#YEAR INT
;
SET #MONTH = 1;
SET #YEAR = 2017;
WITH CTE AS
(
SELECT CAST(CAST(#MONTH AS VARCHAR(2)) + '/01/' + CAST(#YEAR AS VARCHAR(4)) AS [Date]) AS DATE
UNION ALL
SELECT DATEADD(DAY,1,[Date])
FROM CTE
WHERE DATE <= CAST(#MONTH AS VARCHAR(2)) +
CASE
WHEN #MONTH IN (9,4,6,11)
THEN '/30/'
WHEN #MONTH IN (1,3,5,7,8,10,12)
THEN '/31/'
WHEN #MONTH = 2 AND #YEAR/4.00 = #YEAR/4
THEN '/29/'
ELSE '/28/'
END
+ CAST(#YEAR AS VARCHAR(4))
)
SELECT
[Date]
,CASE DATEPART(dw,[Date])
WHEN 1 THEN 'Sunday'
WHEN 2 THEN 'Monday'
WHEN 3 THEN 'Tuesday'
WHEN 4 THEN 'Wednesday'
WHEN 5 THEN 'Thursday'
WHEN 6 THEN 'Friday'
WHEN 7 THEN 'Saturday'
END
FROM CTE
WHERE DATEPART(dw,[Date]) IN (1,7)
OPTION (MAXRECURSION 0)
;
Please try this one.
DECLARE #Year AS INT=2017,
#Month AS INT=3,
#FirstDateOfYear DATETIME,
#LastDateOfYear DATETIME
SELECT #FirstDateOfYear = DATEADD(yyyy, #Year - 1900, 0)
SELECT #LastDateOfYear = DATEADD(yyyy, #Year - 1900 + 1, 0)
-- Creating Query to Prepare Year Data
;WITH cte AS (
SELECT 1 AS DayID,
#FirstDateOfYear AS FromDate,
DATENAME(dw, #FirstDateOfYear) AS Dayname
UNION ALL
SELECT cte.DayID + 1 AS DayID,
DATEADD(d, 1 ,cte.FromDate),
DATENAME(dw, DATEADD(d, 1 ,cte.FromDate)) AS Dayname
FROM cte
WHERE DATEADD(d,1,cte.FromDate) < #LastDateOfYear
)
SELECT FromDate AS Date, Dayname
FROM CTE
WHERE DayName IN ('Saturday','Sunday') and month(FromDate) = #Month
OPTION (MaxRecursion 370)
This should do the trick:
DECLARE #month date = '2017-01-01'
SET #month = dateadd(month, datediff(month, 0, #month), 0)
;WITH CTE as
(
SELECT 0 x
FROM (values(1),(1),(1),(1),(1),(1)) x(n)
),
CTE2 as
(
SELECT
top(day(eomonth(#month)))
-- use this syntax for sqlserver 2008
-- top(datediff(d, #month,dateadd(month,1,#month)))
cast(dateadd(d, row_number()over(order by(select 1))-1,#month) as date) cDate
FROM CTE CROSS JOIN CTE C2
)
SELECT
cDate,
datename(weekday, cDate) Weekday
FROM CTE2
WHERE
datediff(d,0,cDate)%7 > 4
Fiddle
From https://www.sqlservercentral.com/articles/finding-the-correct-weekday-regardless-of-datefirst, you simply:
(DATEPART(dw, #your_date) + ##DATEFIRST) % 7 NOT BETWEEN 2 AND 6
As requested, single select, language neutral, dateFirst neutral, almost SQL version neutral:
declare #OneDate datetime = '28/01/2017'; -- Any date from the target month/year
select MyDate -- raw date or ...
-- convert(varchar, MyDate, 103) + ' - ' + dateName(dw, MyDate) -- as Sample
as WeekEndDate
from (
select dateAdd(dd, number, dateAdd(mm, dateDiff(mm, 0, #OneDate), 0)) as MyDate
from master..spt_values
where type = 'P' and number < 31
) j
where 1 + (datePart(dw, MyDate) + ##DATEFIRST + 5) % 7 in (6, 7)
and month(MyDate) = month(#OneDate)
-- order by 1 + (datePart(dw, MyDate) + ##DATEFIRST + 5) % 7, MyDate -- as Sample
;
Another way to solve this problem as follow -
DECLARE #MONTH INT,#YEAR INT
SET #MONTH = 1;
SET #YEAR = 2017;
Declare #StartDate date =CAST(CAST(#MONTH AS VARCHAR(2)) + '/01/' + CAST(#YEAR AS VARCHAR(4)) AS [Date]), #EndDate date
Set #EndDate = EOMONTH(#StartDate)
Declare #Temp table (DateOfDay date, DaysName varchar(50))
While(#StartDate <= #EndDate)
Begin
Insert into #Temp
SELECT #StartDate DateOfMonth,
case when DATENAME(DW, #StartDate) = 'Saturday' then DATENAME(DW, #StartDate)
when DATENAME(DW, #StartDate) = 'Sunday' then DATENAME(DW, #StartDate)
end DaysName
set #StartDate = DATEADD(d,1,#StartDate)
End
select * from #Temp where DaysName is not null order by DaysName, DateOfDay
Can't you do something like this?
SELECT DATENAME(dw,'10/11/2016') AS DATE
WHERE DATE CONTAINS('Saturday') OR DATE CONTAINS('SUNDAY')
and instead of '10/11/2016' you only have to figure out how to generate all the dates in a month/year?
I need to calculate average of date differences excluding weekends across rows in a column. The query gives me wrong results. But, when i calculate it myself, i get different results. the query is something like -
select zone, avg(datediff(dd,startdate,enddate)-datediff((ww,startdate,enddate)*2)) from table where startdate >'1/1/2013' group by zone
I don't get correct results. Please guide me.
try this code:
select zone , avg(NumOfDays) from
(select zone, (datediff(dd,startdate,enddate)-datediff((ww,startdate,enddate)*2)) as NumOfDays from table where startdate >'1/1/2013')
group by zone
I don't know how your data looks like and what you challenge exactly is but I think your problem is "excluding weekend"? Maybe this helps you?
First create a function that counts every weekend day between your period for filtering it later:
CREATE FUNCTION fnc_GetWeekendDays(#dFrom DATETIME, #dTo DATETIME)
RETURNS INT AS
BEGIN
Declare #weekendDays int
Set #weekendDays = 0
While #dFrom <= #dTo
Begin
If ((datepart(dw, #dFrom) = 1) OR (datepart(dw, #dFrom) = 7))
Set #weekendDays = #weekendDays + 1
Set #dFrom = DateAdd(d, 1, #dFrom)
End
Return (#weekendDays)
END
After this, write your query:
Select
zone,
avg(cast((datediff(SECOND,StartTime,EndTime) - dbo.fnc_GetWeekendDays(StartTime, EndTime)*86400) as float))
from
(
Select zone,
CASE
WHEN (datepart(dw, startdate) = 1) THEN DATEADD(Day, DATEDIFF(Day, 0, startdate), 1)
WHEN (datepart(dw, startdate) = 7) THEN DATEADD(Day, DATEDIFF(Day, 0, startdate), 2)
ELSE startdate END as StartTime,
CASE
WHEN (datepart(dw, enddate) = 1) THEN DATEADD(Day, DATEDIFF(Day, 0, enddate), -2)
WHEN (datepart(dw, enddate) = 7) THEN DATEADD(Day, DATEDIFF(Day, 0, enddate), -1)
ELSE enddateEND as EndTime
from table
) subquery
where StartTime < EndTime and startdate >'2013-01-01 00:00:00.000'
Group by zone
You will get your average time in seconds, if you want another format you have to calculate it...