SQL Server: получить следующий относительный день недели. (Следующий Понедельник, Вторник, Ср.…)

что мне нужно, это дата на следующий день (понедельник, вторник, Ср...) после сегодняшней даты.

пользователь может выбрать следующий день, который они хотят, и который хранится как int в таблице. "Позвони мне в следующий вторник (3)"

Sunday = 1
Monday = 2
Tuesday = 3
...

Так мой стол выглядит так.

UserID, NextDayID

то, что я придумал это:

select dateadd(dd,(7 - datepart(dw,GETDATE()) + NextDayID ) % 7, getdate())

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

что мне интересно, это хорошее решение или есть что-то, чего мне не хватает?

4 ответов


1) ваше решение использует недетерминированную функцию:datepart(dw...) . Из-за этого аспекта меняется DATEFIRST настройка даст разные результаты. Например, вы должны попробовать:

SET DATEFIRST 7;
your solution;

а то

SET DATEFIRST 1;
your solution;

2) Следующее решение не зависит от DATEFIRST/LANGUAGE параметры:

DECLARE @NextDayID INT  = 0 -- 0=Mon, 1=Tue, 2 = Wed, ..., 5=Sat, 6=Sun
SELECT DATEADD(DAY, (DATEDIFF(DAY, @NextDayID, GETDATE()) / 7) * 7 + 7, @NextDayID) AS NextDay

результат:

NextDay
-----------------------
2013-09-23 00:00:00.000

это решение основано на следующем свойстве DATETIME тип:

  • День 0 = 19000101 = Mon

  • 1 день = 19000102 = Вт

  • день 2 = 19000103 = Ср

...

  • день 5 = 19000106 = Sat

  • день 6 = 19000107 = Солнце

Итак, преобразование значения INT 0 в DATETIME дает 19000101.

если вы хотите найти следующее Wednesday тогда вы должны начать со второго дня (19000103/Wed), вычислить дни между днем 2 и текущим днем (20130921; 41534 дня), разделить на 7 (для того, чтобы получить количество полных недель; 5933 недели), кратно 7 (41531 fays; для того, чтобы получить количество дней - полных недель между первым Wednesday/19000103 и последний Wednesday), а затем добавить 7 дней (одна неделя; 41538 дней; для того, чтобы получить следующие Wednesday). Добавьте это число (41538 дней) к дате начала: 19000103.

Примечание: моя текущая дата 20130921.

правка #1:

DECLARE @NextDayID INT;
SET @NextDayID = 1; -- Next Sunday
SELECT DATEADD(DAY, (DATEDIFF(DAY, ((@NextDayID + 5) % 7), GETDATE()) / 7) * 7 + 7, ((@NextDayID + 5) % 7)) AS NextDay

результат:

NextDay
-----------------------
2013-09-29 00:00:00.000 

Примечание: моя текущая дата 20130923.


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

2013-09-20  Fri
2012-09-21  Sat
2012-09-22  Sun
2012-09-23  Mon
2012-09-24  Tue
...

поэтому запрос на получение следующего понедельника может выглядеть так.

select min(cal_date)
from calendar
where cal_date > current_date
  and day_of_week = 'Mon';

на практике вам, вероятно, понадобится гораздо больше столбцов в таблице календаря, потому что вы найдете для него много применений.

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

таблица календаря в PostgreSQL


следующая функция позволяет вам создавать таблицы "на лету"...вот как я обычно это делаю...Мне не нравится идея Пермский таблицы...кажется ненужным, но каждый человек и ситуация разные : -)

CREATE function [dbo].[fxDateTable]
(
    @begindate datetime = null
,   @enddate datetime = null
)
RETURNS @dates TABLE
(
            EventDate datetime primary key not null
)
as
begin
    select @enddate = isnull(@enddate, getdate())
    select @begindate = isnull(@begindate, dateadd(day, -3, @enddate))

    insert @dates
    select dateadd(day, number, @begindate)
    from 
        (select distinct number from master.dbo.spt_values
         where name is null
        ) n
    where dateadd(day, number, @begindate) < @enddate

    return
end

Это старый вопрос. Но я уверен, что публикация лучшего решения стоит того.

-- 0 = 1st Mon, 1 = 1st Tue, 2 = 1st Wed, ..., 5 = 1st Sat, 6 = 1st Sun
-- 7 = 2nd Mon, 8 = 2nd Tue, ...
declare @NextDayID int = 0, @Date date = getdate()

select cast (cast (
    -- last Monday before [Date] inclusive, starting from 1900-01-01
    datediff (day, @NextDayID % 7, @Date) / 7 * 7
    -- shift on required number of days
    + @NextDayID + 7
    as datetime) as date)

это решение является улучшенным решением @Bogdan Sahlean. Он может работать с @NextDayID, который больше 6. Таким образом, вы можете, например, найти 2-й понедельник с сегодняшнего дня.

следующий запрос показывает, что мое решение работает правильно.

select [Date]
    , convert (char(5), [0], 10) as Mon1
    , convert (char(5), [1], 10) as Tue1
    , convert (char(5), [2], 10) as Wed1
    , convert (char(5), [3], 10) as Thu1
    , convert (char(5), [4], 10) as Fri1
    , convert (char(5), [5], 10) as Sat1
    , convert (char(5), [6], 10) as Sun1
    , convert (char(5), [7], 10) as Mon2
    , convert (char(5), [8], 10) as Tue2
from (
    select [Date], NextDayID
        , cast (cast (
          datediff (day, NextDayID % 7, [Date]) / 7 * 7 -- last Monday before [Date] inclusive, starting from 1900-01-01
        + NextDayID + 7 -- shift on required number of days
        as datetime) as date) as NextDay
    from (
        select datefromparts (2018, 5, dt) as [Date]
        from (values(14),(15),(16),(17),(18),(19),(20))t_(dt)
    ) d
    cross join (values(0),(1),(2),(3),(4),(5),(6),(7),(8))nd(NextDayID)
) t
pivot (
    min (NextDay) for NextDayID in ([0], [1], [2], [3], [4], [5], [6], [7], [8])
) pvt

результат:

Date       | Mon1  | Tue1  | Wed1  | Thu1  | Fri1  | Sat1  | Sun1  | Mon2  | Tue2
-----------+-------+-------+-------+-------+-------+-------+-------+-------+------
2018-05-14 | 05-21 | 05-15 | 05-16 | 05-17 | 05-18 | 05-19 | 05-20 | 05-28 | 05-22
2018-05-15 | 05-21 | 05-22 | 05-16 | 05-17 | 05-18 | 05-19 | 05-20 | 05-28 | 05-29
2018-05-16 | 05-21 | 05-22 | 05-23 | 05-17 | 05-18 | 05-19 | 05-20 | 05-28 | 05-29
2018-05-17 | 05-21 | 05-22 | 05-23 | 05-24 | 05-18 | 05-19 | 05-20 | 05-28 | 05-29
2018-05-18 | 05-21 | 05-22 | 05-23 | 05-24 | 05-25 | 05-19 | 05-20 | 05-28 | 05-29
2018-05-19 | 05-21 | 05-22 | 05-23 | 05-24 | 05-25 | 05-26 | 05-20 | 05-28 | 05-29
2018-05-20 | 05-21 | 05-22 | 05-23 | 05-24 | 05-25 | 05-26 | 05-27 | 05-28 | 05-29

это решение не зависит от @@datefirst.