【问题标题】:Conditional Count On Row_NumberRow_Number 的条件计数
【发布时间】:2016-11-22 14:56:48
【问题描述】:

我有一个查询,它根据存储我们所有公共假期的表计算一个月内的工作日数。

当前输出将显示所有工作日,不包括公共假期和周六和周日,我想显示每个月的每一天,但不要在公共假期或周六或周日增加。

有没有办法有条件地增加行号?

查询如下:

DECLARE @startnum INT=0
DECLARE @endnum INT=365;

WITH gen AS 
(
    SELECT @startnum AS num
    UNION ALL
    SELECT num + 1 
    FROM gen 
    WHERE num + 1 <= @endnum   
)
, holidays AS
( 
    SELECT CONVERT(DATE, transdate)  AS HolidayDate 
    FROM WORKCALENDER w  
    WHERE w.CALENDARID = 'PubHoliday'
)
, allDays AS
( 
    SELECT DATEADD( d, num, CONVERT( DATE, '1 Jan 2016' ) ) AS DateOfYear
    , DATENAME( dw, DATEADD( d, num, CONVERT( DATE, '1 Jan 2016' ))) AS [dayOfWeek]
    FROM gen 
)
select number = ROW_NUMBER() OVER ( ORDER BY DateOfYear  )     
, *
from allDays
    LEFT OUTER JOIN holidays
        ON allDays.DateOfYear = holidays.HolidayDate
WHERE holidays.HolidayDate IS NULL
    AND allDays.dayOfWeek NOT IN ( 'Saturday', 'Sunday')
    AND DateOfYear >= CONVERT( DATE, '1 ' + DATENAME( MONTH, GETDATE() ) + ' 2016' )
    AND DateOfYear < CONVERT( DATE, '1 ' + DATENAME( MONTH, DATEADD( month, 1, GETDATE()) ) + ' 2016' )
option (maxrecursion 10000)

【问题讨论】:

  • 一些格式会对这堵文字墙产生奇迹。我对此进行了格式化,以便我可以阅读它,而且它的混乱程度要低得多。 FWIW,您应该考虑使用计数或数字表而不是递归 cte 进行计数。要解决手头的问题,您需要提供更多细节。很可能只有您想要计数的日期的 cte 将加入到所有日期的列表中。
  • 是的,我认为您只需将现有的最终选择(减去行号位)放入另一个 cte,然后从此过滤后的 cte 中选择,添加行号)

标签: sql sql-server database tsql


【解决方案1】:

一种伪代码

select date, row_number() over (order by date) as num  
from  ( select date 
        from allDates 
        where month = x and weekday  
        exept 
        select date 
        from holidays 
        where month is x 
      ) as t
union all
select date, null
from holidays 
where month is x  
order by date 

