2

以下のサンプルに単純化したいくつかの値の選択に問題があります。基本的に、次のようなテーブルがあります。

CREATE TABLE sample_table
(
  pk_id        NUMBER,
  business_id  NUMBER
)

このテーブルの一部の business_id が重複しているため、それらのレコードの pk を知る必要があります。

私が(さらに)次のようにテーブルを作成して埋めるとしましょう:

ALTER TABLE sample_table ADD (
  CONSTRAINT sample_table_PK
 PRIMARY KEY
 (pk_id));

 create sequence sample_sequence;

 create trigger sample_trigger before insert on sample_table for each row 
 begin
    :new.pk_id := sample_sequence.nextval; 
 end;


 insert into sample_table (business_id) values (1000);
 insert into sample_table (business_id) values (1001);
 insert into sample_table (business_id) values (1002);
 insert into sample_table (business_id) values (1003);
 insert into sample_table (business_id) values (1003);
 insert into sample_table (business_id) values (1004);

重複している business_id を特定するのは簡単です。

  SELECT   business_id, COUNT (business_id)
    FROM   sample_table
GROUP BY   business_id
  HAVING   COUNT (business_id) > 1;

しかし、business_id は必要ありません。pk_id が必要です。

上記のクエリをサブクエリとして使用して取得できます。

select * from sample_table where business_id in (
  SELECT   business_id
    FROM   sample_table
GROUP BY   business_id
  HAVING   COUNT (business_id) > 1);

またはサブクエリファクタリングで COUNT ( * ) OVER PARTITION BY を使用する

with q as 
(SELECT   business_id, COUNT ( * ) OVER (PARTITION BY business_id) totalcount
  FROM   sample_table)
select * from q
where q.totalcount > 1

しかし、どちらもクエリをかなり遅くします (このサンプルのクエリは問題なく動作しますが、約 500.000 行の運用データを扱う場合、パフォーマンスはそれほど高くありません)。

4

2 に答える 2

2

テーブルと PK インデックスのみを使用すると、最初のクエリは次のようになります。

SELECT * from sample_table where business_id in (
  SELECT   business_id
    FROM   sample_table
GROUP BY   business_id
  HAVING   COUNT (business_id) > 1);

サブクエリを評価するためにフル テーブル スキャンを実行する必要があります。次に、見つかった business_ids のリストが与えられた場合、メイン クエリもフル スキャンを実行する必要があります (PK インデックスはこれには使用されません)。次のような計画を立てます。

-----------------------------------------------...
| Id  | Operation             | Name         | ...
-----------------------------------------------...
|   0 | SELECT STATEMENT      |              | ...
|*  1 |  HASH JOIN RIGHT SEMI |              | ...
|   2 |   VIEW                | VW_NSO_1     | ...
|*  3 |    FILTER             |              | ...
|   4 |     HASH GROUP BY     |              | ...
|   5 |      TABLE ACCESS FULL| SAMPLE_TABLE | ...
|   6 |   TABLE ACCESS FULL   | SAMPLE_TABLE | ...
-----------------------------------------------...

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

   1 - access("BUSINESS_ID"="BUSINESS_ID")
   3 - filter(COUNT(*)>1)

business_id と pk_id に (この順序で) 一意のインデックスをスローすると、2 回目のテーブル スキャンを省略して、インデックスを使用して重複する business_id のみを検索できるようになります。(重複の可能性がないかすべての行をチェックする必要があるため、最初のテーブル スキャンは避けられません。) 複合インデックスを使用すると、Oracle はテーブルに戻ることなく、business_id を検索し、同時に pk_id を取得できます。

-------------------------------------------------...
| Id  | Operation             | Name            |...
-------------------------------------------------...
|   0 | SELECT STATEMENT      |                 |...
|   1 |  NESTED LOOPS         |                 |...
|   2 |   VIEW                | VW_NSO_1        |...
|*  3 |    FILTER             |                 |...
|   4 |     HASH GROUP BY     |                 |...
|   5 |      TABLE ACCESS FULL| SAMPLE_TABLE    |...
|*  6 |   INDEX RANGE SCAN    | BUSINESS_ID_IDX |...
-------------------------------------------------...

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

   3 - filter(COUNT(*)>1)                          
   6 - access("BUSINESS_ID"="BUSINESS_ID")

重複が例外である場合、これはかなりうまくいくはずです。最悪のシナリオで、すべての business_id が重複していると、インデックス ルックアップがおかしくなる可能性があります。

次のような少しファンキーなものを試すことができます。

SELECT business_id, LISTAGG(pk_id) WITHIN GROUP (ORDER BY pk_id)
FROM sample_table
GROUP BY business_id
HAVING COUNT(*) > 1

これで、完全なテーブル スキャンが 1 回だけ取得されますが、すべての pk_id が同じ行にまとめられます。

于 2013-08-08T16:23:02.120 に答える