7

SQL Server, trying to get day of week via a deterministic UDF.

Im sure this must be possible, but cant figure it out.

UPDATE: SAMPLE CODE..

CREATE VIEW V_Stuff WITH SCHEMABINDING AS 
SELECT    
MD.ID, 
MD.[DateTime]
...
        dbo.FN_DayNumeric_DateTime(MD.DateTime) AS [Day], 
        dbo.FN_TimeNumeric_DateTime(MD.DateTime) AS [Time], 
...
FROM       {SOMEWHERE}
GO
CREATE UNIQUE CLUSTERED INDEX V_Stuff_Index ON V_Stuff (ID, [DateTime])
GO
David
  • 8,340
  • 7
  • 49
  • 71

10 Answers10

8

Ok, i figured it..

CREATE FUNCTION [dbo].[FN_DayNumeric_DateTime] 
(@DT DateTime)
RETURNS INT WITH SCHEMABINDING
AS 
BEGIN
DECLARE @Result int 
DECLARE  @FIRST_DATE        DATETIME
SELECT @FIRST_DATE = convert(DATETIME,-53690+((7+5)%7),112)
SET  @Result = datediff(dd,dateadd(dd,(datediff(dd,@FIRST_DATE,@DT)/7)*7,@FIRST_DATE), @DT)
RETURN (@Result)
END
GO
David
  • 8,340
  • 7
  • 49
  • 71
6

Slightly similar approach to aforementioned solution, but just a one-liner that could be used inside a function or inline for computed column.

Assumptions:

  1. You don't have dates before 1899-12-31 (which is a Sunday)
  2. You want to imitate @@datefirst = 7
  3. @dt is smalldatetime, datetime, date, or datetime2 data type

If you'd rather it be different, change the date '18991231' to a date with the weekday that you'd like to equal 1. The convert() function is key to making the whole thing work - cast does NOT do the trick:

((datediff(day, convert(datetime, '18991231', 112), @dt) % 7) + 1)

scottE
  • 246
  • 2
  • 2
  • 1
    if you stick to the 1899-12-31 date you can even skip the convert `((datediff(day, -1, @dt) % 7) + 1)` – Lucas Ayala Sep 21 '16 at 10:50
  • This works beautifully in a function. Until you try to alter a table and set the computed column, then you get "cannot be persisted because the column is non-deterministic.". This is because the datediff converts the string to a datetime object - and this conversion kills the deterministic ability to use this in a persistent column... unfortunate. Use this instead (replace the "at" with @,..) : SET (at)datediff = (DATEDIFF(dd, -1, convert(datetime, (at)dbdate, 112)) % 7)+1; – T.S Apr 11 '19 at 14:53
2

I know this post is way-super-old, but I was trying to do a similar thing and came up with a different solution and figured I'd post for posterity. Plus I did some searching around and did not find much content on this question.

In my case, I was trying to use a computed column PERSISTED, which requires the calculation to be deterministic. The calculation I used is:

datediff(dd,'2010-01-03',[DateColumn]) % 7 + 1

The idea is to figure out a known Sunday that you know will occur before any possible date in your table (in this case, Jan 3 2010), then calculate the modulo 7 + 1 of the number of days since that Sunday.

The problem is that including a literal date in the function call is enough to mark it as non-deterministic. You can work around that by using the integer 0 to represent the epoch, which for SQL Server is Jan 1st, 1900, a Sunday.

datediff(dd,0,[DateColumn]) % 7 + 1

The +1 just makes the result work the same as datepart(dw,[datecolumn]) when datefirst is set to 7 (default for US), which sets Sunday to 1, Monday to 2, etc

I can also use this in conjunction with case [thatComputedColumn] when 1 then 'Sunday' when 2 then 'Monday' ... etc. Wordier, but deterministic, which was a requirement in my environs.

Tmdean
  • 9,108
  • 43
  • 51
