Отримати перший день тижня в SQL Server


97

Я намагаюся згрупувати записи за тижнями, зберігаючи зведену дату як перший день тижня. Однак стандартний прийом, який я використовую для округлення дат, здається, не працює коректно з тижнями (хоча він працює з днями, місяцями, роками, кварталами та будь-якими іншими часовими рамками, до яких я його застосовував).

Ось SQL:

select "start_of_week" = dateadd(week, datediff(week, 0, getdate()), 0);

Це повертається 2011-08-22 00:00:00.000, це понеділок, а не неділя. Вибір @@datefirstповернень 7, який є кодом неділі, тому сервер налаштований правильно, наскільки мені відомо.

Я можу обійти це досить легко, змінивши наведений вище код на:

select "start_of_week" = dateadd(week, datediff(week, 0, getdate()), -1);

Але той факт, що я маю зробити такий виняток, мене трохи засмучує. Крім того, вибачення, якщо це питання-дублікат. Я знайшов кілька супутніх питань, але жодного, що конкретно стосувався б цього аспекту.


9
(@@DATEFIRST + DATEPART(DW, @SomeDate)) % 7залишається незмінним незалежно від @@datefirstналаштування, я думаю. З понеділка = 2.
Мартін Сміт

Відповіді:


149

Щоб відповісти, чому ви отримуєте понеділок, а не неділю:

Ви додаєте кількість тижнів до дати 0. Що таке дата 0? 1900-01-01. Яким був день 1900-01-01? Понеділок. Отже, у своєму коді ви говорите, скільки тижнів минуло з понеділка, 1 січня 1900 року? Назвемо це [n]. Гаразд, тепер додайте [n] тижнів до понеділка, 1 січня 1900 року. Ви не повинні дивуватися, що це закінчується понеділком. DATEADDне уявляє, що ви хочете додати тижні, але лише доки не дійдете до неділі, це просто додавання 7 днів, потім додавання ще 7 днів, ... так само, як DATEDIFFрозпізнає лише межі, які були перетнуті. Наприклад, вони обидва повертають 1, хоча деякі люди скаржаться на те, що повинна бути вбудована якась розумна логіка для округлення вгору або вниз:

SELECT DATEDIFF(YEAR, '2010-01-01', '2011-12-31');
SELECT DATEDIFF(YEAR, '2010-12-31', '2011-01-01');

Щоб відповісти, як отримати неділю:

Якщо ви хочете неділю, виберіть базову дату, яка не є понеділком, а неділею. Наприклад:

DECLARE @dt DATE = '1905-01-01';
SELECT [start_of_week] = DATEADD(WEEK, DATEDIFF(WEEK, @dt, CURRENT_TIMESTAMP), @dt);

Це не порушиться, якщо ви зміните DATEFIRSTналаштування (або ваш код працює для користувача з іншим налаштуванням) - за умови, що ви все одно хочете неділю незалежно від поточного налаштування. Якщо ви хочете, щоб ці дві відповіді були джив, тоді вам слід використовувати функцію, яка залежить від DATEFIRSTналаштування, наприклад

SELECT DATEADD(DAY, 1-DATEPART(WEEKDAY, CURRENT_TIMESTAMP), CURRENT_TIMESTAMP);

Тож якщо ви зміните DATEFIRSTналаштування на понеділок, вівторок, що у вас буде, поведінка зміниться. Залежно від того, яку поведінку ви хочете, ви можете використовувати одну з таких функцій:

CREATE FUNCTION dbo.StartOfWeek1 -- always a Sunday
(
    @d DATE
)
RETURNS DATE
AS
BEGIN
    RETURN (SELECT DATEADD(WEEK, DATEDIFF(WEEK, '19050101', @d), '19050101'));
END
GO

... або ...

CREATE FUNCTION dbo.StartOfWeek2 -- always the DATEFIRST weekday
(
    @d DATE
)
RETURNS DATE
AS
BEGIN
    RETURN (SELECT DATEADD(DAY, 1-DATEPART(WEEKDAY, @d), @d));
END
GO

Зараз у вас є безліч альтернатив, але яка з них працює найкраще? Я був би здивований, якщо б були якісь суттєві відмінності, але я зібрав усі надані відповіді і провів їх через два набори тестів - один дешевий і один дорогий. Я виміряв статистику клієнта, оскільки я не бачу тут вводу-виводу чи пам'яті, які відіграють роль у продуктивності (хоча вони можуть увійти в дію залежно від того, як використовується функція). У моїх тестах результати такі:

"Дешевий" запит призначення:

