SQLのUPDATEで複数行を一括更新する手順
まず結論:複数行UPDATEは「対象確認→安全実行→検証」がセット
複数行のUPDATEは、SQLの書き方そのものより「どう実行するか」の手順が結果を左右します。
特に本番データに対しては、更新対象の見落としや条件ミスがそのまま事故につながるため、やることを固定し、毎回同じ順番で確認するのが最短です。
このとき重要なのは、UPDATEを“いきなり実行しない”ことです。
まず対象を見て、戻せる状態で実行し、最後に狙いどおりだったかを検証します。
ここで紹介する型(対象確認→安全実行→検証)を守れば、複数行でも落ち着いて更新できます。
事故防止の最短手順(SELECT→BEGIN→UPDATE→件数確認→COMMIT/ROLLBACK)
最初に、UPDATEに書く予定のWHEREをそのままSELECTに移し、更新対象が想定どおりかを「行の中身」と「件数」で確認します。
ここで確認したいのは、単なる件数だけではありません。
たとえば「想定外のステータスが混ざっていないか」「境界値(当日を含める/含めない)が合っているか」「NULLの扱いで拾いすぎていないか」など、事故に直結するポイントを先に潰します。
この段階で、想定より多い・少ない、あるいは混ざってはいけない行が含まれている場合は、UPDATEを打たずに条件を見直します。
次にトランザクションを開始して、戻せる状態でUPDATEを実行します(できる環境なら本番でも基本はこの形が安全です)。
実行直後に影響行数を確認し、さらに再SELECTで差分を確認して、狙った行だけが変わったことを確かめます。
ここで「対象外が変わっていないこと」も一緒に見ると、条件ミスに気づきやすくなります。
問題なければCOMMITして確定します。
少しでも違和感があればROLLBACKして、条件やSET内容、AND/ORの括弧、境界値(< / <= など)、さらには文字の大小や前後空白なども含めて見直し、やり直します。
以下は流れをそのまま写せるテンプレです(テーブル名・列名・条件は置き換えて使います)。
— 1) 対象確認(まずは同じWHEREで確認)
SELECT *
FROM users
WHERE status = ‘pending’;
— 2) 安全実行(DBによりBEGIN/START TRANSACTIONなど)
BEGIN;
— 3) 更新
UPDATE users
SET status = ‘active’
WHERE status = ‘pending’;
— 4) 検証(影響行数+再SELECT)
SELECT *
FROM users
WHERE status = ‘active’;
— 5) 確定 or 取り消し
COMMIT;
— ROLLBACK;
補足として、更新前に「対象の主キーだけ」や「更新列だけ」を控えておくと、検証が速くなります。
さらに安全にするなら、更新後に“控えた主キーだけ”で再SELECTして、意図した行が確実に変わっているかを確認するとブレません。
SELECT id
FROM users
WHERE status = ‘pending’
ORDER BY id;
DB方言の注意(MySQL/PostgreSQL/SQL Serverの差分が出やすい所)
UPDATE自体は共通ですが、トランザクションの開始方法(BEGIN / START TRANSACTION など)や、別テーブルの情報を参照しながら更新するJOIN更新で方言が出ます。
特に「別テーブル参照してUPDATEしたい」場合は、PostgreSQLやSQL ServerでUPDATE FROM、MySQLでUPDATE JOINのように書き分けが必要です。
また、日付型のリテラル表記(DATE ‘YYYY-MM-DD’)やINTERVALの書き方、CURRENT_TIMESTAMP/CURRENT_DATEの扱いもDBにより微妙に異なることがあります。
環境差があると「動かない」だけでなく「動くけど意図と違う」になり得るため、実行前に対象確認(SELECT)を徹底するのが最終的な保険になります。
本記事は基本として標準的なUPDATEを中心に説明し、方言が出る箇所は「注意点」として補足します。
SQLで複数行をUPDATEする基本構文
複数行更新は「SETで何を変えるか」と「WHEREでどこまで絞るか」の2点で決まります。
言い換えると、SETは“変更内容”、WHEREは“影響範囲”です。
UPDATEの基本形(SET/WHERE)
UPDATEは指定したテーブルの列を更新する命令です。
SETで更新後の値(上書きする内容)を指定します。
WHEREで更新対象の行を絞り込みます。
基本形は次のとおりです。
UPDATE テーブル名
SET 列1 = 値1, 列2 = 値2
WHERE 条件;
WHEREが複数行に一致すれば、その一致した行がまとめて更新されます。
逆にWHEREが1行だけに一致すれば、更新は1行だけになります。
なぜ複数行になる?(条件が複数レコードに一致する仕組み)
WHEREは「この条件に当てはまる行すべて」を対象にします。
たとえばstatusがpendingのユーザーが10人いれば、同じWHEREで10行が更新されます。
逆に主キー(idなど)で1件に絞っていれば、更新は1行だけになります。
つまり複数行になるかどうかは、条件がユニークかどうか(主キー/ユニーク制約で絞れるか)で決まります。
慣れないうちは「このWHEREは何行に当たるか」をUPDATE前に必ず数える癖を付けるのが安全です。
実行前チェック:同じWHEREでSELECTして件数確認
UPDATEを書く前に、同じWHEREをSELECTで試すのが最も効果的な安全策です。
SELECT結果の件数が想定より多いなら、そのUPDATEは危険です。
逆に少なすぎる場合も、条件ミスやデータ前提のズレが疑えます。
件数だけ確認したい場合はCOUNTを使います。
SELECT COUNT(*)
FROM users
WHERE status = ‘pending’;
更新前の状態をあとで比較できるように、必要なら対象行の主キー一覧も控えます。
SELECT id
FROM users
WHERE status = ‘pending’
ORDER BY id;
この「同じWHEREでSELECT→更新後も同じ観点でSELECT」が、複数行UPDATEを安全にする基本動作です。
WHERE条件で複数行をUPDATEするSQL例
一括更新の多くは、同じ値を同じ条件でまとめて更新するパターンです。
まずはこの型を身に付けると、INやCASEなどの応用も理解しやすくなります。
例:ステータス/フラグをまとめて更新
フラグ更新は実務で最も頻出で、複数行UPDATEの入口として最適です。
たとえば未確認ユーザーを一括で確認済みにするなら次のように書きます。
UPDATE users
SET verified = 1
WHERE verified = 0;
同時に更新日時も更新したい場合はSETに列を追加します。
UPDATE users
SET verified = 1, verified_at = CURRENT_TIMESTAMP
WHERE verified = 0;
このように「同じ条件で同じ値を入れる」更新は、まずSELECTで対象確認さえしておけば比較的安全に実行できます。
例:日付・範囲条件で更新
日付や数値の範囲でまとめて更新するケースも多いです。
たとえば一定期間ログインがないユーザーを休眠扱いにする例です。
UPDATE users
SET status = ‘inactive’
WHERE last_login_at < DATE ‘2025-01-01’;
範囲条件は境界(< と <=)を間違えやすいので、UPDATE前に必ずSELECTで確認します。
SELECT id, last_login_at
FROM users
WHERE last_login_at < DATE ‘2025-01-01’
ORDER BY last_login_at;
この確認をすると、「思ったより古いユーザーが多い」「特定の期間だけ異常に多い」などの前提ズレも見つけやすくなります。
更新後検証(再SELECT/差分確認の観点)
更新後は「対象が変わったこと」と「対象外が変わっていないこと」を確認します。
まず同じWHEREで再度SELECTして、件数が0や想定値になっているかを見ます(例:pendingが0件になった、など)。
次に更新した列が意図どおりの値になっているかを、数件サンプルで確認します。
可能なら更新対象の主キー一覧で絞って再確認すると、検証が安定します。
また、必要に応じて「更新前と更新後の件数」をセットで記録すると、作業ログとしても役立ちます。
複数条件でUPDATEする方法(AND / OR)— 括弧ミス事故を防ぐ
条件が増えるほど対象がズレやすくなるので、ANDとORの優先順位を意識します。
特にORは対象が増える方向に働くため、更新範囲が一気に広がる事故が起きがちです。
ANDで絞る(安全に狭める)
ANDは条件を足すほど対象が減るので、安全側に倒すときに使います。
たとえば特定ステータスかつ特定プランのユーザーだけ更新する例です。
UPDATE users
SET status = ‘active’
WHERE status = ‘pending’
AND plan = ‘pro’;
ANDで条件を追加するときは、追加前後でSELECT件数を比較すると安心です。
「追加した条件で何件減ったか」を見るだけでも、意図した絞り込みになっているか判断しやすくなります。
ORで広げる(想定外に増えるリスク)
ORは条件を足すほど対象が増えるので、更新範囲が広がるリスクがあります。
たとえば2つのステータスをまとめて更新する例です。
UPDATE users
SET flagged = 1
WHERE status = ‘suspended’
OR status = ‘banned’;
ORを使うときは、更新対象が増える前提で件数確認を丁寧に行います。
可能ならORの各条件を別々にCOUNTして、合算したときのイメージを持ってからUPDATEすると安全です。
NG→OK:AND/OR混在は括弧が必須
ANDとORを混ぜると、括弧がない場合に意図と違う行が対象になることがあります。
まず事故が起きやすいNG例です。
— NG例:意図は「pending かつ(pro または enterprise)」だが違う結果になりうる
UPDATE users
SET status = ‘active’
WHERE status = ‘pending’
AND plan = ‘pro’
OR plan = ‘enterprise’;
次に意図を固定するOK例です。
— OK例:括弧で条件のまとまりを明確にする
UPDATE users
SET status = ‘active’
WHERE status = ‘pending’
AND (plan = ‘pro’ OR plan = ‘enterprise’);
混在条件は、括弧を付けた上で同じ条件をSELECTに移して結果を確認します。
このとき、意図したグルーピングになっているかを“言葉にして読む”(例:pending かつ(proまたはenterprise))とミスが減ります。
IN句で複数IDをまとめてUPDATE(直書き vs サブクエリ)
INは「この集合に含まれる行だけ」を狙い撃ちできるので、一括更新を安全にしやすい手段です。
WHEREの条件が複雑になりそうなときほど、いったんID集合に落としてから更新すると見通しが良くなります。
IN(ID直書き)の基本例
手元に更新対象のIDが決まっているなら、INでまとめて指定できます。
UPDATE users
SET status = ‘active’
WHERE id IN (101, 205, 330);
ID直書きは早い反面、対象が増えると読みづらくなります。
また、手作業でIDを作る場合は重複や抜けが起きやすいので、可能なら元となる抽出SQLも残しておくと安全です。
IN(サブクエリ)を優先すべき場面
条件で抽出したID群をそのままINに渡すと、再現性が高くなります。
たとえば「直近30日で未払いがあるユーザーだけフラグを立てる」例です。
UPDATE users
SET billing_hold = 1
WHERE id IN (
SELECT user_id
FROM invoices
WHERE status = ‘unpaid’
AND issued_at >= CURRENT_DATE – INTERVAL ’30 day’
);
サブクエリを使う場合でも、まずサブクエリ単体をSELECTとして実行して対象IDを確認します。
さらに安全にするなら、サブクエリの結果件数をCOUNTしてからUPDATEに進むと、規模感の取り違えを防げます。
IDが多いときの注意(可読性/運用/パラメータ化)
IDが多い場合は、アプリ側でパラメータ化して渡すか、一時テーブルにIDを入れてJOIN更新を検討します。
SQLの可読性が落ちると、レビューや検証で事故が起きやすくなります。
「ID集合を管理する仕組み」を用意すると、更新のたびに手作業で列挙する必要が減ります。
特に定期バッチや運用作業では、ID集合(対象リスト)を作る工程と、更新する工程を分離しておくと、確認がしやすくなります。
CASEで複数行を異なる値でUPDATE(ELSE必須+WHEREで限定)
CASEを使うと、1回のUPDATEで行ごとに違う値を割り当てられます。
ただし強力な分、範囲を間違えると広範囲を上書きできてしまうので、ELSEとWHEREをセットで考えるのが基本です。
CASE更新の基本例
代表例は、IDごとにランクやスコアを割り当てる更新です。
UPDATE users
SET rank = CASE id
WHEN 101 THEN ‘gold’
WHEN 205 THEN ‘silver’
WHEN 330 THEN ‘bronze’
ELSE rank
END
WHERE id IN (101, 205, 330);
このようにCASEはSETの右辺に書き、どの条件で何を入れるかを列挙します。
「ELSEで変更しない」「WHEREで対象を限定する」を同時に入れておくと、想定外の行が混ざっても被害が小さくなります。
ELSEがないと何が起きる?(NULL化など)+ELSEの書き方
ELSEを書かないと、どのWHENにも当てはまらない行がNULLになる方言や設定があり得ます。
特に列がNULLを許容している場合、気づきにくい形でデータが壊れることがあります。
安全のために、基本はELSEで「変更しない」か「明示値」を入れます。
UPDATE users
SET score = CASE
WHEN country = ‘JP’ THEN 10
WHEN country = ‘US’ THEN 8
ELSE score
END;
変更しない場合はELSEに同じ列を戻すのが分かりやすいです。
一方で「当てはまらない行は必ずエラーにしたい」というケースは、先にSELECTで当てはまらない行が存在しないことを確認してからUPDATEします。
WHEREで対象を限定する
CASEは便利ですが、WHEREを付けないとテーブル全体に評価が走ります。
意図した範囲だけに適用するために、CASEとWHEREをセットにします。
UPDATE users
SET tier = CASE
WHEN spend >= 100000 THEN ‘A’
WHEN spend >= 50000 THEN ‘B’
ELSE ‘C’
END
WHERE status = ‘active’;
対象が多い更新ほど、WHEREで限定してから段階的に適用します。
たとえば最初は1日分だけ、あるいは特定のIDレンジだけに当てて、結果を見ながら広げると安全です。
分岐が多いときの代替(JOIN更新/一時テーブル/更新用マスタ)
WHENが増えすぎるとSQLが読めなくなり、保守と検証が難しくなります。
その場合は「更新用の対応表」をテーブル化してJOIN更新する方が安全です。
また一時テーブルにIDと更新値を入れてから更新すると、レビューしやすい形にできます。
“更新する値の根拠”をテーブルとして残せるので、後から追跡したい業務でも有効です。
UPDATE時の注意点(WHEREなしは危険)+復旧策
UPDATEは一度確定すると戻せないことがあるので、実行前後のガードを仕組みにします。
ここは知識というより運用で、ルール化できるほど安全になります。
WHEREなし更新が起きる原因
WHEREを書き忘れるミスが最も多い原因です。
WHEREがあっても条件が緩すぎて全件に当たるケースもあります。
さらにAND/ORの括弧ミスで対象が広がる事故も頻発します。
加えて、コピー&ペーストで別のSQLのWHEREを誤って持ってきてしまうミスも多いので、実行前のSELECT確認が効きます。
トランザクションで安全に試す(COMMIT/ROLLBACK)
更新は可能な限りトランザクション内で実行します。
更新後にSELECTで検証してからCOMMITすれば、違和感があればROLLBACKで戻せます。
BEGIN;
UPDATE users
SET status = ‘inactive’
WHERE last_login_at < DATE ‘2025-01-01’;
SELECT COUNT(*)
FROM users
WHERE last_login_at < DATE ‘2025-01-01’
AND status = ‘inactive’;
— 問題なければ
COMMIT;
— 問題があれば
— ROLLBACK;
運用上は「検証SQLをセットで用意する」ことが最も効きます。
たとえば“更新前件数/更新後件数/想定外が混ざっていないか”を1セットにしておくと、作業が安定します。
復旧の考え方(更新前SELECT保存/バックアップ/ログの取り方)
万一に備えて、更新前に対象行の主キーと更新対象列を控えるのが現実的です。
更新対象が小さいなら、更新前SELECTの結果をエクスポートして差分を追えるようにします。
本番ではバックアップと監査ログが前提になるので、更新系作業のルールに組み込みます。
加えて「更新した人・時刻・理由・SQL」を最低限残すだけでも、トラブル時の復旧判断が速くなります。
本番チェックリスト(実行前・実行直後)
実行前は「同じWHEREでSELECTして件数確認」を必ず行います。
実行中はトランザクションを使い、検証が終わるまでCOMMITしません。
実行直後は影響行数と再SELECTを確認し、想定外があれば即ROLLBACKします。
チーム運用なら、更新SQLはレビュー(もう一人がWHEREと括弧を確認)を挟むだけでも事故率が下がります。
よくある質問(Q & A)(3問に絞る)
最後に、複数行UPDATEでよく詰まるポイントを短く整理します。
ここだけ先に読んでも、最低限の不安が解消できるようにまとめます。
更新件数(影響行数)を確認する方法は?
多くのDBやクライアントはUPDATE実行後に影響行数を表示します。
確実に把握したい場合は、UPDATE前にCOUNTで件数を取ります。
更新後は同じ条件で再度COUNTし、想定どおり変化したか確認します。
さらに安全にしたい場合は、主キー一覧を控えておき、更新後にそのIDだけ再確認すると検証がブレません。
更新を“安全に戻す”には?
最も安全なのはトランザクション内で実行して、検証後にCOMMITする運用です。
COMMIT後に戻す必要がある場合は、バックアップや監査ログがないと復旧が難しくなります。
更新前のSELECT結果を控える運用は、復旧の手がかりとして有効です。
「戻すSQL(元の値に戻すUPDATE)」を作るには、元データが必須なので、更新前の控えが効いてきます。
CASE更新で漏れが出るのはなぜ?
WHENに当たらない行が想定より多いと、更新漏れや意図しない値になります。
ELSEで「変更しない」か「デフォルト」を明示すると漏れを検知しやすいです。
加えてWHEREで対象を限定し、段階的に適用すると原因切り分けが簡単です。
漏れを見つけたいときは、WHEN条件に当たらない行だけをSELECTで抽出して確認すると早いです。
まとめ
複数行UPDATEは「同じWHEREでSELECT確認」を起点にすると安全性が一気に上がります。
次にINで狙いどおりに絞り、CASEはELSEとWHEREをセットにして事故を防ぎます。
最後にトランザクションと検証SQLをルール化すれば、複数行更新でも怖くなくなります。
迷ったら、更新そのものを工夫するよりも、更新前後の確認を増やす方が確実です。