Примерно так (при условии, что ваша таблица называется 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;
Редактировать:
предложение WITH
называется «общим выражением таблицы» и эквивалентно производной таблице («встроенное представление»).
Это похоже на
select *
from (
.....
) all_dates
join your_table ...
Второй CTE просто создает список дат «на лету», используя недокументированную особенность реализации Oracle connect by
.
Повторное использование выбора (как я делал с вычислением первой и последней даты) немного проще (и ИМХО более читабельно), чем использование производных таблиц.
Редактировать 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;
Что должно работать во всех СУБД, поддерживающих рекурсивные CTE (PostgreSQL и Firebird - будучи более совместимыми со стандартами - все же нуждаются в ключевом слове recursive
).
Обратите внимание на взлом select (select oldest from date_range) + lvl, lvl + 1
в рекурсивной части. Это не должно быть необходимым, но у Oracle все еще есть некоторые ошибки в отношении DATE в рекурсивном CTE. В 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)
)
....