Function - client processing time / wait time on server replies / total exec time
Gandarez     - 330/2029/2359 - 0:23.6
me datefirst - 329/2123/2452 - 0:24.5
me Sunday    - 357/2158/2515 - 0:25.2
trailmax     - 364/2160/2524 - 0:25.2
Curt         - 424/2202/2626 - 0:26.3

"Дорогий" запит призначення:

Function - client processing time / wait time on server replies / total exec time
Curt         - 1003/134158/135054 - 2:15
Gandarez     -  957/142919/143876 - 2:24
me Sunday    -  932/166817/165885 - 2:47
me datefirst -  939/171698/172637 - 2:53
trailmax     -  958/173174/174132 - 2:54

Я можу передати деталі своїх тестів за бажанням - зупиняючись тут, оскільки це вже стає досить затятим. Я був трохи здивований, побачивши, що Курт вийшов найшвидшим у верхній частині, враховуючи кількість обчислень та вбудований код. Можливо, я проведу кілька ретельніших тестів і буду писати про це в блозі ... якщо ви, хлопці, не заперечуєте проти того, щоб я публікував ваші функції деінде.


Отже, якщо я вважаю, що мої тижні починаються в неділю і закінчуються в суботу, я можу отримати останній день тижня для будь-якої дати @d так: SELECT DATEADD (wk, DATEDIFF (wk, '19041231', @d), `` 19041231 '')
Баодад,

21

Для них, які потрібно отримати:

Понеділок = 1 і неділя = 7:

SELECT 1 + ((5 + DATEPART(dw, GETDATE()) + @@DATEFIRST) % 7);

Неділя = 1 і субота = 7:

SELECT 1 + ((6 + DATEPART(dw, GETDATE()) + @@DATEFIRST) % 7);

Вище був подібний приклад, але завдяки подвійному "% 7" це було б набагато повільніше.


Це також чудово працює, щоб отримати число дня з початку тижня, яке є Сонцем чи Пн. Дякую
Fandango68,

В якості альтернативи select (datediff(dd,5,cal.D_DATE)%7 + 1)таselect (datediff(dd,6,cal.D_DATE)%7 + 1)
vasja

8

Для тих, хто потребує відповіді на роботі, а створення функції заборонено вашим DBA, працює наступне рішення:

select *,
cast(DATEADD(day, -1*(DATEPART(WEEKDAY, YouDate)-1), YourDate) as DATE) as WeekStart
From.....

Це дає початок цього тижня. Тут я припускаю, що неділі - це початок тижнів. Якщо ви думаєте, що понеділок - це початок, вам слід використовувати:

select *,
cast(DATEADD(day, -1*(DATEPART(WEEKDAY, YouDate)-2), YourDate) as DATE) as WeekStart
From.....

5

Це чудово працює для мене:

СТВОРИТИ ФУНКЦІЮ [dbo]. [StartOfWeek]
(
  @INPUTDATE DATETIME
)
ПОВЕРНЕННЯ ДАТИ

ЯК
ПОЧАТИ
  - ЦЕ не працює у функції.
  - ВСТАНОВИТИ ДАТА ПЕРШИМ 1 - встановити понеділок першим днем ​​тижня.

  DECLARE @DOW INT - зберігати день тижня
  SET @INPUTDATE = CONVERT (VARCHAR (10), @INPUTDATE, 111)
  SET @DOW = DATEPART (DW, @INPUTDATE)

  - Чарівне перетворення понеділка на 1, вівторка на 2 тощо.
  - не поважає думку SQL-сервера про початок тижня.
  - Але тут у нас є неділя, позначена як 0, але ми це виправляємо пізніше.
  SET @DOW = (@DOW + @@ DATEFIRST - 1)% 7
  IF @DOW = 0 SET @DOW = 7 - виправити на неділю

  ДАТА ПОВЕРНЕННЯДАТИ (DD, 1 - @ DOW, @ INPUTDATE)

Кінець

Здається, це повертається понеділок, враховуючи сьогоднішню дату, а не неділю. OP вже має функцію, яка повертає понеділок, він хоче, щоб він повернув неділю. :-)
Аарон Бертран

дох! Наступного разу мені слід уважніше читати питання. Однак моє рішення можна легко скорегувати, якщо це все ще потрібно. Здається, ОП у будь-якому разі задоволена прийнятою відповіддю -)
trailmax

Це правильне рішення на моїй машині, оскільки для мене: DATEADD (ww, DATEDIFF (ww, 0, CONVERT (DATE, '2017-10-8')), 0) повертає 2017-10-9!
Запустити CMD

3

Погуглив цей сценарій:

