どこでも動く形
UPDATE の WHERE は普通の WHERE なので、サブクエリで行を選べます。これに特別な構文を要求するエンジンはありません。
1
-- Works on every engine in this list, unchanged.
UPDATE customers
SET loyalty_points = loyalty_points + 50
WHERE id IN (
SELECT customer_id FROM orders WHERE status = 'refunded' AND total > 2000
);
242 行を変更
ブラウザの別タブで Planche が開き、サンプルデータベースを読み込んだ状態でこのクエリが入ります。このページの例はどれも同じタブを使い回します。F9 を押すまで実行はされません。
新しい値そのものが別のテーブルから来るなら、SET の中の相関サブクエリがそれをやります。
条件が2回書かれていることに注目してください。SET のサブクエリが値を決め、WHERE のほうがそもそもどの行に触るかを決めています。後者がないと全商品が更新対象になり、対応するログがない商品は COALESCE(..., 0) で何も足さないまま、それでも行が書き込まれます。
この形は移植でき、読みやすくもあります。ただし常に最速ではありません。エンジンが相関サブクエリを行ごとに実行することもあり、そうなっているかは実行計画が教えてくれます。以下の結合構文が存在するのは、大量更新ではその差が移植性を捨てるだけの価値を持つからです。
MySQL と MariaDB
MySQL / MariaDB
-- The join goes between UPDATE and SET.
UPDATE customers c
JOIN orders o ON o.customer_id = c.id
SET c.loyalty_points = c.loyalty_points + 10
WHERE o.status = 'refunded';
-- DELETE names which table's rows to remove, before FROM.
DELETE c
FROM customers c
JOIN orders o ON o.customer_id = c.id
WHERE o.status = 'refunded';
MySQL / MariaDB — 比較のために載せています。ブラウザ内のサンプルは SQLite なので、この文はここでは実行できません。
結合が UPDATE と SET のあいだに入ります。ここに挙げた他のどのエンジンとも違う形です。DELETE では対象を FROM の前に書く必要があります(DELETE c FROM customers c JOIN ...)。2つのテーブルを結合していると、どちらの行を消すのかが曖昧になるからです。この別名を書き忘れると構文エラーになりますが、それはまだ良いほうです。間違ったほうを書いた場合はエラーになりません。
PostgreSQL
PostgreSQL
-- The second table goes in FROM, and the join condition in WHERE.
UPDATE customers
SET loyalty_points = loyalty_points + 10
FROM orders o
WHERE o.customer_id = customers.id
AND o.status = 'refunded';
-- DELETE uses USING rather than FROM for the extra table.
DELETE FROM wishlists
USING products p
WHERE p.id = wishlists.product_id
AND p.stock_quantity = 0;
PostgreSQL — 比較のために載せています。ブラウザ内のサンプルは SQLite なので、この文はここでは実行できません。
追加のテーブルは FROM に置き、結合条件は WHERE に移ります。間違えやすい点が2つ。対象のテーブルは FROM に書きません(書くと自分自身と結合してしまいます)。そして UPDATE が FROM を使うのに対し、DELETE は USING を使います。
SQLite は 3.33(2020年)で PostgreSQL と同じ意味の UPDATE … FROM を取り入れました。このページの3つめの例がここで実行できるのはそのためです。
3
-- SQLite 3.33 (2020) and later, with PostgreSQL's spelling.
UPDATE customers
SET loyalty_points = loyalty_points + 10
FROM orders o
WHERE o.customer_id = customers.id
AND o.status = 'refunded'
AND o.total > 2500;
224 行を変更
SQLite に DELETE … USING はないので、削除は今もサブクエリで書きます。
SQL Server
SQL Server
-- The alias is updated, and the FROM clause repeats the target table.
UPDATE c
SET c.loyalty_points = c.loyalty_points + 10
FROM customers c
JOIN orders o ON o.customer_id = c.id
WHERE o.status = 'refunded';
DELETE c
FROM customers c
JOIN orders o ON o.customer_id = c.id
WHERE o.status = 'refunded';
SQL Server — 比較のために載せています。ブラウザ内のサンプルは SQLite なので、この文はここでは実行できません。
更新されるのは別名で、FROM が対象テーブルを繰り返して、その別名に定義を与えます。1つの文でテーブル名が2回出てくるので妙に見えますが、T-SQL が受け付けるのはこの形だけで、DELETE も同じパターンに従います。
Oracle
Oracle
-- Oracle has no UPDATE ... FROM and no UPDATE ... JOIN.
-- EXISTS is the portable answer, and the one to use.
UPDATE customers c
SET c.loyalty_points = c.loyalty_points + 10
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.id AND o.status = 'refunded'
);
DELETE FROM wishlists w
WHERE EXISTS (
SELECT 1 FROM products p
WHERE p.id = w.product_id AND p.stock_quantity = 0
);
Oracle — 比較のために載せています。ブラウザ内のサンプルは SQLite なので、この文はここでは実行できません。
Oracle には UPDATE … JOIN も UPDATE … FROM もありません。相関サブクエリを使った EXISTS が通常の答えで、これは他の4つのエンジンでもそのまま動く文です。
もうひとつの選択肢として、更新可能な結合ビュー(UPDATE (SELECT a.col, b.col FROM a JOIN b ON …) SET …)がありますが、Oracle は結合がキーを保存すると証明できるときにしか受け付けません。そのためには結合した列に一意制約か主キーが必要です。証明できない場合は ORA-01779 になり、直し方は EXISTS に戻ることです。
実行する前に
Planche が UPDATE と DELETE を実行前に対応する SELECT へ書き換えているのは、まさにこういう文があるからです。結合が入ると、影響する行の範囲は素の WHERE よりずっと読み取りにくくなります。設定 › 安全性 › 影響行のプレビューを有効にすれば、件数とサンプルが先に出ます。この書き換えは上の5つの形をすべて理解していて、リポジトリには、導いた件数が実際の文で変わる行数と一致するかを MySQL、PostgreSQL、SQL Server、SQLite、Oracle で検証するスクリプトも入っています。