通过对字段进行分组来折叠日历表

时间:2018-07-11 16:51:03

标签: sql tsql calendar

我有一个脚本,可以生成日历表,并填写英格兰和威尔士的公共假日和周末。我希望将非工作日作为开始和结束时间,因此请输入

1980-04-01 00:00:00.000 Tuesday         0
1980-04-02 00:00:00.000 Wednesday   0
1980-04-03 00:00:00.000 Thursday    0
1980-04-04 00:00:00.000 Friday          1
1980-04-05 00:00:00.000 Saturday    1
1980-04-06 00:00:00.000 Sunday          1
1980-04-07 00:00:00.000 Monday          1
1980-04-08 00:00:00.000 Tuesday         0
1980-04-09 00:00:00.000 Wednesday   0

它将给出输出

Start      end
1980-04-03 1980-04-08

换句话说,是组中第一个非工作日的前一天和最后一个非工作日的后一天。要做到这一点,我想可以将min和max与group和dateadd一起使用,但是如何获得一个唯一的数字来对每组天进行分组?这是使用2008 R2。

    IF OBJECT_ID('tempdb.dbo.#calendar', 'U') IS NOT NULL
  DROP TABLE #calendar
  IF OBJECT_ID('tempdb.dbo.#easter', 'U') IS NOT NULL
  DROP TABLE #easter; 


-- CREATE TABLE CALENDAR

CREATE TABLE #calendar
(
    [CalendarDate] DATETIME,
       dayofwk varchar(20),
)

DECLARE @StartDate DATETIME
DECLARE @EndDate DATETIME




--INPUTS GO HERE
SET @StartDate = '01-01-1980'
SET @EndDate = '31-12-2018'


DECLARE @Startyear int
DECLARE @endyear int
set @startyear = YEAR(@StartDate)
set @endyear = YEAR(@EndDate)

WHILE @StartDate <= @EndDate
      BEGIN
             INSERT INTO #calendar
             (
                   CalendarDate, dayofwk
             )
             SELECT
                   @StartDate, datename(dw,@startdate)

             SET @StartDate = DATEADD(dd, 1, @StartDate)
      END



---CREATE LIST OF EASTER MONDAY & GOOD FRIDAYS

create table #easter(
eastersunday_goodfriday  date)


DECLARE     @y int,
@EpactCalc INT,  
        @PaschalDaysCalc INT, 
        @NumOfDaysToSunday INT, 
        @EasterMonth INT, 
        @EasterDay INT


WHILE @Startyear <= @endyear
      BEGIN

    SET @y = @startyear
    SET @EpactCalc = (24 + 19 * (@Y % 19)) % 30 
    SET @PaschalDaysCalc = @EpactCalc - (@EpactCalc / 28) 
    SET @NumOfDaysToSunday = @PaschalDaysCalc - ( 
        (@Y + @Y / 4 + @PaschalDaysCalc - 13) % 7 
    ) 

    SET @EasterMonth = 3 + (@NumOfDaysToSunday + 40) / 44 

    SET @EasterDay = @NumOfDaysToSunday + 28 - ( 
        31 * (@EasterMonth / 4) 
    ) 

       insert into #easter

       SELECT dateadd(d,-2, CONVERT 
        (  SMALLDATETIME, 
                 RTRIM(@Y)  
            + RIGHT('0'+RTRIM(@EasterMonth), 2)  
            + RIGHT('0'+RTRIM(@EasterDay), 2)  ))

insert into #easter

       SELECT dateadd(d,1, CONVERT 
        (  SMALLDATETIME, 
                 RTRIM(@Y)  
            + RIGHT('0'+RTRIM(@EasterMonth), 2)  
            + RIGHT('0'+RTRIM(@EasterDay), 2)  ))

             SET @Startyear =@Startyear +1

                     end




select calendardate, dayofwk,
--NEW YEAR'S DAY
case
when day(calendardate) = 1 and month(calendardate) = 1 and dayofwk not in ('Saturday', 'sunday') then 1
when day(calendardate) between 2 and 3 and month(calendardate) = 1 and dayofwk = 'Monday' then 1

--GOOD FRIDAY, EASTER MONDAY
WHEN eastersunday_goodfriday IS NOT NULL THEN 1

--EARLY MAY BANK HOLIDAY
WHEN (datepart(weekday, CALENDARDATE) + @@DATEFIRST - 2) % 7 + 1 = 1   
and (datepart(day, CALENDARDATE) - 1) / 7 + 1 = 1 
AND MONTH(CALENDARDATE) = 5 
THEN 1

