2012-03-06 3 views
3

У меня есть таблица с (помимо прочего) датами в поле.Oracle: выберите отсутствующие даты

Мне нужно получить список всех дат, которые являются более поздними, чем самая старая дата, старше самой последней даты и полностью отсутствуют в таблице.

Таким образом, если таблица содержала:

2012-01-02 
2012-01-02 
2012-01-03 
2012-01-05 
2012-01-05 
2012-01-07 
2012-01-08 

Я хочу, чтобы запрос, который возвращает:

2012-01-04 
2012-01-06 
+0

Я думаю, вам придется использовать курсор, чтобы сделать это (в сочетании с SQL, что группы по дате). – theglauber

+0

Нужно ли быть одним запросом или может быть серией инструкций pl/sql? –

+0

Я бы предпочел один запрос, но это может быть сценарий, если это возможно. –

ответ

11

Что-то вроде этого (предполагается, что таблица называется 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; 

Edit:
Предложение WITH называется «общим табличным выражением» и эквивалентно производной таблице («встроенный вид»).

Это похоже на

select * 
from ( 
    ..... 
) all_dates 
join your_table ... 

Второй КТР просто создает список дат «на лету», используя недокументированную особенность connect by реализации Oracle.

Повторное использование select (как и при вычислении первой и последней даты) немного проще (и IMHO более читаемо), чем с использованием производных таблиц.

Edit 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; 

Который должен работать во всех СУБД, поддерживающих рекурсивный КТР (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) 
) 
.... 
+1

Спасибо! Если вы не возражаете редактировать более общие сведения о том, как работает этот запрос (что делает «с» и т. Д.), Это, безусловно, будет полезно для моего обучения, а также для всех, кто приходит. Но теперь я могу сам это сделать, и знаю, что искать. Опять же, спасибо! –

+0

@DavidOneill: see my edit –

+0

@a_horse Я не видел двойную таблицу, и я думал, что общее выражение таблицы - это вещь SQL Server, а не стандартный SQL. –

0

Вам нужен Calendar таблицу (постоянного или создаются на лету). Тогда вы могли бы сделать простой:

SELECT c.my_date 
FROM 
     calendar c 
    JOIN 
     (SELECT MIN(date_column) AS min_date 
       , MAX(date_column) AS max_date 
      FROM tableX 
     ) mm 
     ON c.mydate BETWEEN min_date AND max_date 
WHERE 
    c.my_date NOT IN 
    (SELECT date_column 
     FROM tableX 
    ) 
1

я выбрал бы этот вариант, потому что это более эффективно:

with all_dates_wo_boundary_values as 
(select oldest + level the_date 
    from (select min(the_date) oldest 
       , max(the_date) recent 
      from your_table 
     ) 
connect by level <= recent - oldest - 1 
) 
select the_date 
    from all_dates_wo_boundary_values 
minus 
select the_date 
    from your_table 

А вот некоторые доказательства.
Первая установка:

SQL> create table your_table (the_date) 
    2 as 
    3 select date '2012-01-02' from dual union all 
    4 select date '2012-01-02' from dual union all 
    5 select date '2012-01-03' from dual union all 
    6 select date '2012-01-05' from dual union all 
    7 select date '2012-01-05' from dual union all 
    8 select date '2012-01-07' from dual union all 
    9 select date '2012-01-08' from dual 
10/

Table created. 

SQL> exec dbms_stats.gather_table_stats(user,'your_table') 

PL/SQL procedure successfully completed. 

SQL> alter session set statistics_level = all 
    2/

Session altered. 

запрос Коня:

SQL> with date_range as 
    2 (select min(the_date) as oldest 
    3   , max(the_date) as recent 
    4   , max(the_date) - min(the_date) as total_days 
    5  from your_table 
    6 ) 
    7 , all_dates as 
    8 (select (select oldest from date_range) + level as a_date 
    9  from dual 
10 connect by level <= (select total_days from date_range) 
11 ) 
12 select ad.a_date 
13 from all_dates ad 
14   left join your_table yt on ad.a_date = yt.the_date 
15 where yt.the_date is null 
16 order by ad.a_date 
17/

A_DATE 
------------------- 
04-01-2012 00:00:00 
06-01-2012 00:00:00 

2 rows selected. 

SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last')) 
    2/

PLAN_TABLE_OUTPUT 
-------------------------------------------------------------------------------------------------------------------------------------- 
SQL_ID gaqx49vb9gz9k, child number 0 
------------------------------------- 
with date_range as (select min(the_date) as oldest  , max(the_date) as recent  , max(the_date) - min(the_date) as total_d 
ays  from your_table) 

