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

时间:2015-02-27 16:46:35

标签: sql row-number

我使用此查询来计算特定日期范围的营业日数:

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

如果DateStartedand和DateCompleted都是工作日,则此工作正常。如果其中任何一个为null,则result也为null:

因此,我们的想法是将以下工作日row_number应用于周末/假日日期。例如:

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

4 个答案:

答案 0 :(得分:1)

您可以使用LEAD()函数从后续行中提取RN值,而不是根据假日/工作日字段排除日期,您可以有条件地应用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

答案 1 :(得分:1)

你仍然只需要在工作日获得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

答案 2 :(得分:1)

你可以通过累积总和得到你想要的东西。类似的东西:

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;

答案 3 :(得分:0)

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