Oracle:选择缺少日期

Dav*_*ill 4 sql oracle gaps-and-islands

我在一个字段中有一个表(包括其他内容)日期.

我需要获取所有日期的列表,这些日期比最早的日期更新,比最近的日期更早,并且在表格中完全丢失.

所以,如果表包含:

2012-01-02
2012-01-02
2012-01-03
2012-01-05
2012-01-05
2012-01-07
2012-01-08
Run Code Online (Sandbox Code Playgroud)

我想要一个返回的查询:

2012-01-04
2012-01-06
Run Code Online (Sandbox Code Playgroud)

a_h*_*ame 13

这样的事情(假设你的表被命名your_table并且日期列被命名the_date):

with date_range as (
      select min(the_date) as oldest, 
             max(the_date) as recent, 
             max(the_date) - min(the_date) as total_days
      from your_table
),
all_dates as (
   select oldest + level - 1 as a_date
   from date_range
   connect by level <= (select total_days from date_range)
)
select ad.a_date
from all_dates ad
  left join your_table yt on ad.a_date = yt.the_date
where yt.the_date is null
order by ad.a_date;  
Run Code Online (Sandbox Code Playgroud)

编辑:
WITH子句称为"公用表表达式",相当于派生表("内联视图").

它类似于

select * 
from ( 
     ..... 
) all_dates
join your_table ...
Run Code Online (Sandbox Code Playgroud)

第二个CTE使用Oracle connect by实现的未记录功能简单地"动态"创建日期列表.

重复使用select(就像我计算第一个和最后一个日期一样)比使用派生表更容易(和IMHO更易读).

编辑2:

这也可以通过递归CTE完成:

with date_range as (
      select min(the_date) as oldest, 
             max(the_date) as recent, 
             max(the_date) - min(the_date) as total_days
      from your_table
),
all_dates (a_date, lvl) as (
   select oldest as a_date, 1 as lvl
   from date_range 
   union all
   select (select oldest from date_range) + lvl, lvl + 1
   from all_dates 
   where lvl < (select total_days from date_range)
)
select ad.a_date, lvl
from all_dates ad    
  left join your_table yt on ad.a_date = yt.the_date
where yt.the_date is null
order by ad.a_date;  
Run Code Online (Sandbox Code Playgroud)

这应该适用于所有支持递归CTE的DBMS(PostgreSQL和Firebird - 更符合标准 - recursive尽管需要关键字).

注意select (select oldest from date_range) + lvl, lvl + 1递归部分中的hack .这不应该是必要的,但Oracle在递归CTE中仍有一些关于DATE的错误.在PostgreSQL中,以下工作没有问题:

....
all_dates (a_date, lvl) as (
   select oldest as a_date, 0 as lvl
   from date_range 
   union all
   select a_date + 1, lvl + 1
   from all_dates 
   where lvl < (select total_days from date_range)
)
....
Run Code Online (Sandbox Code Playgroud)