oracle 如何索引具有空值的日期列?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3068730/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-18 20:51:32  来源:igfitidea点击:

How to index a date column with null values?

oracleoptimizationindexingnull

提问by Heinz Z.

How should I index a date column when some rows has null values? We have to select rows between a date range and rows with null dates.

当某些行具有空值时,我应该如何索引日期列?我们必须在日期范围和具有空日期的行之间选择行。

We use Oracle 9.2 and higher.

我们使用 Oracle 9.2 及更高版本。

Options I found

我找到的选项

  1. Using a bitmap index on the date column
  2. Using an index on date column and an index on a state field which value is 1 when the date is null
  3. Using an index on date column and an other granted not null column
  1. 在日期列上使用位图索引
  2. 使用日期列上的索引和状态字段上的索引,当日期为空时值为 1
  3. 在日期列和其他授予的非空列上使用索引

My thoughts to the options are:

我对选项的想法是:

to 1: the table have to many different values to use an bitmap index
to 2: I have to add an field only for this purpose and to change the query when I want to retrieve the null date rows
to 3: locks tricky to add an field to an index which is not really needed

到 1:表必须有许多不同的值才能使用位图索引
到 2:我必须仅为此目的添加一个字段,并在我想检索空日期行时更改查询
3:添加一个锁很棘手字段到不是真正需要的索引

What is the best practice for this case? Thanks in advance

这种情况下的最佳做法是什么?提前致谢

Some infos I have read:

我读过的一些信息:

Oracle Date Index
When does Oracle index null column values?

Oracle 日期索引 Oracle
何时索引空列值?

Edit

编辑

Our table has 300,000 records. 1,000 to 10,000 records are inserted and delete every day. 280,000 records have a null delivered_at date. It is a kind of picking buffer.

我们的表有 300,000 条记录。每天插入和删除 1,000 到 10,000 条记录。280,000 条记录的 delivery_at 日期为空。它是一种拣选缓冲区。

Our structure (translated to english) is:

我们的结构(翻译成英文)是:

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)
)

采纳答案by APC

"Our table has 300,000 records.... 280,000 records have a null delivered_at date. "

“我们的表有 300,000 条记录……280,000 条记录的 Deliver_at 日期为空。”

In other words almost the entire table satisfies a query which searches on where DELIVERED_AT is null. An index is completely inappropriate for that search. A full table scan is much the best approach.

换句话说,几乎整个表都满足一个查询,该查询搜索 DELIVERED_AT 在哪里为空。索引完全不适合该搜索。全表扫描是最好的方法。

If you have an Enterprise Edition license and you have the CPUs to spare, using a parallel query would reduce the elapsed time.

如果您拥有企业版许可证并且有空闲的 CPU,则使用并行查询会减少经过的时间。

回答by Rob van Wijk

In addition to Tony's excellent advice, there is also an option to index your column in such a way that you don't need to adjust your queries. The trick is to add a constant value to just your index.

除了 Tony 的出色建议之外,还有一个选项可以以无需调整查询的方式为您的列编制索引。诀窍是为您的索引添加一个常量值。

A demonstration:

演示:

Create a table with 10,000 rows out of which only 6 contain a NULL value for the a_date column.

创建一个包含 10,000 行的表,其中只有 6 行包含 a_date 列的 NULL 值。

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.

First I'll show that if you just create an index on the a_date column, the index is not used when you use the predicate "where a_date is null":

首先,我将说明,如果您只是在 a_date 列上创建一个索引,那么当您使用谓词“where a_date is 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 consistent gets and a full table scan.

720 一致获取和全表扫描。

Now change the index to include the constant 1, and repeat the test:

现在更改索引以包含常量 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 consistent gets and an index range scan.

6 个一致的获取和索引范围扫描。

Regards, Rob.

问候,罗伯。

回答by Tony Andrews

Do you mean that your queries will be like this?

你的意思是你的查询会是这样吗?

select ...
from mytable
where (datecol between :from and :to
       or datecol is null);

It would only be worth indexing the nulls if they were relatively few in the table - otherwise a full table scan may be the most efficient way to find them. Assuming it is worth indexing them you could create a function-based index like this:

只有当空值在表中相对较少时才值得索引它们 - 否则全表扫描可能是找到它们的最有效方法。假设值得索引它们,您可以创建一个基于函数的索引,如下所示:

create index mytable_fbi on mytable (case when datecol is null then 1 end);

Then change your query to:

然后将您的查询更改为:

select ...
from mytable
where (datecol between :from and :to
       or case when datecol is null then 1 end = 1);

You could wrap the case in a function to make it 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');

I made sure the function returns NULL when the date is not null so that only the null dates are stored in the index. Also I had to declare the function as DETERMINISTIC. (I changed it to return 'Y' instead of 1 merely because to me the name "isnull" suggests it should; feel free to ignore my preference!)

我确保该函数在日期不为空时返回 NULL,以便只有空日期存储在索引中。此外,我必须将该函数声明为 DETERMINISTIC。(我将其更改为返回 'Y' 而不是 1 仅仅是因为对我来说,名称“isnull”表明它应该这样做;请随意忽略我的偏好!)

回答by marcuoli

Avoid the table lookup and create the index like this :

避免表查找并像这样创建索引:

create index i1 on mytable (a_date,id) ;