【问题标题】:Get nth weekday from XDate in SQL Server从 SQL Server 中的 XDate 获取第 n 个工作日
【发布时间】:2013-12-01 04:32:10
【问题描述】:

我必须每 N 个月重复一次日期。

我有 XDate 开始。

我想要第 n 周的第 m 个工作日的日期。

N 是说 2 - I have to get for every 2 month

XDate 是假设明天的日期。所以,Starting from tomorrow

m 是 7 - 所以,get date of every Saturday

n 是 2 - of second week.

我什至想不出这个复杂逻辑的起点。

任何建议我应该如何开始 - 伪代码

提前致谢,

【问题讨论】:

  • 听你的解释有点麻烦,你能给我们举个例子吗?向我们展示您的输入变量的值以及您希望在输出中看到的日期。
  • 您使用N 来表示月数和星期几。是故意的吗?
  • 从 2014 年 1 月 4 日开始,我想获取每个第二个月第二周的星期六的日期。因此,如果“1 月 1 日至 1 月 4 日位于 1 月的第一周”,则 1 月 11 日。在 1 月 11 日之后,我们有 3 月 8 日,因为我认为 3 月 1 日也是第一周的第一个星期六。
  • @pm-77-1 很抱歉,但请检查解释中的粗线和评论中的最新示例
  • 您是在询问 SQL/T-SQL 解决方案,因此请将表格形式中的预期结果添加到您的帖子中。

标签: sql sql-server sql-server-2008 tsql datetime


【解决方案1】:

首先,这是日历表派上用场的地方。下面的代码创建一个名为 calendar 的表,并用从 2000 年开始的日期填充它。它还有一个名为 NthWeekdayInMonth 的列。例如,如果您查看从 1/29/05 到 1/31/05 的条目,您会看到该列设置为 5,因为它们是该月的第 5 个星期六、星期日和星期一。

CREATE TABLE Calendar
(
    [Date] date NOT NULL,
    [NthWeekdayInMonth] int,
    CONSTRAINT PK_Calendar
        PRIMARY KEY CLUSTERED ([Date])
        WITH FILLFACTOR = 100
)


;WITH cte AS 
(
    SELECT
        DATEADD(d, (a.Number * 256) + b.Number, '01/01/2000') AS [Date]
    FROM 
        (
            SELECT number
            FROM master..spt_values
            WHERE 
                type = 'P'
                AND number <= 255
        ) a (Number),
        (
            SELECT number
            FROM master..spt_values
            WHERE 
                type = 'P'
                AND number <= 255
        ) b (Number)
)

INSERT INTO Calendar
SELECT 
    [Date], 
    ROW_NUMBER() OVER (PARTITION BY YEAR([Date]), MONTH([Date]), DATEPART(dw, [Date]) ORDER BY [Date]) FROM cte
ORDER BY 
    [Date]
GO

现在我们有了一个日历表,剩下的就相当简单了。我确实在某个方面偏离了您的设计,但如果需要,您应该能够对其进行调整。在我的实现中,开始日期实际上是应该返回的第一个日期。因此,开始日期为 2014 年 1 月 11 日,每 2 个月查看一次:

2014-01-11 
2014-03-08 
2014-05-10 
2014-07-12

通过传递第一个日期,代码可以确定它是星期几以及一个月中的星期几。传递这些值是多余的。测试代码如下...

DECLARE @startDate date
DECLARE @everyNMonths int 
DECLARE @numResults int 
DECLARE @nthAppearanceOfDay int 

SET @startDate = '01/11/2014'   -- First occurence is on this date
SET @everyNMonths = 2           -- Skip every n months
SET @numResults = 4             -- Max # of results to return

-- Figure out which x-day of the month this is.  For example, if the starting 
-- date is 1/11/2014 that was the second Saturday so this will be set to 2.
SELECT @nthAppearanceOfDay = NthWeekdayInMonth FROM calendar WHERE [date] = @startDate

-- Use a CTE to get all the months involved in this calculation
;WITH candidateMonths AS (
    SELECT 
        1 AS [resultnum], @startDate AS [date]
    UNION ALL 
        SELECT resultnum + 1, DATEADD(month, @everyNMonths, [date]) FROM candidateMonths
            WHERE resultnum + 1 <= @numResults
)

-- Now evaluate every date for each of the candidate months.  If the day of week matches
-- that of the start date AND it is the Nth occurrence of that day of week in the month
-- include it
SELECT 
    c.[Date]
