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

Мне нужна дата следующего дня (понедельник, вторник, ср.), следующего за сегодняшней датой.

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

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

Итак, моя таблица выглядит так.

UserID, NextDayID

Что я придумал:

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

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

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

Ответ 1

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= Tue

  • День 2 = 19000103= ср

...

  • День 5 = ​​ 19000106= Сб

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

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

Если вы хотите найти следующий Wednesday, то вы должны начать с 2-го дня (19000103/Wed), вычислить дни между днем ​​2 и текущим днем ​​(20130921; 41534 дня), делить на 7 (для того, чтобы получить количество полных недель, 5933 недели), несколько на 7 (41531 чел., чтобы получить количество дней - полные недели между первым 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.

Ответ 2

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

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

Ответ 3

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

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

Ответ 4

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

-- 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. Так, например, вы можете найти второй понедельник с сегодняшнего дня.

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

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.

Ответ 5

Я думаю, что это лучший способ найти следующий понедельник

CONVERT(VARCHAR(11),DateAdd(DAY,case 
     when (DateName(WEEKDAY, NextVisitDate) ='Tuesday') Then 6 
     when (DateName(WEEKDAY, NextVisitDate) ='Wednesday') Then 5 
     when (DateName(WEEKDAY, NextVisitDate) ='Thursday') Then 4
     when (DateName(WEEKDAY, NextVisitDate) ='Friday') Then 3 
     when (DateName(WEEKDAY, NextVisitDate) ='Saturday') Then 2
     when (DateName(WEEKDAY, NextVisitDate) ='Sunday') Then 1
     else 0 end, DateAdd(DAY, DateDiff(DAY, 0,  NextVisitDate), 0)),106) AS Monday,}