How to handle DATEFIRST when using DATEPART - sql-server

-- SET DATEFIRST to U.S. English default value of 7.
SET DATEFIRST 7;
SELECT
##DATEFIRST;
SELECT
GETDATE()
, DATEPART(dw , GETDATE()) AS DayOfWeek;
-- January 1, 1999 is a Friday. Because the U.S. English default
-- specifies Sunday as the first day of the week, DATEPART of 1999-1-1
-- (Friday) yields a value of 6, because Friday is the sixth day of the
-- week when you start with Sunday as day 1.
SET DATEFIRST 3;
SELECT
##DATEFIRST;
-- Because Wednesday is now considered the first day of the week,
-- DATEPART now shows that 1999-1-1 (a Friday) is the third day of the
-- week. The following DATEPART function should return a value of 3.
SELECT
GETDATE()
, DATEPART(dw , GETDATE()) AS DayOfWeek;
SET DATEFIRST 7;
How do we handle getting the DATEPART (1 = Sunday always) irregardless of DATEFIRST setting?
I really don't want to do a case and subtract...

this always seems ridiculous to me, how about
select datediff(day,0, getdate()) % 7
where 6 represents Sunday
or you could do
select (datediff(day,0, '2016-07-31') - 5) % 7
to get Sun = 1, Mon = 2, Tue = 3 ... etc
or you could do this fiddle
select (datepart(weekday,get_date()) + ##datefirst - 1) % 7 + 1
seems to work for all datepart
set datefirst 5
select (datepart(weekday,'2016-07-31') + ##datefirst - 1) % 7 + 1
select (datepart(weekday,'2016-08-01') + ##datefirst - 1) % 7 + 1
select (datepart(weekday,'2016-08-02') + ##datefirst - 1) % 7 + 1
select (datepart(weekday,'2016-08-03') + ##datefirst - 1) % 7 + 1
select (datepart(weekday,'2016-08-04') + ##datefirst - 1) % 7 + 1
select (datepart(weekday,'2016-08-05') + ##datefirst - 1) % 7 + 1
select (datepart(weekday,'2016-08-06') + ##datefirst - 1) % 7 + 1
select (datepart(weekday,'2016-08-07') + ##datefirst - 1) % 7 + 1

This code selects Monday as the first day of week whatever the setting in your engine:
((datepart(DW, #YourDateVariable) + ##DATEFIRST + 5) % 7) + 1
by adding ##DATEFIRST value before the modulus operator it neglects the datefirst setting in your SQL engine.
The value 5 is to make Monday as the first day of the week
To make Sunday as the first day of the week add 6
to make saturday as the first day of the week then add 0
and so on the rest of the day weeks.

Perhaps not the most elegant, but instead of doing the subtraction you can just set it to 7 and then back to what ever it was after your DATEPART
SET DATEFIRST 7;
SELECT DATEPART(dw , GETDATE()) --6
SET DATEFIRST 3;
DECLARE #currentDatefirst int = ##DATEFIRST
SELECT ##DATEFIRST --3
SET DATEFIRST 7;
SELECT DATEPART(dw , GETDATE()) --6
SET DATEFIRST #currentDatefirst
SELECT ##DATEFIRST --3

Related

SQL - Group By Week to begin on a specific weekday without involving two transactions?

I am writing a query that returns the sum of rows for the last 10 weeks FRI-THURS.
It uses a group by to show the sum of each week:
WITH Vars (Friday) -- get current week Fridays Date
AS (
SELECT CAST(DATEADD(DAY,(13 - (##DATEFIRST + DATEPART(WEEKDAY,GETDATE())))%7,GETDATE()) AS DATE) As 'Friday'
)
SELECT datepart(week, DateField) AS WeekNum, COUNT(*) AS Counts
FROM Table
WHERE DateField >= DATEADD(week,-9, (SELECT Friday from Vars))
GROUP BY datepart(week, DateField)
ORDER BY WeekNum DESC
The problem is every week starts on Monday so the Group By doesn't group the dates on how I want it. I want a week to be defined as FRI-THURS.
One workaround to this is to use DATEFIRST. e.g:
SET DATEFIRST = 5; --set beginning of each week to Friday
WITH Vars (Friday) -- get current week Fridays Date
... rest of query
However due to limitations on the interface I am writing this query I cannot have two separate statements run. It needs to be one query with no semicolons.
How can I achieve this?
This should do it. First pre-compute once the StartingFriday of 9 weeks ago, rather than doing that for each row. Then compute the dfYear and dfWeek giving them alias-es, where their DateField is after the starting friday. Lastly, Count/GroupBy/OrderBy.
Declare #StartingFriday as date =
DATEADD(week,-9, (DATEADD(day, - ((Datepart(WEEKDAY,GETDATE()) +1) % 7) , GETDATE())) ) ;
SELECT dfYear, dfWeek, COUNT(*) AS Counts
FROM
(Select -- compute these here, and use alias in Select, GroupBy, OrderBy
(Datepart(Year,(DATEADD(day, - ((Datepart(WEEKDAY,DateField) +1) % 7) , DateField)) ) )as dfYear
,(Datepart(Week,(DATEADD(day, - ((Datepart(WEEKDAY,DateField) +1) % 7) , DateField)) ) )as dfWeek
From Table
WHERE #StartingFriday <= DateField
) as aa
group by dfYear, dfWeek
order by dfYear desc, dfWeek desc
-- we want the weeknum of the (Friday on or before the DateField)
-- the % (percent sign) is the math MODULO operator.
-- used to get back to the nearest Friday,
-- day= Fri Sat Sun Mon Tue Wed Thu
-- weekday= 6 7 1 2 3 4 5
-- plus 1 = 7 8 2 3 4 5 6
-- Modulo7= 0 1 2 3 4 5 6
-- which are the days to subtract from DateField
-- to get to its Friday start of its week.
I did some testing with this
declare #dt as date = '8/17/18';
select ((DATEPART(WEEKDAY,#dt) +1) % 7) as wd
,(DATEADD(day, - ((Datepart(WEEKDAY,#dt) +1) % 7) , #dt)) as Fri
,(Datepart(Week,(DATEADD(day, - ((Datepart(WEEKDAY,#dt) +1) % 7) , #dt)) ) )as wk
,DATEADD(week,-9, (DATEADD(day, - ((Datepart(WEEKDAY,#dt) +1) % 7) , #dt)) ) as StartingFriday

SQL Server : how do you exclude weekends using datediff?

I am still learning SQL so please bare that in mind. I have a query that returns me the average days for a specific range, although my range does not account for weekends & holidays. Holidays may be a little tricky but how do I exclude weekends from my range?
For example I have a range 02-01-18 to 02-15-18 where the datediff is 14 days, but how do I get SQL to identifying which days in that range were weekends and if they were to exclude them from my datediff?
My query is
SELECT
AVG(1.00 * DATEDIFF(DAY, xx, yy)) AS DayDiff
FROM
datebase1.dbo.table1
WHERE
MONTH(datecompleted) = MONTH(DATEADD(month, -1, current_timestamp))
AND YEAR(datecompleted) = YEAR(DATEADD(month, -1, current_timestamp))
AND ApprovalRequiredFrom = 'pp'
I do have a calendar I can source which tells me the date and the name of the day, but I want to avoid having to do this. I want to be able to exclude the weekends from my range to get me a more accurate result.
Thanks
To exclude weekends, you need to filter Saturdays and Sundays from both your comparing dates (xx and yy). To do so you use the DATEPART function with the WEEKDAY parameter. The result is a number from 1 to 7 indicating which day of the week it is.
-- 2018-01-01 is a monday
SELECT DATEPART(WEEKDAY, '2018-01-01')
-- Result: 2
The problem is that different database configurations have the date related to the value "1" changed, so maybe the day of week "1" means Sunday for some and Mondays for others. To unify this, you can change the default with SET DATEFIRST.
SET DATEFIRST 1 -- 1: Monday, 7: Sunday
-- 2018-01-01 is a monday
SELECT DATEPART(WEEKDAY, '2018-01-01')
-- Result: 1
Another solution that is datefirst agnostic is to use the ##DATEFIRST session value directly on your expression. The ##DATEFIRST holds the value we set on the SET DATEFIRST statement, or the database default. Please notice that the result is the same, even changing DATEFIRST.
SET DATEFIRST 7 -- 1: Sunday, 2: Monday
-- 2018-01-01 is a monday
SELECT (DATEPART(WEEKDAY, '2018-01-01') + ##DATEFIRST) % 7
-- Result: 2
SET DATEFIRST 1 -- 1: Monday, 7: Sunday
-- 2018-01-01 is a monday
SELECT (DATEPART(WEEKDAY, '2018-01-01') + ##DATEFIRST) % 7
-- Result: 2
For your example, you need to filter xx and yy dates to not be weekends. Add the following to your WHERE clause:
WHERE
--...
AND (DATEPART(WEEKDAY, xx) + ##DATEFIRST) % 7 NOT IN (0, 1)
AND (DATEPART(WEEKDAY, yy) + ##DATEFIRST) % 7 NOT IN (0, 1)
Because need to work with the old MySQL 5.1 server, just got a chance to try a "math" way to calculate no. of SAT / SUN to subtract from:
Note: MySQL's "weekday" function returns 0 for Mon, 5 for SAT and 6 for SUN, thus you see below SQL has some magic no. with 5 and 6.
Sample:
select floor((datediff (ed, st)+1) / 7)*2 /*complete week's weekends*/
+ case when floor((datediff (ed, st) +1) % 7) between 0 and 6 /*additional weekends besides complete weeks*/
then case when weekday(ed) >= weekday(st) then least(floor((datediff (ed, st) +1) % 7), greatest(least(6, weekday(ed)) - greatest(5, weekday(st)) + 1,0))
else least(floor((datediff (ed, st) +1) % 7), greatest(least(6, weekday(ed)+7) - greatest(5, weekday(st)) + 1,0)) end
else 0
end as num_of_sat_and_sun
from (select '2019-01-07' as st, '2019-01-12' as ed) x

SQL Server : selecting 'WEEKDAY' part by passing date in 'ddd' format

How I can retrieve 'WEEKDAY' part by passing 'ddd' format in SQL Server?
For example in if I pass 'tue' and the response will be 3, beacuse
1 - Sunday
2 - Monday
3 - Tuesday
... like that.
I get the 'WEEKDAY' of current date by executing following query.
select DATEPART(WEEKDAY, GETDATE())
This will return the day number for the given short day name honouring the current DATEFIRST setting.
--Example day name
DECLARE #Day CHAR(3) = 'Tue'
SELECT
DATEPART(WEEKDAY,DATEADD(DAY, Number, GETDATE())) DayNumber
FROM
master..spt_values N
WHERE
N.type = 'P' AND N.number BETWEEN 1 AND 7
AND DATENAME(WEEKDAY,DATEADD(DAY, Number, GETDATE())) LIKE #Day+'%'
You can try similar to this
SELECT *
FROM (
SELECT 1 PK ,'Sunday' Value UNION
SELECT 2,'Monday' UNION
SELECT 3,'Tuesday' UNION
...
SELECT 7,'Saturday'
) T WHERE T.[Value] LIKE '%tue%'
Try this:
SELECT datepart(weekday,getdate()), datename(dw, getdate())
This will return the weekday and name of that day considering ISO_WEEK rule
More detail here
this will give you the weekday no that is not dependant on ##datefirst or langauge setting
select [weekday] = case #weekday_name
when 'Sun' then (7 - ##datefirst + 0) % 7 + 1
when 'Mon' then (7 - ##datefirst + 1) % 7 + 1
when 'Tue' then (7 - ##datefirst + 2) % 7 + 1
when 'Wed' then (7 - ##datefirst + 3) % 7 + 1
when 'Thu' then (7 - ##datefirst + 4) % 7 + 1
when 'Fri' then (7 - ##datefirst + 5) % 7 + 1
when 'Sat' then (7 - ##datefirst + 6) % 7 + 1
end

Creating a date from Week of month and Day of week in SQL server

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
)

SET DATEFIRST in FUNCTION

I want to SET DATEFIRST in my function but it is not allowed.
SET DATEFIRST 1
I can add the code in a SP and call the SP from the function but I am not keen on doing that.
I can SET the DATEFIRST before I call my function but I am not keen on doing that as well.
Any other work around?
EDIT
Below is the code I want to use in my FUNCTION to return the total working days of the month. But I cant add this code into the FUNCTION because of my DATEFIRST
DECLARE #my int
DECLARE #myDeduct int
DECLARE #day INT
DECLARE #mydate DATETIME
DECLARE #TotalDays INT
SET #mydate = GETDATE()
SET #myDeduct = 0
IF (##DATEFIRST + DATEPART(DW, #mydate)) % 7 not in (0,1)
SET DateFirst 1 -- Set it monday=1 (value)
--Saturday and Sunday on the first and last day of a month will Deduct 1
IF (DATEPART(weekday,(DATEADD(dd,-(DAY(#mydate)-1),#mydate))) > 5)
SET #myDeduct = #myDeduct + 1
IF (DATEPART(weekday,(DATEADD(dd,-(DAY(DATEADD(mm,1,#mydate))),DATEADD(mm,1,#mydate)))) > 5)
SET #myDeduct = #myDeduct + 1
SET #my = day(DATEADD(dd,-(DAY(DATEADD(mm,1,#mydate))),DATEADD(mm,1,#mydate)))
Set #TotalDays = (select (((#my/7) * 5 + (#my%7)) - #myDeduct))
Select #TotalDays
Instead of
SET DATEFIRST 1
You can do
SELECT (DATEPART(weekday, GETDATE()) + ##DATEFIRST - 2) % 7 + 1
My usual workaround is to use "known-good" dates for my comparisons.
Say, for instance, that I need to check that a date is a saturday. Rather than relying on DATEFIRST or language settings (for using DATENAME), I instead say:
DATEPART(weekday,DateToCheck) = DATEPART(weekday,'20120714')
I know that 14th July 2012 was a Saturday, so I've performed the check without relying on any external settings.
The expression (DATEPART(weekday,DateToCheck) + ##DATEFIRST) % 7 will always produce the value 0 for Saturday, 1 for Sunday, 2 for Monday, etc.
So, I'd advise you to create a table:
CREATE TABLE WorkingDays (
NormalisedDay int not null,
DaysInMonth int not null,
WorkingDays int not null
)
Populating this table is a one off exercise. NormalisedDay would be the value computed by the expression I've given above.
To compute the DaysInMonth given a particular date, you can use the expression:
DATEDIFF(day,
DATEADD(month,DATEDIFF(month,0,DateToCheck),0),
DATEADD(month,DATEDIFF(month,'20010101',DateToCheck),'20010201'))
Now all your function has to do is look up the value in the table.
(Of course, all of the rows where DaysInMonth is 28 will have 20 as their result. It's only the rows for 29,30 and 31 which need a little work to produce)
Alternative way is to explicitly specify the first day of week value as parameter and avoid depending on ##DATEFIRST setting. You can use the following formula to achieve that in your function:
(DATEPART(dw, GETDATE()) + ##DATEFIRST + 6 - #WeekStartDay) % 7 + 1
where #WeekStartDay is the first day of the week you want for your system (from 1 to 7 which means from Monday to Sunday).
I have wrapped it into below function so we can reuse it easily:
CREATE FUNCTION [dbo].[GetDayInWeek](#InputDateTime DATETIME, #WeekStartDay INT)
RETURNS INT
AS
BEGIN
--Note: #WeekStartDay is number from [1 - 7] which is from Monday to Sunday
RETURN (DATEPART(dw, #InputDateTime) + ##DATEFIRST + 6 - #WeekStartDay) % 7 + 1
END
Example usage:
GetDayInWeek('2019-02-04 00:00:00', 1)
It is equivalent to following (but independent to DATEFIRST setting):
SET DATEFIRST 1
DATEPART(dw, '2019-02-04 00:00:00')
If you need Monday as firstday follow this code snippet
declare #MyDate datetime = getdate()
select CASE WHEN DATEPART(DW,#MyDate) = 1
THEN 7
WHEN DATEPART(DW,#MyDate) <= 7
THEN DATEPART(DW,#MyDate) - 1
END

Resources