MERGE文はレコード重複のちがいで2通りのORAエラーになる
Oracle DatabaseのMERGE文について、職場で短期間に2つのORAエラーを目にした。障害復旧で対応していたのだけれど、最初同一事象なのになぜこんなに異なる結果になるのか、と訝しんだ。
結論としては下記のようになる(厳密に言うと同一事象ではなかった):
- ORA-00001(一意制約…に反しています)
- エラー原因レコードはソース表にあり、マージ対象表にはなかった。エラー原因レコードはINSERTされた(されようとした)。
- ORA-30926(ソース表の安定したセット行を取得できません)
- エラー原因レコードはソース表にあり、マージ対象表にもあった。エラー原因レコードはUPDATEされた(されようとした)。
MERGE文の機能
MERGE文はおおよそ下記のような構文をとり*1、ON句で指定された条件に基づき、USING句で指定されたマージ元(ソース表という)のサブクエリからレコードを抽出して、INTO句で指定されたマージ先(対象の表という)にレコードをマージする。もしマージ先にすでに同じPKの行が存在すればUPDATEされ、存在しなければINSERTされる*2。
MERGE INTO (マージ先) USING (マージ元) ON (マージ条件) ;
ORA-30926(ソース表の安定したセット行を取得できません)
ここで、マージ先(対象の表)では一意制約(PK等)が付与され1行しか存在しない(し得ない)レコードに対して、マージ元(ソース表)として指定されたサブクエリから返された結果セットに一意制約に反する(同一PK等)複数行が含まれているとORA-30926が発生する。
これはおそらく内部的なUPDATEが実行されている最中ではなく、ON句の条件による対応付け処理中に起きている。
ORA-00001(一意制約…に反しています)
また、マージ先には存在しないため、マージ元からそのままINSERTされるレコードについて、マージ先の一意制約に反する複数行が存在するとORA-00001が発生する。
一意制約はあくまでもINTOで指定されたテーブルのパラメータであり、USING句で指定されたクエリ結果セットの取得直後〜INSERTまえの時点では一意制約チェックなどしようがないので、エラーはあくまでも内部的なINSERTが実行されている最中に起きる。結果としておなじみの、わかりやすいエラーコード、エラーメッセージとなる。
まあどうやらそうらしい、という話。