Как индексировать столбец даты с нулевыми значениями?
Как мне индексировать столбец даты, когда некоторые строки имеют нулевые значения?
Мы должны выбирать строки между диапазоном дат и строками с нулевыми датами.
Мы используем Oracle 9.2 и выше.
Параметры, которые я нашел
- Использование растрового индекса в столбце даты
- Использование столбца index on date и индекса в поле состояния, значение которого равно 1, когда дата равна null
- Использование столбца index on date и другого предоставленного столбца без нуля
Мои мысли о вариантах:
до 1: таблица должна иметь множество разных значений, чтобы использовать битмап-индекс
до 2: я должен добавить поле только для этой цели и изменить запрос, когда я хочу получить нулевые строки строк
до 3: блокировки сложны, чтобы добавить поле к индексу, который действительно не нужен
Какова наилучшая практика для этого случая?
Спасибо заранее
Некоторая информация, которую я прочитал:
Индекс даты Oracle
Когда Oracle индексирует значения нулевого столбца?
Изменить
В нашей таблице 300 000 записей. От 1000 до 10 000 записей вставляются и удаляются каждый день. В 280 000 записей указана нулевая дата доставки. Это своего рода буфер сбора.
Наша структура (на английском языке):
create table orders
(
orderid VARCHAR2(6) not null,
customerid VARCHAR2(6) not null,
compartment VARCHAR2(8),
externalstorage NUMBER(1) default 0 not null,
created_at DATE not null,
last_update DATE not null,
latest_delivery DATE not null,
delivered_at DATE,
delivery_group VARCHAR2(9),
fast_order NUMBER(1) default 0 not null,
order_type NUMBER(1) default 0 not null,
produkt_group VARCHAR2(30)
)
Ответы
Ответ 1
"В нашей таблице 300 000 записей... 280 000 записей имеют нулевое значение Дата доставки."
Иными словами, почти вся таблица удовлетворяет запросу, который ищет, где DELIVERED_AT имеет значение NULL. Индекс совершенно неуместен для этого поиска. Полное сканирование таблицы - лучший подход.
Если у вас есть лицензия Enterprise Edition, и у вас есть запасные части процессоров, использование параллельного запроса уменьшит время, прошедшее через.
Ответ 2
В дополнение к отличным советам Тони есть также возможность индексировать колонку таким образом, что вам не нужно настраивать свои запросы. Хитрость заключается в том, чтобы добавить постоянное значение только к вашему индексу.
Демонстрация:
Создайте таблицу с 10 000 строк, из которых только 6 содержат значение NULL для столбца a_date.
SQL> create table mytable (id,a_date,filler)
2 as
3 select level
4 , case when level < 9995 then date '1999-12-31' + level end
5 , lpad('*',1000,'*')
6 from dual
7 connect by level <= 10000
8 /
Table created.
Сначала я покажу, что если вы просто создаете индекс в столбце a_date, индекс не будет использоваться, если вы используете предикат "где a_date равно null":
SQL> create index i1 on mytable (a_date)
2 /
Index created.
SQL> exec dbms_stats.gather_table_stats(user,'mytable',cascade=>true)
PL/SQL procedure successfully completed.
SQL> set autotrace on
SQL> select id
2 , a_date
3 from mytable
4 where a_date is null
5 /
ID A_DATE
---------- -------------------
9995
9996
9997
9998
9999
10000
6 rows selected.
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=72 Card=6 Bytes=72)
1 0 TABLE ACCESS (FULL) OF 'MYTABLE' (Cost=72 Card=6 Bytes=72)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
720 consistent gets
0 physical reads
0 redo size
285 bytes sent via SQL*Net to client
234 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
6 rows processed
720 последовательных запросов и полное сканирование таблицы.
Теперь измените индекс на константу 1 и повторите тест:
SQL> set autotrace off
SQL> drop index i1
2 /
Index dropped.
SQL> create index i1 on mytable (a_date,1)
2 /
Index created.
SQL> exec dbms_stats.gather_table_stats(user,'mytable',cascade=>true)
PL/SQL procedure successfully completed.
SQL> set autotrace on
SQL> select id
2 , a_date
3 from mytable
4 where a_date is null
5 /
ID A_DATE
---------- -------------------
9995
9996
9997
9998
9999
10000
6 rows selected.
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=6 Bytes=72)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'MYTABLE' (Cost=2 Card=6 Bytes=72)
2 1 INDEX (RANGE SCAN) OF 'I1' (NON-UNIQUE) (Cost=2 Card=6)
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
6 consistent gets
0 physical reads
0 redo size
285 bytes sent via SQL*Net to client
234 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
6 rows processed
6 согласованных результатов и сканирование диапазона индексов.
С уважением,
Роб.
Ответ 3
Вы имеете в виду, что ваши запросы будут такими?
select ...
from mytable
where (datecol between :from and :to
or datecol is null);
Было бы целесообразно индексировать нули, если они были относительно небольшими в таблице - в противном случае полное сканирование таблицы может быть наиболее эффективным способом их поиска. Предполагая, что стоит их индексировать, вы можете создать индекс на основе функций следующим образом:
create index mytable_fbi on mytable (case when datecol is null then 1 end);
Затем измените свой запрос на:
select ...
from mytable
where (datecol between :from and :to
or case when datecol is null then 1 end = 1);
Вы можете обернуть случай в функцию, чтобы сделать ее slicker:
create or replace function isnull (p_date date) return varchar2
DETERMINISTIC
is
begin
return case when p_date is null then 'Y' end;
end;
/
create index mytable_fbi on mytable (isnull(datecol));
select ...
from mytable
where (datecol between :from and :to
or isnull(datecol) = 'Y');
Я убедился, что функция возвращает NULL, когда дата не равна null, так что в индексе хранятся только нулевые даты. Также мне пришлось объявить функцию DETERMINISTIC. (Я изменил его, чтобы вернуть "Y" вместо 1 просто потому, что для меня это имя "isnull" предполагает, что это должно быть, не стесняйтесь игнорировать мои предпочтения!)
Ответ 4
Избегайте поиска в таблице и создайте индекс следующим образом:
create index i1 on mytable (a_date,id) ;