create function dbo.F_START_OF_WEEK
(
    @DATE           datetime,
    -- Sun = 1, Mon = 2, Tue = 3, Wed = 4
    -- Thu = 5, Fri = 6, Sat = 7
    -- Default to Sunday
    @WEEK_START_DAY     int = 1 
)
/*
Find the fisrt date on or before @DATE that matches 
day of week of @WEEK_START_DAY.
*/
returns     datetime
as
begin
declare  @START_OF_WEEK_DATE    datetime
declare  @FIRST_BOW     datetime

-- Check for valid day of week
if @WEEK_START_DAY between 1 and 7
    begin
    -- Find first day on or after 1753/1/1 (-53690)
    -- matching day of week of @WEEK_START_DAY
    -- 1753/1/1 is earliest possible SQL Server date.
    select @FIRST_BOW = convert(datetime,-53690+((@WEEK_START_DAY+5)%7))
    -- Verify beginning of week not before 1753/1/1
    if @DATE >= @FIRST_BOW
        begin
        select @START_OF_WEEK_DATE = 
        dateadd(dd,(datediff(dd,@FIRST_BOW,@DATE)/7)*7,@FIRST_BOW)
        end
    end

return @START_OF_WEEK_DATE

end
go

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


2

Можливо, вам потрібно це:

SELECT DATEADD(DD, 1 - DATEPART(DW, GETDATE()), GETDATE())

Або

DECLARE @MYDATE DATETIME
SET @MYDATE = '2011-08-23'
SELECT DATEADD(DD, 1 - DATEPART(DW, @MYDATE), @MYDATE)

Функція

CREATE FUNCTION [dbo].[GetFirstDayOfWeek]
( @pInputDate    DATETIME )
RETURNS DATETIME
BEGIN

SET @pInputDate = CONVERT(VARCHAR(10), @pInputDate, 111)
RETURN DATEADD(DD, 1 - DATEPART(DW, @pInputDate),
               @pInputDate)

END
GO

