Oracle SQL:查找指定日期前 7 天的日期,不包括星期日和其他特定日期

Posted

技术标签:

【中文标题】Oracle SQL:查找指定日期前 7 天的日期,不包括星期日和其他特定日期【英文标题】:Oracle SQL: Find the date 7 days ahead of a specified date excluding Sundays and other specific dates 【发布时间】:2018-02-21 10:19:07 【问题描述】:

我需要找出比任何给定日期早 7 天的日期。开始日期可以是任何日期,但结束日期不应是星期日或标记为假日的日子。节假日只能连续 2 天。

我的代码可以运行,但是很冗长。是否有更简单的解决方案?

另外,如果要求的日期范围在未来某个时间点发生变化或可能的连续假期数发生变化,则脚本需要在多个地方更新,如果可以减少,那就太好了。

我已经有一个表格 (CALENDAR),其中包含大量日期、星期几以及该日期是否被视为假日。像这样的:

START_DATE, DAY_OF_WEEK, HOLIDAY
10-DEC-17 , Sun        , 0
11-DEC-17 , Mon        , 0
12-DEC-17 , Tue        , 0
13-DEC-17 , Wed        , 0
14-DEC-17 , Thu        , 0
15-DEC-17 , Fri        , 0
16-DEC-17 , Sat        , 0
17-DEC-17 , Sun        , 0
18-DEC-17 , Mon        , 0
19-DEC-17 , Tue        , 0
20-DEC-17 , Wed        , 0
21-DEC-17 , Thu        , 0
22-DEC-17 , Fri        , 0
23-DEC-17 , Sat        , 0
24-DEC-17 , Sun        , 0
25-DEC-17 , Mon        , 1
26-DEC-17 , Tue        , 1
27-DEC-17 , Wed        , 0
28-DEC-17 , Thu        , 0
29-DEC-17 , Fri        , 0
30-DEC-17 , Sat        , 0
31-DEC-17 , Sun        , 0
01-JAN-18 , Mon        , 1
etc...

预期的输出将类似于:

START_DATE, END_DATE
10-DEC-17,  18-DEC-17
11-DEC-17,  18-DEC-17
12-DEC-17,  19-DEC-17
13-DEC-17,  20-DEC-17
14-DEC-17,  21-DEC-17
15-DEC-17,  22-DEC-17
16-DEC-17,  23-DEC-17
17-DEC-17,  27-DEC-17
18-DEC-17,  27-DEC-17
19-DEC-17,  27-DEC-17
20-DEC-17,  27-DEC-17
21-DEC-17,  28-DEC-17
22-DEC-17,  29-DEC-17
23-DEC-17,  30-DEC-17
24-DEC-17,  02-JAN-18
25-DEC-17,  02-JAN-18
26-DEC-17,  02-JAN-18
27-DEC-17,  03-JAN-18
28-DEC-17,  04-JAN-18
etc...

以下是我现有的代码。我的方法是,由于只能连续排除 3 天(一个星期天,然后是 2 个假期),因此我会提前 4 天检查每个日期,然后选择第一个不是排除日期的日期。 4 个中的一个应始终是有效的结束日期。

with temp as
(
select
    start_date,
    case    
        when lead(day_of_week, 7) over(order by start_date) = 'Sun'
          or lead(holiday, 7) over(order by start_date) = 1
        then null
    else
        lead(start_date, 7) over(order by start_date)
    end as days_7,
    case    
        when lead(day_of_week, 8) over(order by start_date) = 'Sun'
          or lead(holiday, 8) over(order by start_date) = 1
        then null
    else
        lead(start_date, 8) over(order by start_date)
    end as days_8,
    case    
        when lead(day_of_week, 9) over(order by start_date) = 'Sun'
          or lead(holiday, 9) over(order by start_date) = 1
        then null
    else
        lead(start_date, 9) over(order by start_date)
    end as days_9,
        case    
        when lead(day_of_week, 10) over(order by start_date) = 'Sun'
          or lead(holiday, 10) over(order by start_date) = 1
        then null
    else
        lead(start_date, 10) over(order by start_date)
    end as days_10
from
    calendar
)

