从SQL Server中的XDate获取第n个工作日

时间:2013-12-01 04:32:11

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

我必须每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.

我甚至无法想到这个复杂逻辑的起点。

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

提前致谢,

2 个答案:

答案 0 :(得分: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

答案 1 :(得分:0)

我一直在尝试以下代码:

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

无论@@ DATEFIRST设置如何,其中1是星期日,7是星期六。 n(或0)的正值将返回 Next Nth Weekday ,而n的负值将返回 Previous Nth Weekday

我不完全明白你想要什么但是如果我正确地聚集在一起:仅仅获得第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个月的月初。如果我们有一个可以返回第N个月开始日期的函数,那么我们只需将已有的整数传递给函数并输出我们想要的日期。这正是我们使用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
);