Получить первый день недели в SQL Server
Я пытаюсь сгруппировать записи по неделям, сохраняя агрегированную дату как первый день недели. Однако стандартная методика, которую я использую для округления дат, не работает правильно с неделями (хотя она работает для дней, месяцев, лет, кварталов и любого другого таймфрейма, к которому я ее применил).
вот 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);
но тот факт, что я должен сделать такое исключение, заставляет меня немного беспокоиться. Кроме того, извиняюсь, если это дублирующий вопрос. Я нашел некоторые связанные вопросы, но ни один из них не касался этого аспекта конкретно.
13 ответов:
чтобы ответить, почему вы получаете понедельник, а не воскресенье:
вы добавляете несколько недель к дате 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
настройка (или ваш код выполняется для пользователя с другой настройкой) - при условии, что вы все еще хотите воскресенье независимо от текущей настройки. Если вы хотите, чтобы эти два ответа jive, то вы должны использовать функцию, которая тут зависит от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
я могу передать детали моих тестов при желании-остановившись здесь, поскольку это уже становится довольно многословным. Я был немного удивлен, увидев, что Курт вышел как самый быстрый в верхнем конце, учитывая количество вычислений и встроенный код. Может быть, я проведу более тщательную проверку тесты и блог об этом... если вы, ребята, не возражаете, чтобы я опубликовал ваши функции в другом месте.
для этих, которые нужно получить:
понедельник = 1 и воскресенье = 7:
SELECT 1 + ((5 + DATEPART(dw, GETDATE()) + @@DATEFIRST) % 7);
воскресенье = 1 и суббота = 7:
SELECT 1 + ((6 + DATEPART(dw, GETDATE()) + @@DATEFIRST) % 7);
выше был аналогичный пример, но благодаря двойному "%7" он будет намного медленнее.
Это прекрасно работает для меня:
CREATE FUNCTION [dbo].[StartOfWeek] ( @INPUTDATE DATETIME ) RETURNS DATETIME AS BEGIN -- THIS does not work in function. -- SET DATEFIRST 1 -- set monday to be the first day of week. DECLARE @DOW INT -- to store day of week SET @INPUTDATE = CONVERT(VARCHAR(10), @INPUTDATE, 111) SET @DOW = DATEPART(DW, @INPUTDATE) -- Magic convertion of monday to 1, tuesday to 2, etc. -- irrespect what SQL server thinks about start of the week. -- But here we have sunday marked as 0, but we fix this later. SET @DOW = (@DOW + @@DATEFIRST - 1) %7 IF @DOW = 0 SET @DOW = 7 -- fix for sunday RETURN DATEADD(DD, 1 - @DOW,@INPUTDATE) END
погуглил этот скрипт:
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
может быть, вам нужно это:
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
CREATE FUNCTION dbo.fnFirstWorkingDayOfTheWeek ( @currentDate date ) RETURNS INT AS BEGIN -- get DATEFIRST setting DECLARE @ds int = @@DATEFIRST -- get week day number under current DATEFIRST setting DECLARE @dow int = DATEPART(dw,@currentDate) DECLARE @wd int = 1+(((@dow+@ds) % 7)+5) % 7 -- this is always return Mon as 1,Tue as 2 ... Sun as 7 RETURN DATEADD(dd,1-@wd,@currentDate) END
для тех, кому нужен ответ на работе и создание функции запрещено вашим 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.....
поскольку юлианская дата 0-это понедельник, просто добавьте количество недель в воскресенье который за день до -1 например. выберите dateadd(wk,datediff (wk,0,getdate ()), -1)
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 я вычисляю, какая дата была бы в предыдущий понедельник этой недели.
У меня нет никаких проблем с любым из ответов, приведенных здесь, однако я думаю, что мой намного проще реализовать и понять. Я не запускал никаких тестов производительности на нем, но он должен быть отрицательным.
поэтому я получил свой ответ из того факта, что даты хранятся в SQL server как целые числа (я говорю только о компоненте даты). Если вы мне не верите, попробуйте выбрать 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))
У меня была похожая проблема. Учитывая дату, я хотел получить дату понедельника на этой неделе.
Я использовал следующую логику: найти номер дня в неделю в диапазоне 0-6, а затем вычесть, что от даты originay.
Я использовал: DATEADD (day,-(DATEPART(weekday,)+5)%7,)
Так как DATEPRRT (будний день,) возвращает 1 = воскресенье ... 7=суббота, DATEPART (будний день,)+5)%7 возвращает 0=понедельник ... 6=воскресенье.
вычитание этого количества дней из исходная дата дает предыдущий понедельник. Такая же техника может быть использована для любого начального дня недели.
Я нашел это простым и полезным. Работает, даже если первый день недели воскресенье или понедельник.
объявить @BaseDate как дату
SET @BaseDate = GETDATE ()
объявить @FisrtDOW как дату
SELECT @FirstDOW = DATEADD(d, DATEPART (WEEKDAY,@BaseDate) *-1 + 1, @BaseDate)
может быть, я слишком упрощаю здесь, и это может быть так, но это, кажется, работает для меня. Еще не сталкивался с какими-либо проблемами с ним...
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'