FROM 
    candidateMonths cm
    INNER JOIN calendar c ON ( (YEAR(c.[Date]) = YEAR(cm.[Date])) AND (MONTH(c.[Date]) = MONTH(cm.[Date])))
WHERE 
    (DATEPART(dw, c.[date]) = DATEPART(dw, @startDate)) -- Same day of week
    AND 
    (c.NthWeekdayInMonth = @nthAppearanceOfDay) -- Same week of month

【讨论】:

    【解决方案2】:

    我一直在试验以下代码:

    SELECT * FROM dbo.NthWeekday(GETDATE(), 1, 1);
    SELECT * FROM dbo.NthWeekday(GETDATE(), 1, -1);
    

    无论@@DATEFIRST 设置如何,其中 1 是星期日,7 是星期六。 n 的正值(或 0)将返回下一个第 N 个工作日,而 n 的负值返回前第 N 个工作日

    我不完全理解您想要什么,但如果我收集正确:仅获得第 N 个工作日是不够的。您还想在 X 个月内重复执行此操作。这是我要使用的暂定代码:

    DECLARE @date DATE = GETDATE();
    DECLARE @numMonths INT = -5
    DECLARE @weekday INT = 1;
    DECLARE @n INT = 2;
    
    SELECT C.D
    FROM dbo.RangeSmallInt(0, @numMonths - SIGN(@numMonths)) A
    CROSS APPLY ( -- MonthBegin
        SELECT DT = DATEADD(m, DATEDIFF(m, 0, @date) + A.N, 0)
    ) B
    CROSS APPLY dbo.NthWeekday(B.DT, @weekday, @n) C;
    
    Results: 2014-12-14
             2015-01-11
             2015-02-08
             2015-03-08
             2015-04-12
    

    您可以将其包装在一个表值函数中,就像我对 NthWeekday 和 RangeSmallInt 所做的那样。 RangeSmallInt 函数调用可以替换为数字表、计数 CTE 或任何您喜欢的术语/样式。

    工作原理:

    我们首先生成一组以0 开头的数字,因为我们希望函数具有包容性。 (@numMonths - SIGN(@numMonths)) 根据 @numMonths 的符号处理 @numMonths 的 1/0 的加法或减法。这确保为我们的下一个技巧生成正确的整数范围(在上述情况下:0 到 -4)

    一旦我们有一系列整数可以使用,我们就可以使用它们来抵消日期。在这种情况下,我们想要找出 X 个月的月初。如果我们有一个可以返回 Nth Month Begin Date 的函数,那么我们只需将我们已经拥有的整数传递给函数并获取我们想要的日期。这正是我们使用 CROSS APPLY 所做的。

    现在我们已经解决了 X 个月的月初问题,我们需要做的就是将 NthWeekday 函数应用于这些日期。

    第 N 个工作日:

    CREATE FUNCTION dbo.NthWeekday ( 
        @date DATE = NULL  
      , @weekday INT = NULL
      , @n INT = 1
    )
    RETURNS TABLE   
    AS   
    RETURN (
        SELECT D = CASE SIGN(@n)
                        WHEN -1 THEN DATEADD(d, -(DATEPART(dw, @date) + @@DATEFIRST - @weekday) % 7 + ((@n + 1) * 7), @date)                
                        ELSE DATEADD(d, (@weekday - DATEPART(dw, @date) + @@DATEFIRST) % 7 + ((@n - SIGN(@n)) * 7), @date)
                   END
    );
    

    RangeSmallInt:

    -- Generate a range of up to 65,536 contiguous BIGINTS
    CREATE FUNCTION dbo.RangeSmallInt (
        @num1 BIGINT = NULL
      , @num2 BIGINT = NULL
    )
    RETURNS TABLE
    AS
    RETURN (
        WITH Numbers(N) AS (
            SELECT N FROM(VALUES
                (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 16
              , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 32
              , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 48
              , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 64
              , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 80
              , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 96
              , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 112
              , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 128
              , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 144
              , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 160
              , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 176
              , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 192
              , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 208
              , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 224
              , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 240
              , (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (1) -- 256
            ) V (N)
        )    
        SELECT TOP (
                   CASE
                       WHEN @num1 IS NOT NULL AND @num2 IS NOT NULL THEN ABS(@num1 - @num2) + 1
                       ELSE 0
                   END
               )
               N = ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) + CASE WHEN @num1 <= @num2 THEN @num1 ELSE @num2 END - 1
        FROM Numbers A
           , Numbers B
        WHERE ABS(@num1 - @num2) + 1 < 65537
    );
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-05
      • 2022-06-10
      • 2021-09-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多