liver.larson
  • 164
  • 8
  • this was an interesting idea, but doesn't work. in my tests, i had to make your `0` a `4`. i'm guessing this has to do with the first day of this year, which was on a Wednesday. i'm not sure how this worked in 2013 when you wrote it because that year started on a Tuesday. @scottE's answer accounts for this http://stackoverflow.com/a/5109557/444382 – Brad Oct 29 '14 at 15:35
  • according to the definition of deterministic (http://msdn.microsoft.com/en-us/library/ms178091.aspx) this solution is deterministic. The reason you probably got a different result was your database setting firstdate (http://msdn.microsoft.com/en-us/library/ms178091.aspx). You cannot make a calculated column persisted if it is not deterministic, and this calculation can be persisted – liver.larson Oct 29 '14 at 20:29
  • you state in your solution that you're assuming `@@datefirst` of 7 which is the same as me, so i'm not sure what's causing this to not work for me. – Brad Oct 30 '14 at 15:48
  • I think the confusion was that liver had mixed up @@datefirst being 7 and 1. I've updated the answer with a little more clarification and an explanation how it works. I also changed it slightly as I think using the epoch for this is a bit too magical. You can just pick any date from the past that falls on your desired @datefirst and that will occur before all possible dates that you will use. – Tmdean Apr 10 '15 at 23:01
  • I think this is the best answer, by the way. It's pretty understandable and doesn't rely on magic numbers. – Tmdean Apr 10 '15 at 23:03
  • After further experimentation, I've realized that you need to use the epoch for it to be non-deterministic. – Tmdean Apr 10 '15 at 23:15
  • 1
    On my MSSQL instance, with `@@datefirst = 7`, I had to use `((datediff(dd, 0, datecolumn) + 1) % 7) + 1` to get the same result as `datepart(weekday, datecolumn)`. – Ed Avis May 23 '16 at 09:59
  • 1
    Ed Avis, that's because the answer incorrectly states that Jan 1, 1990 was a Sunday. It was a Monday. Just do a `SELECT DATEPART(dw,'1/1/1900')` and see for yourself. I hope people reading this answer aren't using the "official" answer, and instead use your corrected formula. – Speednet Mar 24 '17 at 23:37
1

Taken from Deterministic scalar function to get week of year for a date

;
with 
Dates(DateValue) as 
(
    select cast('2000-01-01' as date)
    union all 
    select dateadd(day, 1, DateValue) from Dates where DateValue < '2050-01-01'
)
select 
    year(DateValue) * 10000 + month(DateValue) * 100 + day(DateValue) as DateKey, DateValue,        
    datediff(day, dateadd(week, datediff(week, 0, DateValue), 0), DateValue) + 2 as DayOfWeek,
    datediff(week, dateadd(month, datediff(month, 0, DateValue), 0), DateValue) + 1 as WeekOfMonth,
    datediff(week, dateadd(year, datediff(year, 0, DateValue), 0), DateValue) + 1 as WeekOfYear
    from Dates option (maxrecursion 0)
Community
  • 1
  • 1
Irawan Soetomo
  • 1,315
  • 14
  • 35
0

There is an already built-in function in sql to do it:

SELECT DATEPART(weekday, '2009-11-11')

EDIT: If you really need deterministic UDF:

CREATE FUNCTION DayOfWeek(@myDate DATETIME ) 
RETURNS int
AS
BEGIN
RETURN DATEPART(weekday, @myDate)
END
GO
SELECT dbo.DayOfWeek('2009-11-11')

EDIT again: this is actually wrong, as DATEPART(weekday) is not deterministic.

UPDATE: DATEPART(weekday) is non-deterministic because it relies on DATEFIRST (source).
You can change it with SET DATEFIRST but you can't call it inside a stored function.

I think the next step is to make your own implementation, using your preferred DATEFIRST inside it (and not considering it at all, using for example Monday as first day).

Alex Bagnolini
  • 21,990
  • 3
  • 41
  • 41
0

The proposed solution has one problem - it returns 0 for Saturdays. Assuming that we're looking for something compatible with DATEPART(WEEKDAY) this is an issue.

Nothing a simple CASE statement won't fix, though.

SchmitzIT
  • 9,227
  • 9
  • 65
  • 92
0

Make a function, and have @dbdate varchar(8) as your input variable.

Have it return the following:

RETURN (DATEDIFF(dd, -1, convert(datetime, @dbdate, 112)) % 7)+1;

The value 112 is the sql style YYYYMMDD.

This is deterministic because the datediff does not receive a string input, if it were to receive a string it would no longer work because it internally converts it to a datetime object. Which is not deterministic.

T.S
  • 355
  • 4
  • 18
-1

Not sure what you are looking for, but if this is part of a website, try this php function from http://php.net/manual/en/function.date.php

function weekday($fyear, $fmonth, $fday) //0 is monday
{
  return (((mktime ( 0, 0, 0, $fmonth, $fday, $fyear) - mktime ( 0, 0, 0, 7, 17,   2006))/(60*60*24))+700000) % 7;
}
Martin Andersson
  • 1,801
  • 4
  • 21
  • 25
-2

The day of the week? Why don't you just use DATEPART?

DATEPART(weekday, YEAR_DATE)
Maximilian Mayerl
  • 11,253
  • 2
  • 33
  • 40
-2

Can't you just select it with something like:

SELECT DATENAME(dw, GETDATE());
dnagirl
  • 20,196
  • 13
  • 80
  • 123