Query to find the start day of the week as Monday

  • Jeff Moden (12/4/2012)


    Then substitute a 0 for Michael's -53690. 😀

    That, not withstanding, it's always the "edge cases" that cause panic stricken calls at 3 in the morning.

    If I have to worry about dates that far back, I'll convert everything to datetme2 since this is a SQL Server 2008 forum. That way I can go back further. Only problem then is what calendar do you really use? Heck, if I remember correctly we used different calendars into the early 1900's didn't we?

  • sgmunson (12/4/2012)


    Lynn Pettis (12/4/2012)


    Compare:

    DECLARE @date DATE = '20121202';

    SELECT

    CASE WHEN datename(dw,@date) <> 'Sunday' THEN CONVERT(varchar(10), DATEADD(WK, DATEDIFF(WK, 0, @date), 0),

    103) ELSE CONVERT(varchar(10), DATEADD(WK, DATEDIFF(WK, 0, @date), - 7), 103) END

    go

    DECLARE @date DATE = '20121203';

    SELECT

    CASE WHEN datename(dw,@date) <> 'Sunday' THEN CONVERT(varchar(10), DATEADD(WK, DATEDIFF(WK, 0, @date), 0),

    103) ELSE CONVERT(varchar(10), DATEADD(WK, DATEDIFF(WK, 0, @date), - 7), 103) END

    go

    DECLARE @date DATE = '20121202';

    select dateadd(wk, datediff(wk, 0, DATEADD(dd,-1,@Date)), 0);

    GO

    DECLARE @date DATE = '20121203';

    select dateadd(wk, datediff(wk, 0, DATEADD(dd,-1,@Date)), 0);

    GO

    Yup, you have it right. I didn't get to see your post before I posted my answer. I'd sure like to understand a bit more about why that works. It's using the number of weeks since date zero through yesterday, then re-adding the same number of weeks to date zero, which suggests that perhaps the week is defined based on Monday ? Or is it Sunday, and thus why yesterday is used instead of today?

    Why the code I posted returns this weeks Monday date is because 1900-01-01 is a Monday. I subtracted 1 in the calculations on tthis thread because the OP waanted the Sunday prior to the Monday date in the previous week. If it is Monday, and I subtract 1 from it and perform the the calculation used in my posts, it returns the Monday of that week.

    I would suggest playing with the date calculatioins and see what other tidbits you might discover. If you go to my blog on ssc, you will find more date calculations there as well.

  • Lynn Pettis (12/4/2012)


    Jeff Moden (12/4/2012)


    Then substitute a 0 for Michael's -53690. 😀

    That, not withstanding, it's always the "edge cases" that cause panic stricken calls at 3 in the morning.

    If I have to worry about dates that far back, I'll convert everything to datetme2 since this is a SQL Server 2008 forum. That way I can go back further. Only problem then is what calendar do you really use? Heck, if I remember correctly we used different calendars into the early 1900's didn't we?

    Since not all countries adopted the Gregorian calendar at the same time and the Julian calendar (which was mathematically incorrect compared to Earth's orbit) was in effect before that, I suspect a simple switch to DATETIME2 would produce some good inaccuracies.

    Just to be sure, I wasn't challenging you. I meant only to explain that Michaeal does with dates what a lot of us (including you and me) do with a lot of other things... make the code mostly bullet-proof in the face of future unknown domains and scalability especially since we don't know who's going to use our code for what once they find it.

    Still, I'm like you in that I prefer to use the 0 date reference for a couple of reasons. It's shorter to type, requires literally "0" memory for me to remember, and allows for negative math without running into the "bottom" of the allowed timeline.

    Shifting gears, I've always wanted to know what the performance of the integer trick that Micheal (I use it too because it's easy for me to remember) uses for such week calculation and the methods that avoid direct math on dates. Admittedly, both methods are very fast and it takes millions of rows (which I work with in about a 3rd of all the queries I have to write) to actually see a difference but every millisecond counts if you have to do such a thing with many columns across many tables for such a thing. I've long suspected that the integer method would squeak past (9% faster in this case) the 3 part method but have never taken the time to prove it until now.

    Here's the test code. No... I don't trust SET STATISTICS any more.

    --===== Conditionally drop the test table to make reruns in SSMS easier.

    IF OBJECT_ID('tempdb..#SomeTable','U') IS NOT NULL

    DROP TABLE #SomeTable

    ;

    --===== Build and ppulate the test table on-the-fly.

    SELECT TOP 5000000

    SomeDate = DATEADD(dd,ABS(CHECKSUM(NEWID()))%DATEDIFF(dd,'1900','2000'),'1900')

    INTO #SomeTable

    FROM master.sys.all_columns ac1

    CROSS JOIN master.sys.all_columns ac2

    ;

    GO

    PRINT '--===== Performance Baseline Simple Select Method =====--'

    DBCC FREEPROCCACHE;

    DBCC DROPCLEANBUFFERS;

    DECLARE @Bitbucket DATETIME,

    @StartTime DATETIME;

    SELECT @StartTime = GETDATE();

    SELECT @Bitbucket = SomeDate

    FROM #SomeTable

    ;

    PRINT DATEDIFF(ms,@StartTime, GETDATE());

    GO

    PRINT '--===== Performance -53690 Integer Math Function Method =====--'

    DBCC FREEPROCCACHE;

    DBCC DROPCLEANBUFFERS;

    DECLARE @Bitbucket DATETIME,

    @StartTime DATETIME;

    SELECT @StartTime = GETDATE();

    SELECT @Bitbucket = DATEADD(dd,(DATEDIFF(dd,-53690,SomeDate)/7)*7,-53690)

    FROM #SomeTable

    ;

    PRINT DATEDIFF(ms,@StartTime, GETDATE());

    GO

    PRINT '--===== Performance 0 Integer Math Function Method =====--'

    DBCC FREEPROCCACHE;

    DBCC DROPCLEANBUFFERS;

    DECLARE @Bitbucket DATETIME,

    @StartTime DATETIME;

    SELECT @StartTime = GETDATE();

    SELECT @Bitbucket = DATEADD(dd,(DATEDIFF(dd,0,SomeDate)/7)*7,0)

    FROM #SomeTable

    ;

    PRINT DATEDIFF(ms,@StartTime, GETDATE());

    GO

    PRINT '--===== Performance 3 DATETIME Function Method =====--'

    DBCC FREEPROCCACHE;

    DBCC DROPCLEANBUFFERS;

    DECLARE @Bitbucket DATETIME,

    @StartTime DATETIME;

    SELECT @StartTime = GETDATE();

    SELECT @Bitbucket = DATEADD(wk, DATEDIFF(wk, 0, DATEADD(dd,-1,SomeDate)), 0)

    FROM #SomeTable

    ;

    PRINT DATEDIFF(ms,@StartTime, GETDATE());

    GO

    Here are the run results on my humble 2005 desktop box.

    --===== Performance Baseline Simple Select Method =====--

    DBCC execution completed. If DBCC printed error messages, contact your system administrator.

    DBCC execution completed. If DBCC printed error messages, contact your system administrator.

    2850

    --===== Performance -53690 Integer Math Function Method =====--

    DBCC execution completed. If DBCC printed error messages, contact your system administrator.

    DBCC execution completed. If DBCC printed error messages, contact your system administrator.

    4830

    --===== Performance 0 Integer Math Function Method =====--

    DBCC execution completed. If DBCC printed error messages, contact your system administrator.

    DBCC execution completed. If DBCC printed error messages, contact your system administrator.

    4833

    --===== Performance 3 DATETIME Function Method =====--

    DBCC execution completed. If DBCC printed error messages, contact your system administrator.

    DBCC execution completed. If DBCC printed error messages, contact your system administrator.

    5263

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.
    "Change is inevitable... change for the better is not".

    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)
    Intro to Tally Tables and Functions

  • Thanks all for ur reply!

  • Jeff Moden (12/4/2012)


    Lynn Pettis (12/4/2012)


    Jeff Moden (12/4/2012)


    Then substitute a 0 for Michael's -53690. 😀

    That, not withstanding, it's always the "edge cases" that cause panic stricken calls at 3 in the morning.

    If I have to worry about dates that far back, I'll convert everything to datetme2 since this is a SQL Server 2008 forum. That way I can go back further. Only problem then is what calendar do you really use? Heck, if I remember correctly we used different calendars into the early 1900's didn't we?

    Since not all countries adopted the Gregorian calendar at the same time and the Julian calendar (which was mathematically incorrect compared to Earth's orbit) was in effect before that, I suspect a simple switch to DATETIME2 would produce some good inaccuracies.

    Just to be sure, I wasn't challenging you. I meant only to explain that Michaeal does with dates what a lot of us (including you and me) do with a lot of other things... make the code mostly bullet-proof in the face of future unknown domains and scalability especially since we don't know who's going to use our code for what once they find it.

    Still, I'm like you in that I prefer to use the 0 date reference for a couple of reasons. It's shorter to type, requires literally "0" memory for me to remember, and allows for negative math without running into the "bottom" of the allowed timeline.

    Shifting gears, I've always wanted to know what the performance of the integer trick that Micheal (I use it too because it's easy for me to remember) uses for such week calculation and the methods that avoid direct math on dates. Admittedly, both methods are very fast and it takes millions of rows (which I work with in about a 3rd of all the queries I have to write) to actually see a difference but every millisecond counts if you have to do such a thing with many columns across many tables for such a thing. I've long suspected that the integer method would squeak past (9% faster in this case) the 3 part method but have never taken the time to prove it until now.

    Here's the test code. No... I don't trust SET STATISTICS any more.

    --===== Conditionally drop the test table to make reruns in SSMS easier.

    IF OBJECT_ID('tempdb..#SomeTable','U') IS NOT NULL

    DROP TABLE #SomeTable

    ;

    --===== Build and ppulate the test table on-the-fly.

    SELECT TOP 5000000

    SomeDate = DATEADD(dd,ABS(CHECKSUM(NEWID()))%DATEDIFF(dd,'1900','2000'),'1900')

    INTO #SomeTable

    FROM master.sys.all_columns ac1

    CROSS JOIN master.sys.all_columns ac2

    ;

    GO

    PRINT '--===== Performance Baseline Simple Select Method =====--'

    DBCC FREEPROCCACHE;

    DBCC DROPCLEANBUFFERS;

    DECLARE @Bitbucket DATETIME,

    @StartTime DATETIME;

    SELECT @StartTime = GETDATE();

    SELECT @Bitbucket = SomeDate

    FROM #SomeTable

    ;

    PRINT DATEDIFF(ms,@StartTime, GETDATE());

    GO

    PRINT '--===== Performance -53690 Integer Math Function Method =====--'

    DBCC FREEPROCCACHE;

    DBCC DROPCLEANBUFFERS;

    DECLARE @Bitbucket DATETIME,

    @StartTime DATETIME;

    SELECT @StartTime = GETDATE();

    SELECT @Bitbucket = DATEADD(dd,(DATEDIFF(dd,-53690,SomeDate)/7)*7,-53690)

    FROM #SomeTable

    ;

    PRINT DATEDIFF(ms,@StartTime, GETDATE());

    GO

    PRINT '--===== Performance 0 Integer Math Function Method =====--'

    DBCC FREEPROCCACHE;

    DBCC DROPCLEANBUFFERS;

    DECLARE @Bitbucket DATETIME,

    @StartTime DATETIME;

    SELECT @StartTime = GETDATE();

    SELECT @Bitbucket = DATEADD(dd,(DATEDIFF(dd,0,SomeDate)/7)*7,0)

    FROM #SomeTable

    ;

    PRINT DATEDIFF(ms,@StartTime, GETDATE());

    GO

    PRINT '--===== Performance 3 DATETIME Function Method =====--'

    DBCC FREEPROCCACHE;

    DBCC DROPCLEANBUFFERS;

    DECLARE @Bitbucket DATETIME,

    @StartTime DATETIME;

    SELECT @StartTime = GETDATE();

    SELECT @Bitbucket = DATEADD(wk, DATEDIFF(wk, 0, DATEADD(dd,-1,SomeDate)), 0)

    FROM #SomeTable

    ;

    PRINT DATEDIFF(ms,@StartTime, GETDATE());

    GO

    Here are the run results on my humble 2005 desktop box.

    --===== Performance Baseline Simple Select Method =====--

    DBCC execution completed. If DBCC printed error messages, contact your system administrator.

    DBCC execution completed. If DBCC printed error messages, contact your system administrator.

    2850

    --===== Performance -53690 Integer Math Function Method =====--

    DBCC execution completed. If DBCC printed error messages, contact your system administrator.

    DBCC execution completed. If DBCC printed error messages, contact your system administrator.

    4830

    --===== Performance 0 Integer Math Function Method =====--

    DBCC execution completed. If DBCC printed error messages, contact your system administrator.

    DBCC execution completed. If DBCC printed error messages, contact your system administrator.

    4833

    --===== Performance 3 DATETIME Function Method =====--

    DBCC execution completed. If DBCC printed error messages, contact your system administrator.

    DBCC execution completed. If DBCC printed error messages, contact your system administrator.

    5263

    Jeff,

    I know you weren't challenging me, that is part of the problem with text based communication, you miss out on tone of voice and body language for thos subtle clues as to the real meaning of what is said.

    With that, however, you are constantly challenging me to become better at what I do. In another thread I said you were the Mentor of the Year. That's wrong. I should have said the Mentor of the Years as you you have done this for many years. I can honestly say you have been a big part of my learning since 2005. You and several others on ssc to be sure. I just hope I have been able to help others as much as you have.

  • The reason I use -53690, instead of 0 is simple: It produces correct results for the entire range of possible datetime values.

    You can view the difference in the query results below. The calculation is wrong for dates before 1900-01-01 when you use 0.

    FYI, -53690 = 1753-01-01, the earliest possible datetime value, so if it makes it easier to remember, you can use that as illustrated in the query below.

    In case you wonder why datetime starts 1753-01-01: England and its colonies adopted the Gregorian calendar in mid 1752 and 1753 was the first full year that used the Gregorian calendar. Other countries adopted it earlier or later.

    select

    SomeDate,

    [Weekday]= left(datename(dw,SomeDate),9),

    [Date -53690]=

    dateadd(dd,(datediff(dd,-53690,SomeDate)/7)*7,-53690),

    [Date 17530101]=

    dateadd(dd,(datediff(dd,'17530101',SomeDate)/7)*7,'17530101'),

    [Date 0]=

    dateadd(dd,(datediff(dd,0,SomeDate)/7)*7,0)

    from

    (-- Test Data

    select SomeDate = convert(datetime,'18900504')union all

    select SomeDate = '18900505'union all

    select SomeDate = '18900506'union all

    select SomeDate = '18900507'union all

    select SomeDate = '18900508'union all

    select SomeDate = '18900509'union all

    select SomeDate = '18900510'union all

    select SomeDate = '18900511'union all

    select SomeDate = '18900512'union all

    select SomeDate = '18900513'union all

    select SomeDate = '18900514'

    ) a

    order by

    a.SomeDate

    Results:

    SomeDate Weekday Date -53690 Date 17530101 Date 0

    ----------------------- --------- ----------------------- ----------------------- -----------------------

    1890-05-04 00:00:00.000 Sunday 1890-04-28 00:00:00.000 1890-04-28 00:00:00.000 1890-05-05 00:00:00.000

    1890-05-05 00:00:00.000 Monday 1890-05-05 00:00:00.000 1890-05-05 00:00:00.000 1890-05-05 00:00:00.000

    1890-05-06 00:00:00.000 Tuesday 1890-05-05 00:00:00.000 1890-05-05 00:00:00.000 1890-05-12 00:00:00.000

    1890-05-07 00:00:00.000 Wednesday 1890-05-05 00:00:00.000 1890-05-05 00:00:00.000 1890-05-12 00:00:00.000

    1890-05-08 00:00:00.000 Thursday 1890-05-05 00:00:00.000 1890-05-05 00:00:00.000 1890-05-12 00:00:00.000

    1890-05-09 00:00:00.000 Friday 1890-05-05 00:00:00.000 1890-05-05 00:00:00.000 1890-05-12 00:00:00.000

    1890-05-10 00:00:00.000 Saturday 1890-05-05 00:00:00.000 1890-05-05 00:00:00.000 1890-05-12 00:00:00.000

    1890-05-11 00:00:00.000 Sunday 1890-05-05 00:00:00.000 1890-05-05 00:00:00.000 1890-05-12 00:00:00.000

    1890-05-12 00:00:00.000 Monday 1890-05-12 00:00:00.000 1890-05-12 00:00:00.000 1890-05-12 00:00:00.000

    1890-05-13 00:00:00.000 Tuesday 1890-05-12 00:00:00.000 1890-05-12 00:00:00.000 1890-05-19 00:00:00.000

    1890-05-14 00:00:00.000 Wednesday 1890-05-12 00:00:00.000 1890-05-12 00:00:00.000 1890-05-19 00:00:00.000

    Note that if you are not starting the week on a Monday, you may need additional code for the edge conditions:

    select

    SomeDate,

    [Weekday]= left(datename(dw,SomeDate),9),

    [Sunday]=

    case

    -- There is no possible datetime Sunday before 1753-01-07

    when SomeDate < '17530107' then null

    else

    dateadd(dd,(datediff(dd,'17530107',SomeDate)/7)*7,'17530107')

    end

    from

    (-- Test Data

    select SomeDate = convert(datetime,'17530101')union all

    select SomeDate = convert(datetime,'17530102')union all

    select SomeDate = convert(datetime,'17530103')union all

    select SomeDate = convert(datetime,'17530104')union all

    select SomeDate = convert(datetime,'17530105')union all

    select SomeDate = convert(datetime,'17530106')union all

    select SomeDate = convert(datetime,'17530107')union all

    select SomeDate = convert(datetime,'17530108')

    ) a

    order by

    a.SomeDate

    Results:

    SomeDate Weekday Sunday

    ----------------------- --------- -----------------------

    1753-01-01 00:00:00.000 Monday NULL

    1753-01-02 00:00:00.000 Tuesday NULL

    1753-01-03 00:00:00.000 Wednesday NULL

    1753-01-04 00:00:00.000 Thursday NULL

    1753-01-05 00:00:00.000 Friday NULL

    1753-01-06 00:00:00.000 Saturday NULL

    1753-01-07 00:00:00.000 Sunday 1753-01-07 00:00:00.000

    1753-01-08 00:00:00.000 Monday 1753-01-07 00:00:00.000

    Sample of code to handle edge conditions for any start day of week:

    select

    a.DATE,

    [DayOfWeek] = left(datename(dw,a.DATE),9),

    Mon =dateadd(dd,(datediff(dd,-53690,a.DATE)/7)*7,-53690),

    Tue =case when a.DATE >= -53689 then

    dateadd(dd,(datediff(dd,-53689,a.DATE)/7)*7,-53689) end,

    Wed =case when a.DATE >= -53688 then

    dateadd(dd,(datediff(dd,-53688,a.DATE)/7)*7,-53688) end,

    Thu =case when a.DATE >= -53687 then

    dateadd(dd,(datediff(dd,-53687,a.DATE)/7)*7,-53687) end,

    Fri =case when a.DATE >= -53686 then

    dateadd(dd,(datediff(dd,-53686,a.DATE)/7)*7,-53686) end,

    Sat =case when a.DATE >= -53685 then

    dateadd(dd,(datediff(dd,-53685,a.DATE)/7)*7,-53685) end,

    Sun =case when a.DATE >= -53684 then

    dateadd(dd,(datediff(dd,-53684,a.DATE)/7)*7,-53684) end

    from

    (-- Test Data

    select Date = convert(datetime,'17530101')union all

    select Date = convert(datetime,'17530102')union all

    select Date = convert(datetime,'17530103')union all

    select Date = convert(datetime,'17530104')union all

    select Date = convert(datetime,'17530105')union all

    select Date = convert(datetime,'17530106')union all

    select Date = convert(datetime,'17530107')union all

    select Date = convert(datetime,'17530108')union all

    select Date = convert(datetime,'99991231')

    ) a

    order by

    a.DATE

    This function will give the correct results for any datetime value for any day of the week you want to start with:

    Start of Week Function:

    http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=47307

  • select cast(case datepart(dw,Loaddate)

    when 1 then dateadd(ww,-0,Loaddate-6)

    when 2 then dateadd(ww,-0,Loaddate)

    when 3 then dateadd(ww,-0,Loaddate-1)

    when 4 then dateadd(ww,-0,Loaddate-2)

    when 5 then dateadd(ww,-0,Loaddate-3)

    when 6 then dateadd(ww,-0,Loaddate-4)

    when 7 then dateadd(ww,-0,Loaddate-5)

    end as date)

  • Ulysses_Infinity (12/6/2012)


    select cast(case datepart(dw,Loaddate)

    when 1 then dateadd(ww,-0,Loaddate-6)

    when 2 then dateadd(ww,-0,Loaddate)

    when 3 then dateadd(ww,-0,Loaddate-1)

    when 4 then dateadd(ww,-0,Loaddate-2)

    when 5 then dateadd(ww,-0,Loaddate-3)

    when 6 then dateadd(ww,-0,Loaddate-4)

    when 7 then dateadd(ww,-0,Loaddate-5)

    end as date)

    This only produces correct results when the setting of DATEFIRST is 7 (Sunday)

    SET DATEFIRST 7

  • Lynn Pettis (12/5/2012)


    I know you weren't challenging me, that is part of the problem with text based communication, you miss out on tone of voice and body language for thos subtle clues as to the real meaning of what is said.

    With that, however, you are constantly challenging me to become better at what I do. In another thread I said you were the Mentor of the Year. That's wrong. I should have said the Mentor of the Years as you you have done this for many years. I can honestly say you have been a big part of my learning since 2005. You and several others on ssc to be sure. I just hope I have been able to help others as much as you have.

    Gosh. :blush: Thanks, Lynn. I can only hope to live up to such a high compliment.

    I agree. Text based communication is a bit tough. That's part of the reason why I wanted to make sure you knew I wasn't challenging but rather just providing info.

    Keep up the good work! You're definitely a heavy hitter and definitely one of the good guys. You've helped more people than I think you realize.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.
    "Change is inevitable... change for the better is not".

    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)
    Intro to Tally Tables and Functions

Viewing 9 posts - 16 through 23 (of 23 total)

You must be logged in to reply to this topic. Login to reply