【问题标题】:SQL: get next relative day of week. (Next Monday, Tuesday, Wed.....)SQL:获取下一个相对星期几。 (下周一、周二、周三......)
【发布时间】:2013-09-20 15:23:42
【问题描述】:

我需要的是今天日期之后的下一个给定日期(星期一、星期二、星期三...)的日期。

允许用户选择他们想要的日期,并将其作为 int 存储在表格中。 “下周二给我打电话 (3)”

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

所以我的桌子是这样的。

UserID, NextDayID

我想出的是:

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

它似乎有效,如果您要求下一个今天的日期,它将返回今天的日期,如果需要,我可以添加一周。

我想知道的是,这是一个好的解决方案还是我缺少什么?

【问题讨论】:

    标签: sql date


    【解决方案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 = 星期一

    • 第 1 天 = 19000102 = 星期二

    • 第 2 天 = 19000103 = 周三

    ...

    • 第 5 天 = 19000106 = 周六

    • 第 6 天 = 19000107 = 太阳

    因此,将 INT 值 0 转换为 DATETIME 会得到 19000101

    如果你想找到下一个Wednesday,那么你应该从第 2 天(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

    【讨论】:

    • 我更喜欢这个解决方案,只是希望我可以使用 Sun 作为一周的第一天。你有什么建议吗?如果需要,我可以将星期一作为一周的第一天。
    • Edit #1 它得到了和我一样的结果,但我更喜欢它,因为我不必像你所说的那样使用非确定性函数(我不知道那部分datepart/datefirst 直到现在),它使我可以将这些日子保持在最终用户易于理解的顺序中。谢谢博格丹。
    【解决方案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';
    

    实际上,您可能需要在日历表中添加更多列,因为您会发现它有很多用途。

    此外,通常可以看出使用日历表的代码显然是正确的。阅读上面的代码很简单:选择今天之后且在星期一的最小日历日期。很少看到依赖日期函数和日期算术的代码显然是正确的。

    A calendar table in PostgreSQL

    【讨论】:

    • 您是每年更新该表还是只用 100 年的数据填充它?
    • 用 100 年的数据填充它。我在这台机器上拥有 50 年的数据,上面的查询在 0.050 毫秒内运行。好的索引很重要。
    【解决方案3】:

    这是一个老问题。但我确信发布更好的解决方案是值得的。

    -- 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 的改进解决方案。 它可以操作大于 6 的@NextDayID。 例如,您可以从今天开始查找第 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

    【讨论】:

      【解决方案4】:

      我认为这是找到下周一的最佳方法

      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,
      

      【讨论】:

        【解决方案5】:

        如果今天是需要查找的那一天,则查找下一个即将到来的日子,包括今天。

        稍作调整... 设置变量@weekdayno 如下: 1 = 星期日,2 = 星期一,3 = 星期二,4 = 星期三,5 = 星期四,6 = 星期五,7 = 星期六

            DECLARE @weekdayno INT 
            DECLARE @todayno INT 
        
            SET @weekdayno = 2  ---For Monday----
            SET @todayno = DATEPART(dw,GETDATE()) 
        
            SELECT CASE 
            WHEN (@todayno = @weekdayno) 
                THEN CONVERT(varchar, GETDATE(), 101) 
        
            WHEN (@todayno < @weekdayno) 
                THEN CONVERT(varchar, (@weekdayno - @todayno + GETDATE()), 101) 
        
            WHEN (@todayno > @weekdayno) 
                then CONVERT(varchar,(GETDATE() - (@todayno - @weekdayno) + 7), 101) 
        
            END AS UpcomingOrToday
        

        【讨论】:

          【解决方案6】:

          以下功能可以让您即时生成表格...这是我通常的做法...我不喜欢烫发日期表的想法...似乎没有必要,但每个人和情况不同:-)

          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
          

          【讨论】:

            【解决方案7】:

            试试这个:这将给出一个月中所需工作日的日期。

             declare @monthstartdate date='2020-01-01',@monthenddate date='2020-01-31',@weekday char(9)='thursday',@weeknum int=4
            
                    ; with cte(N,WeekDayName_C,Date_C) as
                    (select 1,datename(WEEKDAY,@monthstartdate),@monthstartdate
                    union all
                    select n+1,datename(WEEKDAY,dateadd(day,n,@monthstartdate)),dateadd(day,n,@monthstartdate) from cte where n<31 and Date_C<=@monthenddate )
                    select * from (select *,ROW_NUMBER() over (partition by WeekDayName_C order by Date_C asc)Weeknum from cte)a
                    where WeekDayName_C=@weekday and Weeknum=@weeknum
            

            【讨论】:

              【解决方案8】:

              我将此作为一个函数,其中过程使用可用的简化知识,因此我认为它是一个强大的解决方案。

              CREATE FUNCTION [nilnul.time_._dated.date].[NextWeekday]
              (
                  @nextWeekDay int  -- sunday as firstday is 1.
              )
              RETURNS datetime
              AS
              BEGIN
              
              
              declare @time datetime;
              set @time=getdate();
              
              declare @weekday int;
              set @weekday = datepart(weekday,  @time) ;
              
              declare @diff int;
              set @diff= @nextWeekDay-@weekday;
              
              --modulo 7 bijectively
              declare @moduloed int;
              set @moduloed = case 
                  when @diff <=0 then @diff+7 
                  else @diff
              end;
              
              return dateadd(day, @moduloed,  @time);
              
              END
              

              【讨论】:

              • 为了改善这一点,计划将 `bijective modulo' 也作为一个函数
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2019-03-31
              • 2015-01-29
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-11-27
              相关资源
              最近更新 更多