--LATE MAY BANK HOLIDAY
WHEN (datepart(weekday, CALENDARDATE) + @@DATEFIRST - 2) % 7 + 1 = 1   
and (datepart(day, CALENDARDATE) - 1) / 7 + 1 = 4 
AND MONTH(CALENDARDATE) = 5 
THEN 1

--LATE AUGUST BANK HOLIDAY
WHEN (datepart(weekday, CALENDARDATE) + @@DATEFIRST - 2) % 7 + 1 = 1   
and (datepart(day, CALENDARDATE) - 1) / 7 + 1 = 4 
AND MONTH(CALENDARDATE) = 8 
THEN 1

--CHRISTMAS DAY
WHEN DAY(CalendarDate) = 25 AND MONTH(CALENDARDATE) = 12 AND dayofwk NOT IN ('SATURDAY', 'SUNDAY') THEN 1 

--BOXING DAY
WHEN DAY(CalendarDate) = 26 AND MONTH(CALENDARDATE) = 12 AND dayofwk NOT IN ('SATURDAY', 'SUNDAY') THEN 1 
WHEN DAY(CalendarDate) between 27 and 28 AND MONTH(CALENDARDATE) = 12 AND DAYOFWK IN ('MONDAY','TUESDAY') THEN 1 

--SAT&SUN
WHEN DATENAME(DW,CALENDARDATE) IN ('SATURDAY','SUNDAY') THEN 1


ELSE 0 
end as ISNONWORKINGDAY 
                     from #calendar c 
                     left join #easter e 
                     on c.calendardate = e.eastersunday_goodfriday
                     ORDER BY C.CALENDARDATE

2 个答案:

答案 0 :(得分:0)

此接缝就像一个典型的岛屿问题。鉴于您发布的内容,您可以执行以下操作:

DECLARE @table TABLE (dt DATE, isNonWorkDay BIT);
INSERT @table(dt,isNonWorkDay)
VALUES
('1980-04-01', 0),
('1980-04-02', 0),
('1980-04-03', 0),
('1980-04-04', 1),
('1980-04-05', 1),
('1980-04-06', 1),
('1980-04-07', 1),
('1980-04-08', 0),
('1980-04-09', 0);

SELECT DATEADD(DAY,-1,MIN(t.dt)), DATEADD(DAY,1,MAX(t.dt))
FROM @table t
WHERE t.isNonWorkDay = 1;

返回:1980-04-03、1980-04-08

对于多个岛屿,您可以执行以下操作:

DECLARE @table TABLE (dt DATE, isNonWorkDay BIT);
INSERT @table(dt,isNonWorkDay)
VALUES
('1980-04-01', 0),
('1980-04-02', 0),
('1980-04-03', 0),
('1980-04-04', 1),
('1980-04-05', 1),
('1980-04-06', 1),
('1980-04-07', 1),
('1980-04-08', 0),
('1980-04-09', 0),
('1980-04-10', 0),
('1980-04-11', 1),
('1980-04-12', 1),
('1980-04-13', 1),
('1980-04-14', 0),
('1980-04-15', 0);

WITH x AS
(
  SELECT *, rn = ROW_NUMBER() OVER (ORDER BY t.dt)
  FROM @table t
),
xx AS 
(
  SELECT *, grouper = x.rn - ROW_NUMBER() OVER (ORDER BY x.dt)
  FROM x
  WHERE x.isNonWorkDay = 1
)
SELECT dtStart = DATEADD(DAY,-1,MIN(xx.dt)),  dtend = DATEADD(DAY,1,MAX(xx.dt))
FROM xx
GROUP BY xx.grouper;

返回:

dtStart    dtend
---------- ----------
1980-04-03 1980-04-08
1980-04-10 1980-04-14

答案 1 :(得分:0)

您可以使用全天连续编号与所有非工作日连续编号之差来进行分组。 对于连续的一组非工作日,这些差异将具有相同的值,并且由于各组之间的间隔(工作日),下一组将具有不同的(较高)值。

对于全天编号,您可以使用DATEDIFF,对于非工作日编号,您可以使用ROW_NUMBER。查询看起来可能像这样:

WITH
  numbering (calendardate, grp_num) AS (
    SELECT 
      calendardate,
      DATEDIFF(day, 0, calendardate) - ROW_NUMBER() OVER (ORDER BY calendardate)
    FROM YourTable WHERE ISNONWORKINGDAY = 1
  )
SELECT
    DATEADD(day, -1, MIN(calendardate)) AS StartDate,
    DATEADD(day, +1, MAX(calendardate)) AS EndDate
FROM numbering GROUP BY grp_num;