, all_dates as (select (select oldest from date_range) + level as a_date  from dual 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 

Plan hash value: 1419150012 

------------------------------------------------------------------------------------------------------------------------------------------------------------------------  
| Id | Operation       | Name      | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads | Writes | OMem | 1Mem | Used-Mem | 
------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 
| 1 | TEMP TABLE TRANSFORMATION  |        |  1 |  |  2 |00:00:00.01 |  22 |  1 | 1 |  |  |   | 
| 2 | LOAD AS SELECT     |        |  1 |  |  1 |00:00:00.01 |  7 |  0 | 1 | 262K| 262K| 262K (0)| 
| 3 | SORT AGGREGATE     |        |  1 |  1 |  1 |00:00:00.01 |  3 |  0 | 0 |  |  |   | 
| 4 |  TABLE ACCESS FULL    | YOUR_TABLE     |  1 |  7 |  7 |00:00:00.01 |  3 |  0 | 0 |  |  |   | 
| 5 | SORT ORDER BY     |        |  1 |  1 |  2 |00:00:00.01 |  12 |  1 | 0 | 2048 | 2048 | 2048 (0)| 
|* 6 | FILTER       |        |  1 |  |  2 |00:00:00.01 |  12 |  1 | 0 |  |  |   | 
|* 7 |  HASH JOIN OUTER    |        |  1 |  1 |  7 |00:00:00.01 |  12 |  1 | 0 | 1048K| 1048K| 707K (0)| 
| 8 |  VIEW       |        |  1 |  1 |  6 |00:00:00.01 |  9 |  1 | 0 |  |  |   | 
| 9 |  CONNECT BY WITHOUT FILTERING|        |  1 |  |  6 |00:00:00.01 |  3 |  0 | 0 |  |  |   | 
| 10 |  FAST DUAL     |        |  1 |  1 |  1 |00:00:00.01 |  0 |  0 | 0 |  |  |   | 
| 11 |  VIEW      |        |  1 |  1 |  1 |00:00:00.01 |  3 |  0 | 0 |  |  |   | 
| 12 |   TABLE ACCESS FULL   | SYS_TEMP_0FD9D660C_81240964 |  1 |  1 |  1 |00:00:00.01 |  3 |  0 | 0 |  |  |   | 
| 13 |  TABLE ACCESS FULL   | YOUR_TABLE     |  1 |  7 |  7 |00:00:00.01 |  3 |  0 | 0 |  |  |   | 
------------------------------------------------------------------------------------------------------------------------------------------------------------------------ 


Predicate Information (identified by operation id): 
--------------------------------------------------- 

    6 - filter("YT"."THE_DATE" IS NULL) 
    7 - access("YT"."THE_DATE"=INTERNAL_FUNCTION("AD"."A_DATE")) 


32 rows selected. 

И мое предложение:

SQL> with all_dates_wo_boundary_values as 
    2 (select oldest + level the_date 
    3  from (select min(the_date) oldest 
    4     , max(the_date) recent 
    5    from your_table 
    6   ) 
    7 connect by level <= recent - oldest - 1 
    8 ) 
    9 select the_date 
10 from all_dates_wo_boundary_values 
11 minus 
12 select the_date 
13 from your_table 
14/

THE_DATE 
------------------- 
04-01-2012 00:00:00 
06-01-2012 00:00:00 

2 rows selected. 

SQL> select * from table(dbms_xplan.display_cursor(null,null,'allstats last')) 
    2/

PLAN_TABLE_OUTPUT 
-------------------------------------------------------------------------------------------------------------------------------------- 
SQL_ID 7aavxmzkj7zq7, child number 0 
------------------------------------- 
with all_dates_wo_boundary_values as (select oldest + level the_date  from (select min(the_date) oldest 
    , max(the_date) recent    from your_table   ) connect by level <= recent - oldest - 1) select 
the_date from all_dates_wo_boundary_values minus select the_date from your_table 

Plan hash value: 2293301832 

----------------------------------------------------------------------------------------------------------------------------------- 
| Id | Operation      | Name  | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem | 
----------------------------------------------------------------------------------------------------------------------------------- 
| 1 | MINUS       |   |  1 |  |  2 |00:00:00.01 |  6 |  |  |   | 
| 2 | SORT UNIQUE     |   |  1 |  1 |  5 |00:00:00.01 |  3 | 9216 | 9216 | 8192 (0)| 
| 3 | VIEW       |   |  1 |  1 |  5 |00:00:00.01 |  3 |  |  |   | 
| 4 |  CONNECT BY WITHOUT FILTERING|   |  1 |  |  5 |00:00:00.01 |  3 |  |  |   | 
| 5 |  VIEW      |   |  1 |  1 |  1 |00:00:00.01 |  3 |  |  |   | 
| 6 |  SORT AGGREGATE   |   |  1 |  1 |  1 |00:00:00.01 |  3 |  |  |   | 
| 7 |  TABLE ACCESS FULL  | YOUR_TABLE |  1 |  7 |  7 |00:00:00.01 |  3 |  |  |   | 
| 8 | SORT UNIQUE     |   |  1 |  7 |  5 |00:00:00.01 |  3 | 9216 | 9216 | 8192 (0)| 
| 9 | TABLE ACCESS FULL   | YOUR_TABLE |  1 |  7 |  7 |00:00:00.01 |  3 |  |  |   | 
----------------------------------------------------------------------------------------------------------------------------------- 


22 rows selected. 

С уважением,
Роб.

1

Мы можем использовать простой иерархический запрос, как показано ниже:

WITH CTE AS 
(SELECT (SELECT MIN(COL1) FROM T)+LEVEL-1 AS OUT FROM DUAL 
CONNECT BY (LEVEL-1) <= (SELECT MAX(COL1) - MIN(COL1) FROM T)) 
SELECT OUT FROM CTE WHERE OUT NOT IN (SELECT COL1 FROM T); 
Смежные вопросы