Q1INSERT INTO stock(sku, qty) VALUES ('A001', 15) ON CONFLICT(sku) DO UPDATE SET qty = qty + excluded.qty;で、A001 が既存(qty 120)のとき、UPSERT 後の qty はいくつになりますか。
UPSERT(ON CONFLICT)と一括 INSERT 応用
この記事は、基礎から複雑なSQL,SQLチューニングまでSQLの実践的なスキルを1からマスターする「SQL入門講座の一部」です。
在庫テーブルstockでINSERT … ON CONFLICT(sku) DO UPDATEを使い、既存A001はexcluded.qtyで加算・未登録A006は新規挿入する分岐、DO NOTHINGでの既存保護、stock_inの3行の一括UPSERTを確かめます。
本記事で使うデータ — stock と stock_in
本記事のUPSERT(UPDATE と INSERT を合わせた造語です。
「あれば更新・なければ挿入」を 1 文で行う操作)は、INSERT … ON CONFLICT(キー) DO UPDATE構文で実現します。
主キーや UNIQUE 制約と衝突したら更新、衝突しなければそのまま挿入されます。
ON CONFLICT DO UPDATE — あれば更新・なければ挿入
INSERT INTO 表(...) VALUES (...) ON CONFLICT(キー列) DO UPDATE SET 列 = ...と書くと、INSERTしようとした行がON CONFLICTで指定したキー(主キーや UNIQUE 列)と衝突したらDO UPDATEの更新が実行され、衝突しなければそのまま挿入されます。
DO UPDATEの中では特別なテーブル名excludedで「挿入しようとした行の値」を参照できます。
qty = qty + excluded.qtyと書くと、衝突した場合に「既存のqty」へ「挿入しようとしたqty」を足し込めます。
qty = excluded.qtyなら上書き、qty = qty + excluded.qtyなら加算と、用途に応じて使い分けます。
-- 既存 sku に衝突 → DO UPDATE で qty を加算
INSERT INTO stock(sku, name, qty, price)
VALUES ('A001', 'Pen', 5, 80)
ON CONFLICT(sku) DO UPDATE SET qty = qty + excluded.qty;
-- 新規 sku → 衝突しないのでそのまま挿入
INSERT INTO stock(sku, name, qty, price)
VALUES ('A006', 'Marker', 3, 120)
ON CONFLICT(sku) DO UPDATE SET qty = qty + excluded.qty;
SELECT sku, name, qty FROM stock WHERE sku IN ('A001', 'A006');
DO NOTHING — 衝突したら何もしない
更新も挿入もしたくない、つまり「衝突したら静かにスキップしたい」場合はON CONFLICT(キー) DO NOTHINGを使います。
衝突しなければ挿入され、衝突すればその行はエラーにならず無視されます。
下の例では、既存の A002 をDO NOTHINGで挿入しようとすると衝突して無視され、未登録の A007 は新規挿入されます。
-- 衝突したら無視(DO NOTHING)
INSERT INTO stock(sku, name, qty, price)
VALUES ('A002', 'Note', 999, 999)
ON CONFLICT(sku) DO NOTHING;
-- A002 は元の値(qty 60 / price 250)のまま変わらない
SELECT sku, name, qty, price FROM stock WHERE sku = 'A002';
複数行 UPSERT — 一括 INSERT と ON CONFLICT を組み合わせる
VALUESを(...),(...),(...)とカンマで並べる複数行 INSERTにON CONFLICTを付けると、各行が独立に「衝突なら更新・なければ挿入」されます。
入庫データをまとめて 1 文で反映でき、行ごとにUPDATEとINSERTを呼び分ける手続き的な処理を 1 つの SQL に置き換えられます。
excludedは複数行 UPSERT でも「その行で挿入しようとした値」を指すため、qty = qty + excluded.qtyと書けば、既存行は加算、新規行はそのまま挿入される、を行単位で適用できます。
本記事の最後の演習として、stock_inの 3 行(A001 / A004 / A006)を一括 UPSERT します。
-- 複数行をまとめて UPSERT: 既存は qty を加算しつつ price も最新化、新規は挿入
INSERT INTO stock(sku, name, qty, price)
VALUES
('A002', 'Note', 10, 260),
('A005', 'Glue', 20, 190),
('A007', 'Ruler', 15, 90)
ON CONFLICT(sku) DO UPDATE
SET qty = qty + excluded.qty,
price = excluded.price;
SELECT sku, name, qty, price FROM stock ORDER BY sku;
理解度チェック
まずは1問ずつ答えてみましょう。
Q2UPSERT のDO UPDATE内で使われるexcludedは何を指しますか。
Q3既存データを絶対に書き換えず、衝突した行だけエラーにせず無視したい場合に使う構文はどれですか。