Получить первый день недели в 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
настройка (или ваш код работает для пользователя с другой настройкой) - при условии, что вы все еще хотите воскресенье независимо от текущей настройки. Если вы хотите, чтобы эти два ответа на джайв, то вы должны использовать функцию, которая тут зависит от 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
Googled этот скрипт:
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 (weekday,) возвращает 1 = Sundaye ... 7=суббота, DATEPART (weekday,)+5)%7 возвращает 0=понедельник ... 6=воскресенье.
вычитание этого количества дней из первоначальная дата - предыдущий понедельник. Такая же техника может быть использована в любой день недели.
Я нашел это простым и полезным. Работает, даже если первый день недели воскресенье или понедельник.
объявить @BaseDate как дату
SET @BaseDate = GETDATE ()
объявить @FisrtDOW как дату
выберите @FirstDOW = DATEADD(d,DATEPART (будний день,@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'