SQL - 将相同的 ROW_NUMBER 应用于不匹配的行

SQL - Apply same ROW_NUMBER to non-matching rows

我正在使用此查询来计算特定日期范围内的工作日数:

WITH cte AS (
 SELECT [Date] AS WorkingDay,
 ROW_NUMBER() OVER (ORDER BY [Date] ASC) AS RN
 FROM DimDate
 WHERE IsHolidayUSA = 0
 AND IsWeekday = 1
)
SELECT
 DateStarted,
 DateCompleted,
 c2.RN - c1.RN AS CycleTime
FROM MyTable t
INNER JOIN cte c1
 ON t.DateStarted=c1.WorkingDay
INNER JOIN cte c2
 ON t.DateCompleted=c2.WorkingDay

如果 DateStarted 和 DateCompleted 都是工作日,这就可以正常工作。如果其中任何一个为空,则结果也为空:

所以我的想法是将下一个工作日 row_number 应用到 weekend/holiday 日期。例如:

Date        RN
2015-02-23  1 -- Mon
2015-02-24  2 -- Tue
2015-02-25  3 -- Wed
2015-02-26  4 -- Thu
2015-02-27  5 -- Fri
2015-02-28  6 -- Sat (applied row number of next business day) 
2015-03-01  6 -- Sun (applied row number of next business day)
2015-03-02  6 -- Mon
2015-03-03  7 -- Tue
2015-03-04  8 -- Wed
2015-03-05  9 -- Thu

编辑:

已提取 ROW_NUMBER 查询并指向需要处理的部分:

select Date as WorkingDay,

RN = 
    CASE WHEN IsHolidayUSA = 0 AND IsWeekday = 1
    THEN ROW_NUMBER() OVER (ORDER BY [Date] ASC)
    ELSE 1 -- need to modify this one
    END 
from DimDate

您可以使用 LEAD() 函数从后续行中提取 RN 值,而不是根据 holiday/weekday 字段排除日期,您可以有条件地应用 ROW_NUMBER() 给他们:

;WITH cte AS (SELECT [Date] AS WorkingDay
                    , CASE WHEN IsHolidayUSA <> 0  AND IsWeekday <> 1 THEN NULL
                           ELSE ROW_NUMBER() OVER(PARTITION BY CASE WHEN IsHolidayUSA <> 0  AND IsWeekday <> 1 THEN 1 END ORDER BY [Date])
                           END AS RN
             FROM DimDate
            )
SELECT *,RN = COALESCE(RN,LEAD(RN,1) OVER(ORDER BY WorkingDay) ,LEAD(RN,2) OVER(ORDER BY WorkingDay))
FROM  cte
ORDER BY WorkingDay

如果需要,您可以添加更多 LEAD() 功能以适应 3 或 4 天的周末。

这是一个在不存在的表上进行演示的工作示例:

;WITH cal AS (SELECT CAST('2013-03-01' AS DATE) dt
              UNION  ALL
              SELECT DATEADD(DAY,1,dt)
              FROM cal
              WHERE dt < '2013-03-31')
     ,RN AS (SELECT *,CASE WHEN DATENAME(WEEKDAY,dt) IN ('Saturday','Sunday') THEN NULL
                           ELSE ROW_NUMBER() OVER(PARTITION BY CASE WHEN DATENAME(WEEKDAY,dt) IN ('Saturday','Sunday') THEN 1 END ORDER BY dt)
                           END AS RN
             FROM  cal
             )
SELECT *,RN = COALESCE(RN,LEAD(RN,1) OVER(ORDER BY dt) ,LEAD(RN,2) OVER(ORDER BY dt))
FROM  RN
ORDER BY dt

尝试 DENSE_RANK() 而不是 ROW_NUMBER()。

您仍然需要仅在工作日获取 row_number(),但诀窍是将所有日期加入该工作日 cte 并查找下一个工作日的非-工作日。 (令人困惑)...

with dn as (

    select 
        *,
        IsWorkingDay = cast(case when IsHolidayUSA = 0 AND IsWeekday = 1 then 1 else 0 end as bit)
    from DimDate
    where [Date] between '2/23/2015' and '3/5/2015'

), wd as (
    select 
        [Date],
        WorkingDayNum = row_number() OVER (ORDER BY [Date] ASC)
    from dn
    where IsWorkingDay = 1

), d as (

    select 
        dn.[Date], 
        [WorkingDayNum] = coalesce(wd.WorkingDayNum, n.WorkingDayNum)
    from
        dn
        left outer join wd on wd.[Date] = dn.[Date]
        outer apply (
            select top 1 wd.WorkingDayNum 
            from wd 
            where wd.[Date] > dn.[Date]
            order by wd.[Date]
        ) n
)


select * from d order by Date

你已经非常接近你想要的累积总和了。类似于:

select Date as WorkingDay,
       SUM(case when IsHolidayUSA = 0 and IsWeekday = 1 then 1 else 0 end) over
              (order by [date] asc) as rn
from DimDate;

问题是这给周末和节假日一个等于前一个工作日的数字,而不是下一个。所以,在某些情况下通过加1来修改它:

select Date as WorkingDay,
       (SUM(case when IsHolidayUSA = 0 and IsWeekday = 1 then 1 else 0 end) over
              (order by [date] asc)
        (case when IsHolidayUSA = 0 and IsWeekday = 1 then 0 else 1 end)) as rn
from DimDate;