ようこそ、大量データロードのドラマチックなシナリオのど真ん中へ!今日は、ON CONFLICT構文を使ってデータロード時に発生するエラーをうまく処理する方法を学ぶよ。これは飛行機のオートパイロットをオンにするみたいなもんで、何かトラブルが起きてもパニックにならずに済むんだ。じゃあ、PostgreSQLのトリックを見ていこう!
誰だってサプライズは嫌だよね、特にデータがロードできない時!大量データのロードでは、よくある問題がいくつかあるんだ:
- データの重複。 例えば、テーブルに
UNIQUE制約があるのに、データファイルに同じ値がたくさん入ってる場合とか。 - 制約との衝突。 例えば、
NOT NULL制約があるカラムに空の値を入れようとしたら…結果は?エラーだよ。PostgreSQLはこういう時、めっちゃ厳しい。 - 重複した主キー情報。 テーブルにすでに同じIDのデータが入ってて、CSVファイルにも同じIDがある場合とか。
じゃあ、こういう「落とし穴」をON CONFLICTでどう避けるか見ていこう。
ON CONFLICTでエラー処理する方法
ON CONFLICT構文のシンタックスでは、制約(例えばUNIQUEやPRIMARY 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 = 2とid = 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 |
データロードと衝突処理
- まず、一時テーブル
tmp_studentsを作成:
CREATE TEMP TABLE tmp_students (
id INTEGER,
name TEXT,
age INTEGER
);
\COPYを使ってファイルからデータをロード:
\COPY tmp_students FROM 'students_update.csv' DELIMITER ',' CSV HEADER
- 一時テーブルから本テーブルに
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で大量データを柔軟かつ安全にロードできる。衝突でクエリが落ちるのを防げるだけじゃなく、どう処理するかもコントロールできるよ。ユーザーも(サーバーも!)きっと感謝してくれるはず。
GO TO FULL VERSION