各列のインデックスを作成し、次のクエリを使用します。
select start_num
from my_table
where
start_num =
(
--Last start <= number
select start_num
from
(
select start_num
from my_table
where :1 >= start_num
order by start_num desc
)
where rownum = 1
) and
end_num =
(
--First end >= number
select end_num
from
(
select end_num
from my_table
where :1 <= end_num
order by end_num
)
where rownum = 1
);
うん。これを書くにはもっと良い方法があるかもしれません。または、これを関数でラップすることもできます。
問題
テスト データ (予約語以外の列名を使用):
drop table my_table;
create table my_table(
start_num int,
end_num int
);
insert into my_table select level*2,level*2+1 from dual connect by level <= 1000000;
commit;
create index my_table_index on my_table(start_num, end_num);
begin
dbms_stats.gather_table_stats(user, 'MY_TABLE', no_invalidate => false);
end;
/
低い数値はほぼ瞬時 - 0.015 秒
select start_num from my_table where 2 between start_num and end_num;
数値が大きいほど遅くなります - 0.125 秒
select start_num from my_table where 1000000 between start_num and end_num;
範囲スキャンと全テーブル スキャンの間には 1 つのポイントがあります。
explain plan for select start_num from my_table where 402741 between start_num and end_num;
select * from table(dbms_xplan.display);
Plan hash value: 3804444429
------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 160K| 1570K| 622 (2)| 00:00:08 |
|* 1 | TABLE ACCESS FULL| MY_TABLE | 160K| 1570K| 622 (2)| 00:00:08 |
------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("START_NUM"<=402742 AND "END_NUM">=402742)
explain plan for select start_num from my_table where 402742 between start_num and end_num;
select * from table(dbms_xplan.display);
Plan hash value: 3804444429
------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 160K| 1570K| 622 (2)| 00:00:08 |
|* 1 | TABLE ACCESS FULL| MY_TABLE | 160K| 1570K| 622 (2)| 00:00:08 |
------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("START_NUM"<=402742 AND "END_NUM">=402742)
しかし問題は、Oracle がインデックスを使用しないことではありません。素朴な方法でインデックスを使用しても役に立ちません。実際、これはさらに遅く、0.172 秒です。
select /*+ index(my_table my_table_index) */ start_num
from my_table
where 1000000 between start_num and end_num;
解決
新しいインデックスを作成します。
drop index my_table_index;
create index my_table_index1 on my_table(start_num);
create index my_table_index2 on my_table(end_num);
begin
dbms_stats.gather_table_stats(user, 'MY_TABLE', no_invalidate => false);
end;
/
任意の数に対して、結果は再び瞬時に得られます。
select start_num
from my_table
where
start_num =
(
--Last start <= number
select start_num
from
(
select start_num
from my_table
where 1000000 >= start_num
order by start_num desc
)
where rownum = 1
) and
end_num =
(
--First end >= number
select end_num
from
(
select end_num
from my_table
where 1000000 <= end_num
order by end_num
)
where rownum = 1
);
計画は素晴らしいようです - これはおそらくあなたが得ることができる最高のパフォーマンスです.
Plan hash value: 522166032
--------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 10 | 10 (0)| 00:00:01 |
|* 1 | TABLE ACCESS BY INDEX ROWID | MY_TABLE | 1 | 10 | 4 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | MY_TABLE_INDEX2 | 1 | | 3 (0)| 00:00:01 |
|* 3 | COUNT STOPKEY | | | | | |
| 4 | VIEW | | 3 | 39 | 3 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | MY_TABLE_INDEX2 | 3 | 18 | 3 (0)| 00:00:01 |
|* 6 | COUNT STOPKEY | | | | | |
| 7 | VIEW | | 2 | 26 | 3 (0)| 00:00:01 |
|* 8 | INDEX RANGE SCAN DESCENDING| MY_TABLE_INDEX1 | 500K| 2929K| 3 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("START_NUM"= (SELECT "START_NUM" FROM (SELECT "START_NUM" "START_NUM" FROM
"MY_TABLE" "MY_TABLE" WHERE "START_NUM"<=1000000 ORDER BY "START_NUM" DESC)
"from$_subquery$_002" WHERE ROWNUM=1))
2 - access("END_NUM"= (SELECT "END_NUM" FROM (SELECT "END_NUM" "END_NUM" FROM
"MY_TABLE" "MY_TABLE" WHERE "END_NUM">=1000000 ORDER BY "END_NUM") "from$_subquery$_004"
WHERE ROWNUM=1))
3 - filter(ROWNUM=1)
5 - access("END_NUM">=1000000)
6 - filter(ROWNUM=1)
8 - access("START_NUM"<=1000000)