Получить первый день недели в SQL Server - PullRequest
85 голосов
/ 24 августа 2011

Я пытаюсь сгруппировать записи по неделям, сохраняя агрегированную дату в качестве первого дня недели.Тем не менее, стандартная техника, которую я использую для округления дат, кажется, не работает должным образом с неделями (хотя она применяется для дней, месяцев, лет, кварталов и любых других временных периодов, к которым я ее применял).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);

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

Ответы [ 14 ]

132 голосов
/ 24 августа 2011

Чтобы ответить, почему вы получаете понедельник, а не воскресенье:

Вы добавляете количество недель к дате 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

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

14 голосов
/ 14 февраля 2014

Для тех, кому нужно получить:

Понедельник = 1 и Воскресенье = 7:

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

Воскресенье = 1 и суббота = 7:

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

Выше был похожий пример, но благодаря двойному «% 7» это было бы намного медленнее.

4 голосов
/ 24 августа 2011

Это прекрасно работает для меня:

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
3 голосов
/ 30 марта 2018

Для тех, кому нужен ответ на работе, а создание функций запрещено вашим администратором базы данных, будет работать следующее решение:

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.....
3 голосов
/ 24 августа 2011

Погуглил этот скрипт:

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 голосов
/ 03 декабря 2013
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
2 голосов
/ 24 августа 2011

Может быть, вам нужно это:

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
0 голосов
/ 18 июня 2019

Для основного (воскресенье текущей недели)

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

Если на предыдущей неделе:

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

Внутренне, мы создали функцию, которая делает это, но если вам нужно быстро и грязно, это сделает это.

0 голосов
/ 21 мая 2018
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 вычислите, какой была бы дата предыдущего понедельника этой недели.

0 голосов
/ 21 мая 2016

Я нашел это простым и полезным. Работает, даже если первый день недели - воскресенье или понедельник.

ОБЪЯВИТЬ @BaseDate AS Дата

SET @BaseDate = GETDATE ()

ОБЪЯВИТЬ @FisrtDOW AS Дата

SELECT @FirstDOW = DATEADD (d, DATEPART (WEEKDAY, @ BaseDate) * -1 + 1, @BaseDate)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...