如何为具有空值的日期列建立索引?

发布于 2024-09-06 18:54:08 字数 1274 浏览 2 评论 0原文

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

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

我找到的选项

  1. 在日期列上使用位图索引
  2. 在日期列上使用索引,在状态字段上使用索引,当日期为空时,状态字段的值为 1
  3. 在日期列上使用索引,并在其他授予的非空列上使用索引

我的想法选项有:

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

这种情况的最佳实践是什么? 提前致谢

我读过的一些信息:

Oracle Date Index
Oracle何时索引空列值?

编辑

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

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

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

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.

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

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

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?

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.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

那些过往 2024-09-13 18:54:08

除了托尼的出色建议之外,还有一个选项可以为您的列建立索引,这样您就无需调整查询。诀窍是仅向索引添加一个常量值。

演示:

创建一个包含 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.

首先,我将展示如果您只是在 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 一致获取和全表扫描。

现在更改索引以包含常量 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 次一致获取和索引范围扫描。

问候,
抢。

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.

A demonstration:

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

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":

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.

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

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.

Regards,
Rob.

对不⑦ 2024-09-13 18:54:08

“我们的表有 300,000 条记录......
280,000 条记录为空
交付日期。 ”

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

如果您有企业版许可证并且有 CPU为了节省时间,使用并​​行查询会减少花费的时间。

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

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.

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

白馒头 2024-09-13 18:54:08

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

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

您可以将案例包装在函数中以使其更平滑:

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,所以仅空日期存储在索引中。我还必须将该函数声明为 DETERMINISTIC。 (我将其更改为返回“Y”而不是 1,仅仅是因为对我来说名称“isnull”表明它应该这样做;请随意忽略我的偏好!)

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

二智少女 2024-09-13 18:54:08

避免表查找并创建索引,如下所示:

create index i1 on mytable (a_date,id) ;

Avoid the table lookup and create the index like this :

create index i1 on mytable (a_date,id) ;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文