オラクルのバグ?SELECT は重複を返しません。SELECT からの INSERT には重複行があります
質問
作業中の Oracle インスタンスから奇妙な動作が発生しています。これは Itanium 上の 11gR1 で、RAC も特別なものもありません。最終的には、データ ウェアハウスのシナリオで、ある Oracle インスタンスから別の Oracle インスタンスにデータを移動します。
DB リンク上で実行される準複雑なビューがあります。大規模なテーブルに対する 4 つの内部結合と、中規模のテーブルに対する 5 つの左結合。
問題は次のとおりです。SQL Developer(またはSQL*Plus)でビューをテストすると、重複はなく、問題ないようです。ただし、実際にビューを使用してテーブルにデータを挿入すると、大量の重複が発生します。
編集:- データは空のテーブルに入ります。クエリ内のすべてのテーブルはデータベース リンク上にあります。クエリに渡されるのは日付だけです (例:INSERT INTO ターゲット SELECT * FROM ビュー WHERE view.datecol = dQueryDate) -
ROW_NUMBER() 関数を select ステートメントに追加して、ビューの PK によってパーティション化してみました。すべての行には 1 という番号が付けられます。ただし、繰り返しになりますが、同じステートメントを挿入として実行すると、以前と同じ複製が生成され、便利な番号が付けられるようになりました。複製された行の数はキーごとに同じではありません。4 回存在するレコードもあれば、1 回しか存在しないレコードもあります。
この行動は非常に不可解だと思います。:) SET テーブル (一意の行のみ) と MULTISET テーブル (重複行が許可される) がある Teradata での作業を思い出しますが、Oracle にはそのような機能がありません。
クライアントに行を返す選択は、それらの行を別の場所に挿入する選択と同じように動作する必要があります。これが起こった正当な理由は想像できませんが、おそらく私の想像力の欠如に苦しんでいます。;)
他にもこれを経験した人はいるのでしょうか、それともこのプラットフォームのバグなのでしょうか。
解決
@Garyのおかげで、 "EXPLAIN PLAN FOR {my query};"と "SELECT * FROM TABLE(dbms_xplan.display);"を使用して、この真相を突き止めることができました。と説明します 実際に慣れてくる INSERT は SELECT とは大きく異なります。
SELECT の場合、ほとんどのプラン操作は「TABLE ACCESS BY INDEX ROWID」と「INDEX UNIQUE SCAN」です。「述語情報」ブロックには、クエリからのすべての結合とフィルターが含まれます。最後にこう書いてある 「注 - 完全にリモートのステートメント」.
INSERT の場合、インデックスへの参照はありません。「述語情報」ブロックはわずか 3 行で、新しい「リモート SQL」ブロックは次のようになります。 9 小さな SQL ステートメント。
データベースは私のクエリを 9 つのサブクエリに分割し、それらをローカルで結合しようとしました。より小さな選択を実行することで、重複のソースを特定しました。
これは、リモート リンクに関する Oracle コンパイラのバグだと思います。 SQL を書き直すときに論理的な欠陥が生じます。基本的に、コンパイラは WHERE 句を適切に適用していません。私はそれをテストしていたので、戻すための 5 つのキーの IN リストを与えました。SELECT は 5 行を返します。INSERT は 77,000 行以上をターゲットに挿入し、 IN リストを完全に無視します。
{正しい動作を強制する方法をまだ探しているので、リモート データベース上にビューを作成するように要求する必要があるかもしれませんが、開発の観点からは理想的ではありません。動作するようになったら編集します…}
解決
これは Oracle のバグのようです。次の回避策が見つかりました。それが欲しいなら、あなたの「」insert into select ...
「あなたらしく働く」select ...
」では、選択をサブ選択にパックできます。
例えば :
select x,y,z from table1, table2, where ...
--> 重複はありません
insert into example_table
select x,y,z from table1, table2, where ...
--> 重複エラー
insert into example_table
select * from (
select x,y,z from table1, table2, where ...
)
--> 重複はありません
よろしく
他のヒント
思い当たることの 1 つは、通常、SELECT のオプティマイザー プランでは、呼び出し元に早く行を返すために FIRST_ROWS プランが優先されますが、INSERT...SELECT では、完全なデータセット。DBMS_XPLAN.DISPLAY_CURSOR (V$SQL の sql_id を使用) を使用してクエリ プランを確認します。
セミコンプレックスビューを実行しています DBリンク経由。4 つの内部結合 大きなテーブルと5つの左の結合 中サイズのテーブル。...クエリ内のすべてのテーブルが データベース・リンク
繰り返しますが、潜在的なトラブルスポットです。SELECT 内のすべてのテーブルが DB リンクの反対側にある場合、クエリ全体がリモート データベースに送信され、結果セットが返されます。INSERT を投入すると、ローカル データベースがクエリを担当し、子テーブルからすべてのデータをプルする可能性が高くなります。ただし、それはビューがローカル データベースで定義されているかリモート データベースで定義されているかによって異なる場合があります。後者の場合、ローカル オプティマイザーに関する限り、リモート オブジェクトは 1 つだけ存在し、そこからデータを取得し、リモート データベースが結合を実行します。
リモート DB に移動し、そこにあるテーブルに対して INSERT を実行するとどうなりますか?
これは、Oracle による DB リンク上の結合処理のバグです。INSERT と SELECT を関係しない、より単純な状況があります。クエリをリモートで実行すると重複した行が取得されますが、ローカルで実行すると重複しません。クエリ間の唯一の違いは、リモート クエリのテーブルに追加される「@...」です。Oracle SQL Developer 3.0を使用して、10.2データベースから9iデータベースにクエリを実行しています。
これは、合計 1000 列を超えるテーブルを結合できないという Oracle のバグよりもさらに愚かです。これは、ERP システムにクエリを実行するときに非常に簡単に実行できます。いいえ、このエラー メッセージはテーブルの列が多すぎるというものではありません。
これは、ANSI 構文を使用した LOB ロケータを含むテーブルのクエリを禁止する他の Oracle データベースのバグと同じくらい愚かです。Oracle 構文のみが機能します。
いくつかの選択肢が思い浮かびます。
表示されているカモはすでに宛先テーブルにありますか??
選択で、挿入先のテーブルを参照する場合、( ?)、挿入は結合された選択と対話しています。
入れる ...選択する ...から ...
複製を作成するような方法 (デカルト積?)
おそらくテーブルに関連する他の何かによる副作用が発生しているのではないかと思わずにはいられません。データを操作している可能性のあるトリガーはありますか?
元のテーブルに重複がないことをどのように判断しましたか?
他の人も指摘しているように、これがこの奇妙な動作の最も単純な説明のようです。
あなたの JOIN
慎重に。個々のテーブルに重複がない可能性がありますが、結合の指定が不十分な場合、不注意が発生する可能性があります。 CROSS JOIN
そのため、結果セットには多重性が原因で重複があり、挿入すると宛先テーブルの一意性制約に違反します。
この場合に行うことは、ビューまたは CTE にクエリをネストし、クエリから直接重複を検出しようとすることです。 SELECT
:
WITH resultset AS (
-- blah, blah
)
SELECT a, b, c, COUNT(*)
FROM resultset
GROUP BY a, b, c
HAVING COUNT(*) > 1
実行しているクエリの計画を立てて、そこで CARTESIAN JOIN を探すことをお勧めします。これは、行の重複を引き起こす欠落条件を示している可能性があります。
AS @Pop は、挿入の実行時に SQLPlus でログインとは異なるログインを使用している場合にこの動作が発生する可能性があることをすでに示唆しています。(これは、他のログインに同じ名前のテーブル/ビュー/シノニムがある場合です)