6
DATEPART(DWзалежить від@@datefirst
Мартін Сміт

Мені подобається простота цього. Здається, це працює досить добре і для дуже великих наборів даних.
Quick Joe Smith

2
Чому б просто не зробити вхідний параметр, DATEтоді вам не доведеться виконувати будь-які неоптимальні перетворення в VARCHARі назад, щоб просто вилучити будь-який випадковий компонент часу, який пройшов.
Аарон Бертран,

Була використана функція Convert, оскільки повернене значення не потребує Timeзначень.
Gandarez

1
Так, але справа в тому, що перетворення на varchar і назад знову дороге. Якщо у вас просто є параметр DATE, то вам байдуже, чи був включений час ... він позбавляється вас.
Аарон Бертран,

2
СТВОРИТИ ФУНКЦІЮ dbo.fnFirstWorkingDayOfTheWeek
(
    дата @currentDate
)
ПОВЕРНЕННЯ ІНТ
ЯК
ПОЧАТИ
    - отримати налаштування DATEFIRST
    ЗАЯВИТИ @ds int = @@ DATEFIRST 
    - отримати номер дня тижня за поточним налаштуванням DATEFIRST
    ЗАЯВИТИ @dow int = DATEPART (dw, @ currentDate) 

    ЗАЯВИТИ @wd int = 1 + (((@ dow + @ ds)% 7) +5)% 7 - це завжди повернення пн як 1, вт як 2 ... сонце як 7 

    ДАТА ПОВЕРНЕННЯ ДОДАТОК (dd, 1- @ wd, @ currentDate) 

Кінець

Це єдина функція, яка працювала у мене в SQL Server 2005. Дякую
Fandango68,

@ Fernando68 Чи можете ви пояснити, як інші рішення не працювали?
Аарон Бертран,

@AaronBertrand вибачте, не пригадую, але я думаю, що я зосередився на швидкій відповіді, і я спробував вашу, але з якихось причин у мене це не спрацювало.
Fandango68

@ Fernando68 Ну, це дуже корисно. : - \
Аарон Бертран

2

Для основного (неділя поточного тижня)

select cast(dateadd(day,-(datepart(dw,getdate())-1),getdate()) as date)

Якщо попередній тиждень:

select cast(dateadd(day,-(datepart(dw,getdate())-1),getdate()) -7 as date)

Внутрішньо ми створили функцію, яка робить це, але якщо вам потрібно швидко і брудно, це зробить це.


0

Оскільки юліанською датою 0 є понеділок, просто додайте кількість тижнів до неділі, яка є днем ​​до -1 Напр. виберіть dateadd (wk, datumiff (wk, 0, getdate ()), - 1)


0
Set DateFirst 1;

Select 
    Datepart(wk, TimeByDay) [Week]
    ,Dateadd(d,
                CASE 
                WHEN  Datepart(dw, TimeByDay) = 1 then 0
                WHEN  Datepart(dw, TimeByDay) = 2 then -1
                WHEN  Datepart(dw, TimeByDay) = 3 then -2
                WHEN  Datepart(dw, TimeByDay) = 4 then -3
                WHEN  Datepart(dw, TimeByDay) = 5 then -4
                WHEN  Datepart(dw, TimeByDay) = 6 then -5
                WHEN  Datepart(dw, TimeByDay) = 7 then -6
                END
                , TimeByDay) as StartOfWeek

from TimeByDay_Tbl

Це моя логіка. Встановіть для першого тижня понеділок, потім обчисліть, який день тижня є даним днем, а потім за допомогою DateAdd та Case I обчисліть, якою датою була б попередній понеділок того тижня.


-1

У мене не виникає жодної проблеми з жодною з наведених тут відповідей, однак я думаю, що мій набагато простіший у реалізації та розумінні. Я не проводив жодних тестів продуктивності на ньому, але цим слід нехтувати.

Тож я отримав свою відповідь з того факту, що дати зберігаються на сервері SQL як цілі числа (я кажу лише про компонент дати). Якщо ви мені не вірите, спробуйте цей SELECT CONVERT (INT, GETDATE ()), і навпаки.

Тепер, знаючи це, ви можете зробити кілька крутих математичних рівнянь. Можливо, ви зможете придумати кращу, але ось моя.

/*
TAKEN FROM http://msdn.microsoft.com/en-us/library/ms181598.aspx
First day of the week is
1 -- Monday
2 -- Tuesday
3 -- Wednesday
4 -- Thursday
5 -- Friday
6 -- Saturday
7 (default, U.S. English) -- Sunday
*/

--Offset is required to compensate for the fact that my @@DATEFIRST setting is 7, the default. 
DECLARE @offSet int, @testDate datetime
SELECT @offSet = 1, @testDate = GETDATE()

SELECT CONVERT(DATETIME, CONVERT(INT, @testDate) - (DATEPART(WEEKDAY, @testDate) - @offSet))

1
Я вважаю, що це не працює для мене. Мені @@DATEFIRSTтакож 7, але якщо у вас @testDateпочаток тижня, то це повертає дату, яка є напередодні.
row1

-1

У мене була подібна проблема. Вказавши дату, я хотів отримати дату понеділка того тижня.

Я використав таку логіку: Знайдіть номер дня на тижні в діапазоні 0-6, а потім відніміть його від дати початку.

Я використовував: DATEADD (day, - (DATEPART (day day,) + 5)% 7,)

Оскільки DATEPRRT (тиждень,) повертає 1 = неділя ... 7 = субота, DATEPART (день тижня,) + 5)% 7 повертає 0 = понеділок ... 6 = неділя.

Віднімання цієї кількості днів від початкової дати дає попередній понеділок. Той самий прийом можна використовувати для будь-якого початкового дня тижня.


-1

Я знайшов це простим і корисним. Працює, навіть якщо перший день тижня - неділя чи понеділок.

ЗАЯВИТИ @BaseDate ЯК Дата

ВСТАНОВИТИ @BaseDate = GETDATE ()

ЗАЯВИТИ @FisrtDOW ЯК Дата

ВИБРАТИ @FirstDOW = DATEADD (d, DATEPART (WEEKDAY, @ BaseDate) * -1 + 1, @BaseDate)


-3

Можливо, я надто спрощую тут, і це може бути так, але це, здається, працює для мене. Ще не зіткнувся з проблемою ...

CAST('1/1/' + CAST(YEAR(GETDATE()) AS VARCHAR(30)) AS DATETIME) + (DATEPART(wk, YOUR_DATE) * 7 - 7) as 'FirstDayOfWeek'
CAST('1/1/' + CAST(YEAR(GETDATE()) AS VARCHAR(30)) AS DATETIME) + (DATEPART(wk, YOUR_DATE) * 7) as 'LastDayOfWeek'

Тут ви можете отримати різні відповіді, якщо спробувати різні налаштування для SET DATEFIRST.
Аарон Бертран,

5
Ну, я не голосував проти, але у вашій відповіді взагалі не згадується DATEFIRST(ось уже три з половиною роки) і досі немає. І вам також слід уникати регіональних форматів, таких як m/d/yнавіть у тих випадках, коли m і d однакові.
Аарон Бертран,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.