select
    start_date,
    COALESCE(days_7, days_8, days_9, days_10) as end_date 
from
    temp

【问题讨论】:

为此目的创建一个函数不是更直接吗? 【参考方案1】:

SQL Fiddle

Oracle 11g R2 架构设置

CREATE TABLE Calendar( START_DATE, HOLIDAY ) AS
SELECT TO_DATE( '10-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '11-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '12-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '13-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '14-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '15-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '16-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '17-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '18-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '19-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '20-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '21-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '22-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '23-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '24-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '25-DEC-17', 'DD-MON-YY' ), 1 FROM DUAL UNION ALL
SELECT TO_DATE( '26-DEC-17', 'DD-MON-YY' ), 1 FROM DUAL UNION ALL
SELECT TO_DATE( '27-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '28-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '29-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '30-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '31-DEC-17', 'DD-MON-YY' ), 0 FROM DUAL UNION ALL
SELECT TO_DATE( '01-JAN-18', 'DD-MON-YY' ), 1 FROM DUAL;

查询 1

SELECT start_date,
       LEAD( end_date, 7 ) OVER ( ORDER BY start_date ) AS end_date
FROM   (
  SELECT start_date,
         LAST_VALUE(
           CASE
           WHEN Holiday = 0
           AND  start_date - TRUNC( start_date, 'IW' ) < 6
           THEN start_date
           END
         ) IGNORE NULLS OVER ( ORDER BY start_date DESC ) AS End_date
  FROM   Calendar
)

Results

|           START_DATE |             END_DATE |
|----------------------|----------------------|
| 2017-12-10T00:00:00Z | 2017-12-18T00:00:00Z |
| 2017-12-11T00:00:00Z | 2017-12-18T00:00:00Z |
| 2017-12-12T00:00:00Z | 2017-12-19T00:00:00Z |
| 2017-12-13T00:00:00Z | 2017-12-20T00:00:00Z |
| 2017-12-14T00:00:00Z | 2017-12-21T00:00:00Z |
| 2017-12-15T00:00:00Z | 2017-12-22T00:00:00Z |
| 2017-12-16T00:00:00Z | 2017-12-23T00:00:00Z |
| 2017-12-17T00:00:00Z | 2017-12-27T00:00:00Z |
| 2017-12-18T00:00:00Z | 2017-12-27T00:00:00Z |
| 2017-12-19T00:00:00Z | 2017-12-27T00:00:00Z |
| 2017-12-20T00:00:00Z | 2017-12-27T00:00:00Z |
| 2017-12-21T00:00:00Z | 2017-12-28T00:00:00Z |
| 2017-12-22T00:00:00Z | 2017-12-29T00:00:00Z |
| 2017-12-23T00:00:00Z | 2017-12-30T00:00:00Z |
| 2017-12-24T00:00:00Z |               (null) |
| 2017-12-25T00:00:00Z |               (null) |
| 2017-12-26T00:00:00Z |               (null) |
| 2017-12-27T00:00:00Z |               (null) |
| 2017-12-28T00:00:00Z |               (null) |
| 2017-12-29T00:00:00Z |               (null) |
| 2017-12-30T00:00:00Z |               (null) |
| 2017-12-31T00:00:00Z |               (null) |
| 2018-01-01T00:00:00Z |               (null) |

【讨论】:

很好的解决方案,非常感谢。不过有一件事,我需要将 IGNORE NULLS 移动到 LAST_VALUE 函数的第一组括号内,以使其运行。否则,它会抛出“ORA-30484:缺少此函数的窗口规范”。看起来 LAST_VALUE 函数的语法在 Oracle 10 和 11 之间发生了变化。【参考方案2】:

给定假期表:

with holidays as ( 
select to_date('10-DEC-17','dd-MON-YY') as mydate , 'Sun' as myday, 0 holiday from dual
union all
select to_date('11-DEC-17','dd-MON-YY') as mydate , 'Mon' as myday, 0 holiday from dual
union all
select to_date('12-DEC-17','dd-MON-YY') as mydate , 'Tue' as myday, 0 holiday from dual
union all
select to_date('13-DEC-17','dd-MON-YY') as mydate , 'Wed' as myday, 0 holiday from dual
union all
select to_date('14-DEC-17','dd-MON-YY') as mydate , 'Thu' as myday, 0 holiday from dual
union all
select to_date('15-DEC-17','dd-MON-YY') as mydate , 'Fri' as myday, 0 holiday from dual
union all
select to_date('16-DEC-17','dd-MON-YY') as mydate , 'Sat' as myday, 0 holiday from dual
union all
select to_date('17-DEC-17','dd-MON-YY') as mydate , 'Sun' as myday, 0 holiday from dual
union all
select to_date('18-DEC-17','dd-MON-YY') as mydate , 'Mon' as myday, 0 holiday from dual
union all
select to_date('19-DEC-17','dd-MON-YY') as mydate , 'Tue' as myday, 0 holiday from dual
union all
select to_date('20-DEC-17','dd-MON-YY') as mydate , 'Wed' as myday, 0 holiday from dual
union all
select to_date('21-DEC-17','dd-MON-YY') as mydate , 'Thu' as myday, 0 holiday from dual
union all
select to_date('22-DEC-17','dd-MON-YY') as mydate , 'Fri' as myday, 0 holiday from dual
union all
select to_date('23-DEC-17','dd-MON-YY') as mydate , 'Sat' as myday, 0 holiday from dual
union all
select to_date('24-DEC-17','dd-MON-YY') as mydate , 'Sun' as myday, 0 holiday from dual
union all
select to_date('25-DEC-17','dd-MON-YY') as mydate , 'Mon' as myday, 1 holiday from dual
union all
select to_date('26-DEC-17','dd-MON-YY') as mydate , 'Tue' as myday, 1 holiday from dual
union all
select to_date('27-DEC-17','dd-MON-YY') as mydate , 'Wed' as myday, 0 holiday from dual
union all
select to_date('28-DEC-17','dd-MON-YY') as mydate , 'Thu' as myday, 0 holiday from dual
union all
select to_date('29-DEC-17','dd-MON-YY') as mydate , 'Fri' as myday, 0 holiday from dual
union all
select to_date('30-DEC-17','dd-MON-YY') as mydate , 'Sat' as myday, 0 holiday from dual
union all
select to_date('31-DEC-17','dd-MON-YY') as mydate , 'Sun' as myday, 0 holiday from dual
union all
select to_date('01-JAN-18','dd-MON-YY') as mydate , 'Mon' as myday, 1 holiday from dual),

在这里我找到工作日,然后使用第一行来获取所需的日期

working as (
select holidays.*, case when myday='Sun' or holiday=1 
 then 0 else 1 end as working_day  from holidays
)
select  mydate from (
select mydate from working where 
   mydate>=to_date('21/12/2017','dd/mm/rrrr')+7
   order by (case when working_day=0 then null  else mydate end) asc nulls last
) where rownum=1

【讨论】:

以上是关于Oracle SQL:查找指定日期前 7 天的日期,不包括星期日和其他特定日期的主要内容,如果未能解决你的问题,请参考以下文章

oracle 数据太多 想只显示当前日期前三天的数据 条件语句怎么写呢?

如何根据sql中组中的日期集获取前7天的数据

使用 oracle SQL 查找日期范围内的星期几

java中,根据指定日期显示出前n天的日期

SQL语句 如何取得指定月份的最后一天的日期

PHP获取指定日期