【讨论】:

    【解决方案2】:

    您可以使用加窗求和,看看 WorkdaySequenceInMonth 的输出是如何组成的。

    DECLARE   @startDate  DATE = '20160101'
            , @numDays    INT =  365
            , @num        INT  =  0;
    
    DECLARE @Holidays TABLE (Holiday DATE);
    
    INSERT INTO @Holidays(Holiday)
         VALUES ('20160101')
              , ('20160115')
              , ('20160714');
    
    WITH nums AS
    (
        SELECT row_number() OVER (ORDER BY object_id) - 1 as num
        FROM sys.columns
    ),
    dateRange as
    (
      SELECT 
          DATEADD(DAY, num, @startDate) AS Dt
        , num
      FROM nums
      WHERE num < @numDays
    ),
    Parts AS
    (
        SELECT
             R.Dt as [Date]
           , Year(R.Dt) as [Year]
           , Month(R.Dt) as [Month]
           , Day(R.Dt) as [Day]
           , Datename(weekday, R.Dt) as [Weekday]
           , CASE WHEN H.Holiday IS NOT NULL 
                       OR Datename(weekday, R.Dt) IN ('Saturday', 'Sunday')
                  THEN 0
                  ELSE 1
                  END AS IsWorkday
    
        FROM dateRange R    
        LEFT JOIN @Holidays H ON R.Dt = H.Holiday
    )
    -- 
    select 
        *   
        , sum(IsWorkday) over (PARTITION BY [Year],[month]
                               ORDER BY [Day]
                               ROWS UNBOUNDED PRECEDING) as WorkdaySequenceInMonth      
    from Parts
    order by [Year], [Month]
    

    【讨论】:

      【解决方案3】:

      你好你可以试试这个查询,最初的部分是数据生成,也许你不需要它。 然后我生成一个临时表,其中包含@StartYear、@EndYear 中设置的时间段的所有日期 然后只需简单的查询即可返回数据

                  -- generate holidays table
                  select holiday 
                  into #tempHolidays
                  from
                  (
                  select '20160101' as holiday
                  union all 
                  select '20160201' as holiday
                  union all 
                  select '20160205' as holiday
                  union all 
                  select '20160301' as holiday
                  union all 
                  select '20160309' as holiday
                  union all 
                  select '20160315' as holiday
                  )  as t
      
      
                  create table #tempCalendar (Date_temp date)
      
                  select * from 
                  #tempHolidays
      
                  declare @startYear int , @endYear int,  @i int,  @dateStart datetime , @dateEnd datetime,  @date datetime, @i = 0
                  Select  @startYear = '2016'
                          ,@endYear = '2016'
                          ,@dateStart = (Select cast( (cast(@startYear as varchar(4)) +'0101') as datetime))
                          ,@dateEnd = (Select cast( (cast(@startYear as varchar(4)) +'1231') as datetime))
                          ,@date = @dateStart
      
      
                          --Insert dates of the period of time
                  while (@date <> @dateEnd)
                  begin
      
                  insert into #tempCalendar
                  Select @date
                  set @date = (select DATEADD(dd,1,@date))
      
                  end
      
                  -- Retrive Date list
                  Select Date_temp 
                  from #tempCalendar
                  where Date_temp not in (Select holiday from #tempHolidays)
                  and datename(weekday,Date_temp) not in ('Saturday','Sunday')
      
                  --REtrieve sum of working days per month
                  select DATEPART(year,Date_temp) as year
                          ,DATEPART(month,Date_temp) as Month
                          ,Count(*) as CountOfWorkingDays
      
                  from #tempCalendar
                  where Date_temp not in (Select holiday from #tempHolidays)
                  and datename(weekday,Date_temp) not in ('Saturday','Sunday')
                  Group by DATEPART(year,Date_temp)
                          ,DATEPART(month,Date_temp) 
      

      您应该更改假期表的#tempHolidays,并使用@StarYear 和@EndYear 作为您的时间段。

      【讨论】:

        【解决方案4】:

        这是一个简单的演示,展示了使用 partition by 子句来保持非节假日排序的连续性

        IF OBJECT_ID('tempdb.dbo.#dates') IS NOT null
            DROP TABLE #dates;
        CREATE TABLE #dates (d DATE);
        
        IF OBJECT_ID('tempdb.dbo.#holidays') IS NOT null
            DROP TABLE #holidays;
        CREATE TABLE #holidays (d DATE);
        
        INSERT INTO [#holidays]
                ( [d] )
        VALUES
            ('2016-12-25'),
            ('2017-12-25'),
            ('2018-12-25');
        
        INSERT INTO [#dates]
                ( [d] )
        SELECT TOP 1000 DATEADD(DAY, n, '2015-12-31')
        FROM [Util].dbo.[Numbers] AS [n];
        
        
        WITH holidays AS (
            SELECT d.*, CASE WHEN h.d IS NULL THEN 0 ELSE 1 END AS [IsHoliday]
            FROM [#dates] AS [d]
            LEFT JOIN [#holidays] AS [h]
                ON [d].[d] = [h].[d]
        )
        SELECT d, ROW_NUMBER() OVER (PARTITION BY [holidays].[IsHoliday] ORDER BY d)
        FROM [holidays]
        ORDER BY d;
        

        请原谅我只将圣诞节标记为假期!

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-09-16
          • 1970-01-01
          • 1970-01-01
          • 2020-11-16
          • 2022-10-14
          • 2019-04-17
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多