CodeGym /コース /SQL SELF /データロード時のエラー処理(`ON CONFLICT`)

データロード時のエラー処理(`ON CONFLICT`)

SQL SELF
レベル 23 , レッスン 3
使用可能

ようこそ、大量データロードのドラマチックなシナリオのど真ん中へ!今日は、ON CONFLICT構文を使ってデータロード時に発生するエラーをうまく処理する方法を学ぶよ。これは飛行機のオートパイロットをオンにするみたいなもんで、何かトラブルが起きてもパニックにならずに済むんだ。じゃあ、PostgreSQLのトリックを見ていこう!

誰だってサプライズは嫌だよね、特にデータがロードできない時!大量データのロードでは、よくある問題がいくつかあるんだ:

  • データの重複。 例えば、テーブルにUNIQUE制約があるのに、データファイルに同じ値がたくさん入ってる場合とか。
  • 制約との衝突。 例えば、NOT NULL制約があるカラムに空の値を入れようとしたら…結果は?エラーだよ。PostgreSQLはこういう時、めっちゃ厳しい。
  • 重複した主キー情報。 テーブルにすでに同じIDのデータが入ってて、CSVファイルにも同じIDがある場合とか。

じゃあ、こういう「落とし穴」をON CONFLICTでどう避けるか見ていこう。

ON CONFLICTでエラー処理する方法

ON CONFLICT構文のシンタックスでは、制約(例えばUNIQUEPRIMARY KEY)と衝突した時にどうするか指定できるよ。PostgreSQLでは、既存データを更新するか、衝突した行を無視するか選べるんだ。

基本的なON CONFLICT構文はこんな感じ:

INSERT INTO table_name (column1, column2, ...)
VALUES (value1, value2, ...)
ON CONFLICT (conflict_target)
DO UPDATE SET column1 = new_value1, column2 = new_value2;

もし更新じゃなくて無視したい場合は、DO UPDATEの代わりにDO NOTHINGを使えばOK。

例:衝突時にデータを更新する

例えば、studentsテーブルがあるとしよう:

CREATE TABLE students (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    age INT
);

ここに新しいデータを入れたいけど、いくつかはすでにDBにある場合:

INSERT INTO students (id, name, age)
VALUES 
    (1, 'Peter', 22),  -- この学生はすでにいる
    (2, 'Anna', 20),  -- 新しい学生
    (3, 'Mal', 25) -- 新しい学生
ON CONFLICT (id) DO UPDATE SET 
    name = EXCLUDED.name, 
    age = EXCLUDED.age;

この例だと、追加しようとしたIDがすでにある場合、そのデータが更新されるよ:

ON CONFLICT (id) DO UPDATE SET
    name = EXCLUDED.name, 
    age = EXCLUDED.age;

魔法のワードEXCLUDEDに注目!これは「挿入しようとしたけど、衝突で除外された値」って意味だよ。

結果:

  • id = 1の学生は(名前と年齢が)更新される。
  • id = 2id = 3の学生はテーブルに追加される。

例:衝突を無視する

データを更新せず、衝突した行を単に無視したい場合は、DO NOTHINGを使おう:

INSERT INTO students (id, name, age)
VALUES 
    (1, 'Peter', 22),  -- この学生はすでにいる
    (2, 'Anna', 20),  -- 新しい学生
    (3, 'Mal', 25) -- 新しい学生
ON CONFLICT (id) DO NOTHING;

これで衝突した行は挿入されず、他の行だけがDBに入るよ。

エーログの記録

時には無視や更新だけじゃ足りないこともある。例えば、衝突を後で分析したい場合。そんな時はエラーを記録する専用テーブルを作ろう:

CREATE TABLE conflict_log (
    conflict_time TIMESTAMP DEFAULT NOW(),
    id INT,
    name TEXT,
    age INT,
    conflict_reason TEXT
);

次に、エラー処理とログ記録を追加しよう:

INSERT INTO students (id, name, age)
VALUES 
    (1, 'Peter', 22), 
    (2, 'Anna', 20), 
    (3, 'Mal', 25)
ON CONFLICT (id) DO UPDATE SET 
    name = EXCLUDED.name, 
    age = EXCLUDED.age
RETURNING EXCLUDED.id, EXCLUDED.name, EXCLUDED.age
INTO conflict_log;

この最後の例はストアドプロシージャの中でだけ動くよ。どう動くかはPL-SQLを勉強する時に詳しくやるから、今は「データロード時の衝突を全部ログに残す方法もある」って覚えておいて!

これで衝突の原因を分析できるね。特に複雑なシステムでは、大量データロード時に「痕跡」を残すのが大事なんだ。

実践例

じゃあ、今までの知識を使って簡単な課題をやってみよう。例えば、学生の更新データが入ったCSVファイルをテーブルにロードしたいとする:

ファイル名:students_update.csv

id name age
1 Otto 23
2 Anna 21
4 Wally 30

データロードと衝突処理

  1. まず、一時テーブルtmp_studentsを作成:
CREATE TEMP TABLE tmp_students (
  id   INTEGER,
  name TEXT,
  age  INTEGER
);
  1. \COPYを使ってファイルからデータをロード:
\COPY tmp_students FROM 'students_update.csv' DELIMITER ',' CSV HEADER
  1. 一時テーブルから本テーブルにINSERT ON CONFLICTでデータを入れる:
INSERT INTO students (id, name, age)
SELECT id, name, age FROM tmp_students
ON CONFLICT (id) DO UPDATE
  SET name = EXCLUDED.name,
      age = EXCLUDED.age;

これで全データ(id = 1の更新も含む)がちゃんとロードされるよ。

よくあるミスとその回避法

どんなベテランプログラマーでもミスはするもの。でも、どう避けるか知っていれば、何時間(もしかしたら何日も!)も無駄なストレスを減らせるよ。

  • UNIQUE制約との衝突。 ON CONFLICTで正しいカラムを指定してるか確認しよう。例えば、間違ったキー(idじゃなくてemailとか)を指定したら、PostgreSQLは「バイバイ」ってクエリを蹴っちゃうよ。
  • EXCLUDEDの使い方ミス。 このエイリアスは今のクエリで渡した値だけに使える。他の文脈で使おうとしないでね。
  • カラムの抜け。 SETで指定したカラムがテーブルにちゃんとあるか確認しよう。例えば、SET non_existing_column = 'value'なんてやるとエラーになるよ。

ON CONFLICTを使えば、PostgreSQLで大量データを柔軟かつ安全にロードできる。衝突でクエリが落ちるのを防げるだけじゃなく、どう処理するかもコントロールできるよ。ユーザーも(サーバーも!)きっと感謝してくれるはず。

2
タスク
SQL SELF, レベル 23, レッスン 3
ロック未解除
データの競合時の更新
データの競合時の更新
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION