« 実行計画は、SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS | トップページ | 標準はあるにはあるが癖の多いSQL - #27 LNNVL is 何? と思った方向け »

2022年4月 9日 (土)

実行計画は、SQL文のレントゲン写真だ! No.34 - 似て非なるもの USE_CONCAT と OR_EXPAND ヒント と 手書きSQLのレントゲンの見分け方

Previously on Mac De Oracle
前回のレントゲンは、BITMAP CONVERSION TO ROWIDSでした。複数の索引を同時に使うという昔からあるオペレーションでした。

今回は単にレントゲンを見ていくだけではなく、同じ問い合わせ結果(よくある間違いなどもいれてありますw)になるものの微妙に違うレントゲンをみつつ、元のSQL文、それに今回ヒントになにが使われているか、見ていきたいと思います。

これが前回のエントリで使ったSQL分です. 問い合わせ結果と実行計画(前回のエントリで取り上げたBITMAP CONVERSIONです。この問い合わせ結果と実行計画という名のレントゲンをよーーーーーーーーーく、覚えておいてくださいね。

いくつかのレントゲンを使って、これなーーーーーーーんだ? wみたいなw

これが原型なので、覚えておいてください。

select
*
from
tab311
where
unique_id= 1
or sub_item_code = '0001000000';

UNIQUE_ID SUB_ITEM_CODE FOO IS_DELETE
---------- ------------------------------ -------------------------------------------------- ----------
1 0000000002 ************************************************** 0
**************************************************
**************************************************
**************************************************
*************************************************1

1 0001000001 fooooooooooooo1 0
2 0001000000 fooooooo2 0
1 0001000000 2**** 0

---------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 8 (13)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 5 | 1345 | 8 (13)| 00:00:01 |
| 2 | BITMAP CONVERSION TO ROWIDS | | | | | |
| 3 | BITMAP OR | | | | | |
| 4 | BITMAP CONVERSION FROM ROWIDS | | | | | |
| 5 | SORT ORDER BY | | | | | |
|* 6 | INDEX RANGE SCAN | TAB311_PK | | | 3 (0)| 00:00:01 |
| 7 | BITMAP CONVERSION FROM ROWIDS | | | | | |
|* 8 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | | | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------------

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

6 - access("UNIQUE_ID"=1)
filter("UNIQUE_ID"=1)
8 - access("SUB_ITEM_CODE"='0001000000')

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
8 consistent gets
0 physical reads
0 redo size
1248 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
4 rows processed

 

前回のエントリでも少し書いたのですが、CONCATENATION がレントゲンに現れるときは、BITMAP CONVERTなど効率が悪いOR条件の実行計画を改善するため、OR条件部分を分離排除し、2つの索引それぞれを有効に利用させるためのヒントによるチューニングで行なった場合が多いです。オプティマイザが選択するケースもありますが。

ヒントで強制的にすることもありますが、ヒントが効かないケースは多も多いのは事実です。理由は内部的に2つのクエリーに分解しているわけですが、それぞれで利用する索引のアクセス効率が悪いオプティマイザに見えている場合にはヒントが効かない場合が多いように思います。
なんとなーーーくざっくりなイメージですが、UNIONのような形に内部的に書き換えていると思うとわかりやすいかもしれないですね。。UNIONとでてないのでUNIONのようなものとしかかけないのですがW
とにかく、CONCATENATIONを見つけたら USE_CONCAT ヒントでチューニングされてるね!
と脊髄反応できるようになっているとよいですね!

 UNIQUE_ID SUB_ITEM_CODE                  FOO                                                 IS_DELETE
---------- ------------------------------ -------------------------------------------------- ----------
2 0001000000 fooooooo2 0
1 0001000000 2**** 0
1 0000000002 ************************************************** 0
**************************************************
**************************************************
**************************************************
*************************************************1

1 0001000001 fooooooooooooo1 0

----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 11 (0)| 00:00:01 |
| 1 | CONCATENATION | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------

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

3 - access("SUB_ITEM_CODE"='0001000000')
5 - access("UNIQUE_ID"=1)
filter(LNNVL("SUB_ITEM_CODE"='0001000000'))

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
11 consistent gets
0 physical reads
0 redo size
1228 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
4 rows processed

 

上記のレントゲンの元はこれです。USE_CONCATヒント使われてますよね。このケースではオプティマイザは言うことを聞いてくれたようですね。w

select
/*+
use_concat
*/
*
from
tab311
where
unique_id= 1
or sub_item_code = '0001000000';

 

では、オプティマイザが言うことを聞いてくれなかった場合、Oracle Database 12c R1まではどうやって、治療していたか。知りたく無いですか?
USE_CONCATでUNIONのように内部的書き換えてくれると、SQLへはヒントの追加だけで済むので、同値検証等も不要で患者さんの痛みは少なくてすむわけですが、先にも買いたように必ず効くわけでもないという、ちょっと癖のあるヒントなんです。で、12c R1までは、しかたないの、SQL構文変更という中程度の難易度の手術(SQL書き換えw)が必要でした。

 

先ほと、UNIONのようにと書きましたが、まさに、それで、UNIONまたは、UNION ALLに書き換えてしまうという手術ですw

 

どちらでやってもよいのですが、重複データの排除がどれだけの負荷になるかというところかなと思います。重複排除するデータ量が多いのであれば UNION にしてHASH UNIQUEによる重複行排除の方がよいかもしれませんし、少量なら UNION ALLでフィルタリングによる重複行排除のほうがよいかもしれません。HASH UNIQUEにしてもPGA不足でTEMP落ちしてしまうようなことがあるのならフィルタリングのほうがよさそうですし、その時の状況次第かと思います。

 

では、ずは、UNION を使った書き換えから。

 

ソートしていないので並びが変わってますが、あえてソートしていません。Id=2のUNION-ALLとId=1にHASH UNIQUEというoperationがありますが、これが現在のUNIONの典型的なoperationです。HASH UNIQUEがなかったころは、SORT UNIQUEだったわけですが、その影響でデータがソートされていたので、諸々勘違いしてデフォルトでソートされるんだー、みたいな勘違いしている方も一定数存在していた時期があり、HASH UNIQUEがなって順序通りになってない! と勝手にザワザワしていたこともありましたね。それ知ってる方々はOracleにながーーーーいこと関わっている方だと思いますw 注意しましょうね。思った通りの並びにしたい場合は、ちゃんとORDER BY句でソートしましょうね。(これ言いたかっただけw)
あと、最近は、UNIONをパラレル実行できるようになったので、その場合も、順序はバラバラになります。シリアルに実行している場合は上位にあるクエリから処理されるのでその順序で行が戻されていましたが、パラレルだと何が来るかはその時々ですね。

 

脇道にそれましたが、手書きでSQLを UNION に書き換えた場合のレントゲンはこんな感じです。UNION-ALLのオペレーションの後に重複行排除のUNIQUE操作が必ず入るので覚えやすいと思います。

 UNIQUE_ID SUB_ITEM_CODE                  FOO                                                 IS_DELETE
---------- ------------------------------ -------------------------------------------------- ----------
1 0000000002 ************************************************** 0
**************************************************
**************************************************
**************************************************
*************************************************1

1 0001000000 2**** 0
1 0001000001 fooooooooooooo1 0
2 0001000000 fooooooo2 0

-----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 12 (9)| 00:00:01 |
| 1 | HASH UNIQUE | | 5 | 1345 | 12 (9)| 00:00:01 |
| 2 | UNION-ALL | | | | | |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------------

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

4 - access("UNIQUE_ID"=1)
6 - access("SUB_ITEM_CODE"='0001000000')

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
9 consistent gets
0 physical reads
0 redo size
1225 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
4 rows processed

 

もともとOR条件でしたが、それぞれの索引を有効に活用させるため、2つの文に分解し、それらを UNIONしています。UNIONで重複排除も行なっているわけです。

select
*
from
tab311
where
unique_id = 1
union
select
*
from
tab311
where
sub_item_code = '0001000000';

 

 

次に、UNION ALLへ手動で書き換えた場合はどうなるかレントゲンを見てみましょう。

 

結果も正しいです。レントゲンで見える UNION と UNION-ALLとの違いは、重複行排除のUNIQUEオペレーションが無いところです。Id=1にあるUNION-ALL だけで、 HASH UNIQUEがありません。
これ大丈夫なのでしょうか? 重複行を排除するオペレーションがないなんで、たまため結果が正しいだけでしょうか???

 

実は、実行計画に現れない違いが述語部分にあります。 Predicate Information (identified by operation id):セクションに 4 - filter(LNNVL("UNIQUE_ID"=1)) とあるのに気づきましたか?

 

4は、実行計画の Id = 4を示しています。これは Id = 4の TAB311のアクセス時に、"UNIQUE_ID"=1 であれば falseとして該当行をフィルタリングして捨てていることを意味しています。
つまり、UNIONで HASH UNIQUEを行なっていた重複行を排除と同様の効果をえるフィルター条件なんです。この条件にで、 Id = 2 と Id = 3で取得されたUNIQUE_ID=1の行を捨てています

 UNIQUE_ID SUB_ITEM_CODE                  FOO                                                 IS_DELETE
---------- ------------------------------ -------------------------------------------------- ----------
1 0000000002 ************************************************** 0
**************************************************
**************************************************
**************************************************
*************************************************1

1 0001000000 2**** 0
1 0001000001 fooooooooooooo1 0
2 0001000000 fooooooo2 0

----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 11 (0)| 00:00:01 |
| 1 | UNION-ALL | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
|* 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------

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

3 - access("UNIQUE_ID"=1)
4 - filter(LNNVL("UNIQUE_ID"=1))
5 - access("SUB_ITEM_CODE"='0001000000')

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
10 consistent gets
0 physical reads
0 redo size
1225 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
4 rows processed

 

UNION ALLでの書き換えは、以下のようなSQL文でした。and LNNVL(unique_id=1)という条件追加がポイントです。

select
*
from
tab311
where
unique_id = 1
union all
select
*
from
tab311
where
sub_item_code = '0001000000'
and LNNVL(unique_id=1);

 

では、つい忘れそうな、フィルター条件追加をわすれてUNION ALLにしてしまった場合はどうなるでしょう? もう想像できますよねw

 

SQLの違いからみてみましょう。 and LNNVL(unique_id=1) と言う条件が無いこと以外前述のSQLと同じです。

select
*
from
tab311
where
unique_id = 1
union all
select
*
from
tab311
where
sub_item_code = '0001000000';

 

実行してみると。。。。。あららららら、ちゃっしゃいましたな感じの結果ですねw Predicate Information (identified by operation id): には重複排除のフィルター条件は見当たりません。(当然ですね。書き忘れているわけですから)

UNIQUE_ID = 1 でもあり、SUB_ITEM_CODE = '0001000000' である行が2回リストされています。ざんねーーーん。注意しましょうね。

 UNIQUE_ID SUB_ITEM_CODE                  FOO                                                 IS_DELETE
---------- ------------------------------ -------------------------------------------------- ----------
1 0000000002 ************************************************** 0
**************************************************
**************************************************
**************************************************
*************************************************1

1 0001000000 2**** 0
1 0001000001 fooooooooooooo1 0
2 0001000000 fooooooo2 0
1 0001000000 2**** 0


----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 11 (0)| 00:00:01 |
| 1 | UNION-ALL | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------

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

3 - access("UNIQUE_ID"=1)
5 - access("SUB_ITEM_CODE"='0001000000')

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
10 consistent gets
0 physical reads
0 redo size
1239 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
5 rows processed

 

よーーーーし、バグになるのが怖いから、常に UNION だーーーと安全策に流れそうですが、性能面ではフィルタリングと HASH UNIQUE、または、SORT UNIQUEかということであれば、データ量次第でどちらが良いか判断した方がよいだろうと。個人的には思っています。フィルターしたほうが有利なケースも当然ありますし、しないほうが良い場合もあるでしょう。

最後に、今日のタイトルにも書いた NO_EXPAND これ少々気難しい、USE_CONCATの後継として 12c R2 で登場したヒントです。内部の書き換えもそれまでのCONCATENATIONではなく、UNION ALLとフィルタリングにより重複行排除が行われるように、内部的に書き換えられるのが特徴です。なんでこれもっと早く実装してくれなかったんですかね? 強強ですね。USE_CONCATの気まぐれ感は消えてる感じがします。

事実、某所で、USE_CONCAT効かなくて、しぬーーーーーみたいな状況で、たまたま 12c R2だったので、NO_EXPAND で回避したーーーーなんてこともありました。そういことで、USE_EXPANDをUSE_CONCATの代わりにUSE_EXPANDを使うことをおすすめしますw (それ以外にもメリットも多いですし、それはまた、次回にでも)

select
/*+
or_expand
*/
*
from
tab311
where
unique_id= 1
or sub_item_code = '0001000000';

UNIQUE_ID SUB_ITEM_CODE FOO IS_DELETE
---------- ------------------------------ -------------------------------------------------- ----------
1 0000000002 ************************************************** 0
**************************************************
**************************************************
**************************************************
*************************************************1

1 0001000000 2**** 0
1 0001000001 fooooooooooooo1 0
2 0001000000 fooooooo2 0

 

レントゲンを見ると、手書きで書いた UNION ALLへの書き換えと微妙に違うの気づきますか? これまで紹介してきた手書きでの書き換えとヒントに夜書き換えは4つありますが、それぞれ実行計画に特徴があるんです。(もしかしたら将来は区別しにくくなるかもしれませんが、現状は区別できます!!!

OR_EXPANヒントでUNION ALL変換した場合 Id = 1にあるような、インラインビューがは登場します。VW_ORE_5F0E22D2 とオプティマイザが動的に名称をつけますが、ポイントは VW_ORE_* というprefixが作ろころですね。VWはびゅー。OREは、OR_Expand の大文字部分みたいですねw (そのうち内部的に生成されるインラインビュー名もまとめて紹介したいですね。すでに誰かやってそうな気もしますがw)

実行計画
----------------------------------------------------------
Plan hash value: 3148130991

-----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1450 | 11 (0)| 00:00:01 |
| 1 | VIEW | VW_ORE_5F0E22D2 | 5 | 1450 | 11 (0)| 00:00:01 |
| 2 | UNION-ALL | | | | | |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
|* 5 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------------

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

4 - access("UNIQUE_ID"=1)
5 - filter(LNNVL("UNIQUE_ID"=1))
6 - access("SUB_ITEM_CODE"='0001000000')

統計
----------------------------------------------------------
0 recursive calls
0 db block gets
10 consistent gets
0 physical reads
0 redo size
1225 bytes sent via SQL*Net to client
52 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
4 rows processed

 

長くなったので、まとめです。
ヒントによる OR条件のUNIONのような書き換えや、UNION ALLへの書き換え、または、人が UNION や UNION ALLへ手書きで書き換えたSQLのレントゲン、それぞれに特徴があり、4つとも、レントゲンから元のSQLがイメージできるんですよ!!!

USE_CONCATによる書き換え Oracle 8i 8.1以降〜

select
/*+
use_concat
*/
*
from
tab311
where
unique_id= 1
or sub_item_code = '0001000000';

----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 11 (0)| 00:00:01 |
| 1 | CONCATENATION | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
| 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------

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

3 - access("SUB_ITEM_CODE"='0001000000')
5 - access("UNIQUE_ID"=1)
filter(LNNVL("SUB_ITEM_CODE"='0001000000'))

 

OR_EXPANDによる書き換え Oracle 12cR2以降〜

select
/*+
or_expand
*/
*
from
tab311
where
unique_id= 1
or sub_item_code = '0001000000';

-----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1450 | 11 (0)| 00:00:01 |
| 1 | VIEW | VW_ORE_5F0E22D2 | 5 | 1450 | 11 (0)| 00:00:01 |
| 2 | UNION-ALL | | | | | |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
|* 5 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------------

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

4 - access("UNIQUE_ID"=1)
5 - filter(LNNVL("UNIQUE_ID"=1))
6 - access("SUB_ITEM_CODE"='0001000000')

 

手動書き換え UNION

select
*
from
tab311
where
unique_id = 1
union
select
*
from
tab311
where
sub_item_code = '0001000000';

-----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 12 (9)| 00:00:01 |
| 1 | HASH UNIQUE | | 5 | 1345 | 12 (9)| 00:00:01 |
| 2 | UNION-ALL | | | | | |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 6 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------------

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

4 - access("UNIQUE_ID"=1)
6 - access("SUB_ITEM_CODE"='0001000000')

 

手動書き換え UNION ALL + 重複排除フィルター条件追加

select
*
from
tab311
where
unique_id = 1
union all
select
*
from
tab311
where
sub_item_code = '0001000000'
and LNNVL(unique_id=1);

----------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 1345 | 11 (0)| 00:00:01 |
| 1 | UNION-ALL | | | | | |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 1 | 269 | 4 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | TAB311_PK | 1 | | 3 (0)| 00:00:01 |
|* 4 | TABLE ACCESS BY INDEX ROWID BATCHED| TAB311 | 4 | 1076 | 7 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | TAB311_IX_SUB_ITEM_CODE | 4 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------------

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

3 - access("UNIQUE_ID"=1)
4 - filter(LNNVL("UNIQUE_ID"=1))
5 - access("SUB_ITEM_CODE"='0001000000')

 

 

レントゲンから諸々読み取るスキルは大切だと思っているので、みんなもレントゲンというなの実行計画は読んでみるといいよーーーっ。おすすめ。

 

では、次回へつづく

 

 



Related article on Mac De Oracle
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 1 / TABLE FULL SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 2 / INDEX UNIQUE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 3 / INDEX RANGE SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 4 / INDEX RANGE SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 5 / INDEX RANGE SCAN, INLIST ITERATOR
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 6 / INDEX FAST SCAN, Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 7 / INDEX FULL SCAN,Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 8 / INDEX SKIP SCAN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 9 / TABLE ACCESS INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 10 / NESTED LOOP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 11 / MERGE JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 12 / HASH JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 13 / HASH JOIN OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 14 / HASH JOIN FULL OUTER
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 15 / PX, TABLE ACCESS FULL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 16 / CONCATENATION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 17 / SORT UNIQUE, UNION-ALL = UNION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 18 / UNION-ALL
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 19 / INTERSECTION
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 20 / MINUS
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 21 / WINDOW NOSORT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 22 / COUNT STOPKEY
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 23 / HASH JOIN - LEFT-DEEP JOIN vs RIGHT-DEEP JOIN
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 24 / CONNECT BY NO FILTERING WITH START-WITH
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - Day 25 / UNION ALL (RECURSIVE WITH) DEPTH FIRST, RECURSIVE WITH PUMP
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#1 / STAR TRANSFORM, VECTOR TRANSFORM (DWH向け)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#2 / MERGE (UPSERT)
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#3 / RDFView
・実行計画は、SQL文のレントゲン写真だ! Oracle Database編 (全部俺)Advent Calendar 2019 - おまけ#4 / INDEX FULL SCAN (MIN/MAX) - Index Only Scan
・実行計画は、SQL文のレントゲン写真だ! No.30 - LOAD TABLE CONVENTIONAL vs. LOAD AS SELECT
・実行計画は、SQL文のレントゲン写真だ! No.31 - TEMP TABLE TRANSFORMATION LOAD AS SELECT (CURSOR DURATION MEMORY)
・実行計画は、SQL文のレントゲン写真だ! No.32 - EXTERNAL TABLE ACCESS FULL / INMEMORY FULL
・実行計画は、SQL文のレントゲン写真だ! No.33 - BITMAP CONVERSION TO ROWIDS

|

コメント

コメントを書く