sql update徹底ガイド:構文・CASE・JOIN

UPDATE文の基礎から応用までを網羅。WHERE/INでの対象限定、文字列結合や計算、JOIN・サブクエリで他表更新、CASE条件分岐、Snowflakeの構文と注意点、やりがちな複雑化のアンチパターンまで、具体例で安全な更新手順が分かります。

目次

UPDATE文とは(SQL UPDATEの概要)

sql+update+syntax

SQL UPDATE(sql update)は、既存レコードの値を変更するためのデータ操作言語(DML)の代表的な文です。CRUDで言えば「U(Update)」に相当し、業務アプリケーションからデータウェアハウス、分析基盤まで幅広く用いられます。データの品質維持(誤入力の修正)、状態遷移(ワークフローの進行)、再計算(在庫やスコアの更新)など、日常的な運用業務の中心を担う重要なコマンドです。適切に使うためには「どの行を」「どの値に」更新するかを明確にし、制約やトランザクションの影響を理解しておくことが不可欠です。

UPDATEの役割と基本概念

UPDATE文の本質は、既存データの一部を意図通りに置き換えることです。以下の観点を押さえると、誤操作を避けつつ堅牢に活用できます。

  • 対象の特定: 通常は特定テーブル(または更新可能ビュー)に対して実行し、条件で対象行を絞り込みます。条件を欠くと全件が対象になり得るため、運用では常に要注意です。
  • 変更内容の指定: 1つ以上のカラムに新しい値を設定します。新しい値はリテラルに限らず、計算式、関数、他カラムの値、サブクエリの結果なども指定できます。
  • 同時更新と一貫性: 多数の行・複数カラムを一度に更新できます。多くのRDBMSはトランザクションで原子性を保証し、途中失敗時はロールバック可能です。整合性制約やトリガーも更新時に評価されます。
  • 更新結果の取り扱い: 影響行数(何行更新したか)が返るのが一般的で、データベースによっては更新後の値をその場で受け取れる仕組み(例: RETURNING/OUTPUT)もあります。
  • 代表的なユースケース: ステータスやフラグの切替、在庫数量の加減算、住所・氏名の修正、更新日時・更新者の付与、楽観的ロック用バージョン番号のインクリメントなど。
  • パフォーマンスの勘所: 適切な条件指定とインデックス活用が鍵です。大規模更新はI/Oやロックの負荷が高くなりやすく、バッチ設計やスケジューリングの配慮が求められます。

データベースによる方言の存在

UPDATE文は標準SQLの枠組みがありますが、主要RDBMSごとに便利機能や拡張が異なります。移植性や最適化を考える際は、以下の方言差を把握しておくと安心です。

  • PostgreSQL: UPDATEでFROM句を使った結合更新やRETURNINGで更新後の値の取得が可能。柔軟なサブクエリ・エイリアス指定が特徴です。
  • MySQL/MariaDB: UPDATE ... JOINによる結合更新をサポート。MySQLはORDER BYLIMIT付きのUPDATEが可能です。RETURNINGの可否は実装やバージョンで扱いが異なるため公式ドキュメントの確認が安全です。
  • SQL Server: UPDATE ... FROMでの結合更新、影響行の新旧値を返すOUTPUT、対象行を制限するTOPなどを提供します。
  • Oracle Database: 結合を伴う更新は相関サブクエリやMERGEで表現するのが一般的。PL/SQLではRETURNING INTOで更新後の値を変数に受け取れます。
  • SQLite: 近年のバージョンでUPDATE ... FROMRETURNINGが利用可能になりましたが、利用可否はビルドやバージョン依存のため要確認です。
  • Snowflake: 分析基盤向けにFROMを伴うUPDATEやサブクエリ更新をサポート。更新結果の取得は別途SELECTで確認する運用が一般的です。

同じ「SQL UPDATE」でも、結合の書き方、結果の受け取り方、件数制限(LIMIT/TOP)の有無などがベンダーで異なります。移植性重視なら標準的な書き方を優先し、性能や表現力を重視する場合は各データベースの拡張機能を積極的に検討するとよいでしょう。

UPDATE文の構文

sql+update+syntax

sql update(UPDATE文)は、既存行の列値を書き換えるためのSQL文です。ここでは「構文」に絞って、最小構成から主要データベースの方言が使う拡張句まで、順を追って整理します。[]は任意句を示します。

基本構文の書き方

最もシンプルなUPDATE文は、更新対象のテーブル(または更新可能ビュー)を指定し、SETで列と新しい値(式)を並べます。条件を付ける場合はWHEREを使います。

-- 概念的な基本形(標準的な書き方)
UPDATE target_table
SET    column1 = expression1,
       column2 = expression2
[WHERE search_condition];
  • target_table: 更新対象のテーブル名(スキーマ修飾可)。
  • SET: 1つ以上の「列 = 式」をカンマで列挙。
  • WHERE: 更新対象行を絞る検索条件(省略時は構文上は全行が対象)。

式にはリテラル、他列を使った算術・文字列演算、関数呼び出し、スカラ副問い合わせなどを指定できます。多くの実装で型の整合性が要求され、必要に応じて暗黙/明示の型変換が行われます。

必須句(ターゲットとSET)

UPDATE文で必ず必要なのは「更新ターゲット(テーブル/ビュー)」と「SET句」です。sql updateの可読性と安全性を高めるため、それぞれの書き方の要点を押さえましょう。

  • ターゲット(更新対象)
    • 形式: UPDATE schema_name.table_name [AS alias]
    • エイリアス(alias)を付けると、SETや条件式で alias.column という短い参照が可能になります(ASの可否は方言に依存)。
    • 多くのDBで更新可能ビュー(updatable view)も対象にできます(ビュー側の定義に依存)。
  • SET句(更新内容の指定)
    • 複数列を更新: SET c1 = v1, c2 = v2, ...
    • 式の例: 定数、他列参照、算術/文字列演算、関数、スカラ副問い合わせ(SET c = (SELECT ...)
    • 特殊値: SET col = NULL(NULL代入)、SET col = DEFAULT(列のデフォルト値)
    • 一部の実装(例: PostgreSQL)は行コンストラクタを使った複数列同時代入をサポート:
      UPDATE t
      SET (c1, c2) = (SELECT x, y FROM src WHERE src.id = t.id);

例(基本形):

UPDATE employees
SET    title = 'Senior ' || title,      -- 文字列結合(方言により CONCAT 等)
       salary = salary * 1.05;          -- 算術演算

任意句(WHERE、FROM、RETURNING など)

UPDATEには目的やデータベース方言に応じて追加できる任意句があります。ここでは代表的なものの「構文的な位置づけ」と「主な方言差」を示します。

  • WHERE句(条件指定)
    • 位置: SET ... [WHERE 条件]
    • 役割: 更新対象の行を絞り込みます。条件はSELECT同様に論理式(AND/OR/NOT、比較、IN、EXISTSなど)が書けます。
  • FROM句(他テーブル参照による更新・結合更新)
    • PostgreSQL など:
      UPDATE target AS t
      SET    col = expr
      FROM   other AS o
      WHERE  o.key = t.key;
    • SQL Server(FROMを使う形):
      UPDATE t
      SET    t.col = expr
      FROM   dbo.Target AS t
      JOIN   dbo.Other  AS o ON o.key = t.key;
    • MySQL(JOINをUPDATE直後に書く形):
      UPDATE target AS t
      JOIN   other  AS o ON o.key = t.key
      SET    t.col = expr;
    • OracleはFROM句ではなく相関副問い合わせで同等のことを表現するのが一般的です。
  • RETURNING/OUTPUT(更新後の値を返す)
    • PostgreSQL(RETURNING):
      UPDATE accounts
      SET    balance = balance - 100
      WHERE  id = 1
      RETURNING id, balance;  -- 更新された行の列を返す
    • SQL Server(OUTPUT):
      UPDATE dbo.Accounts
      SET    balance = balance - 100
      OUTPUT inserted.id, inserted.balance;  -- 変更後は inserted、変更前は deleted
    • OracleはPL/SQL文脈で RETURNING ... INTO 変数 を使用します。
  • 更新件数の制御(方言ごと)
    • MySQL: ORDER BY ... LIMIT n をUPDATEで使用可
      UPDATE tasks
      SET    status = 'processing'
      ORDER BY created_at
      LIMIT  100;
    • SQL Server: UPDATE TOP (n) が利用可
      UPDATE TOP (100) t
      SET    t.status = 'processing'
      FROM   dbo.Tasks AS t
      ORDER BY t.created_at;  -- ORDER BYはサブクエリなどで明示するのが確実
    • 標準SQLには件数制御の規定はなく、方言依存です。

このように、sql update の「核」は UPDATE … SET … [WHERE …] ですが、FROMやRETURNING(またはOUTPUT)といった拡張句を組み合わせることで、他テーブル参照や更新結果の即時取得など、現実的な要件に即した構文が書けます。利用中のデータベースの文法に合わせて句の位置と書式を選択してください。

基本的な使い方

sql+update+database

SQL UPDATE(sql update)は、既存データを条件に応じて修正するための中心的なコマンドです。ここでは、現場でよく使う「条件付き更新」「全件更新」「複数カラム更新」「IN句での一括指定」「計算式・関数を使った更新」を、短い実例とともに整理します。

条件を指定したレコード更新(WHERE)

WHERE句で対象レコードを絞り込み、必要な行だけを更新します。WHEREを付けないと全件が更新されるため、更新対象の明確化が重要です。

-- 部署がSalesかつ在籍中の社員の給与を5,000アップ
UPDATE employees
SET salary = salary + 5000
WHERE dept = 'Sales' AND status = 'active';
-- 在庫が0の商品のステータスをdiscontinuedへ
UPDATE products
SET status = 'discontinued'
WHERE stock = 0;
  • 複合条件: AND/OR、比較演算子(=、<>、>= など)、NULL判定(IS NULL/IS NOT NULL)を組み合わせます。
  • 文字列の部分一致更新にはLIKE(例: WHERE name LIKE ‘A%’) を使用します。

全レコードの一括更新

全行に同じ変更を適用する場合は、WHERE句を省略します。システムメンテナンス時のフラグ付与や基準日の更新などに使います。

-- 全ユーザーの規約改定日を更新
UPDATE users
SET terms_version_date = CURRENT_DATE;
-- 全商品の表示価格を税込価格へ切り替え
UPDATE products
SET display_price = price_with_tax;
  • 全件更新は影響範囲が大きいため、意図した対象かどうかを事前に確認してから実行しましょう。

複数カラムを同時に更新

1回のSQL UPDATEで複数カラムをカンマ区切りで指定して更新できます。行ごとに同時に評価されるため、相互依存する計算も書きやすくなります。

-- 顧客のステータス変更と、更新者/更新日時を同時に記録
UPDATE customers
SET status = 'VIP',
    updated_by = 'system',
    updated_at = CURRENT_TIMESTAMP
WHERE total_spent >= 100000;
-- 単価と合計金額を同時更新(合計は新単価×数量で再計算)
UPDATE order_items
SET unit_price = unit_price * 0.95,
    total_amount = (unit_price * 0.95) * quantity
WHERE campaign_id = 123;
  • 複数列の更新は、一貫性のある状態を1クエリで作れるため、整合性を保ちやすいのが利点です。

IN句で複数の対象を指定して更新

IN句を使うと、複数の値にマッチする行をまとめて更新できます。外部リストや固定の小さな集合に対して便利です。

-- 指定部門(Sales/Marketing/CS)の予算年度を更新
UPDATE departments
SET fiscal_year = 2025
WHERE dept_name IN ('Sales', 'Marketing', 'Customer Success');
-- 特定IDの注文を一括で確定へ
UPDATE orders
SET status = 'confirmed'
WHERE order_id IN (10101, 10105, 10112, 10145);
  • NOT INで逆条件も書けます(例: WHERE region NOT IN (‘APAC’,’EMEA’))。

計算式・文字列結合・関数を用いた更新

sql updateでは、数値計算、文字列連結、日付・文字列関数などを駆使して動的に値を生成できます。実データに応じて変換・加工を行う基本パターンです。

-- 数値計算: 価格を10%値上げし、四捨五入
UPDATE products
SET price = ROUND(price * 1.10, 0)
WHERE category = 'accessory';
-- 文字列結合(PostgreSQL/Oracle の例)
UPDATE members
SET display_name = first_name || ' ' || last_name;

-- 文字列結合(MySQL/MariaDB の例)
UPDATE members
SET display_name = CONCAT(first_name, ' ', last_name);
-- 日付・時刻関数: 最終ログインを現在時刻に更新
UPDATE users
SET last_login_at = CURRENT_TIMESTAMP
WHERE user_id = 42;
-- 文字列・整形関数の例: 前後の空白除去と大文字化
UPDATE tags
SET code = UPPER(TRIM(code));
  • よく使う関数: ROUND/CEIL/FLOOR(数値)、CONCAT/||/SUBSTRING(文字列)、CURRENT_DATE/CURRENT_TIMESTAMP(日時)、TRIM/REPLACE(整形)など。
  • 関数名や演算子はデータベースによって異なる場合があります。対象DBの文法に合わせて選択してください。

応用テクニック

sql+update+join

基本的なUPDATE文に慣れたら、より複雑な要件に応えるための応用テクニックを押さえておくと、保守性と実行効率の両面で有利です。ここでは「サブクエリで値を取得して更新」「テーブル結合(JOIN)を用いた更新」「CASE式を組み合わせた条件分岐更新」の3つを中心に、現場で使えるsql updateの書き方を具体例とともに解説します。

サブクエリで値を取得して更新

サブクエリを使うと、別テーブルや集計結果から導いた単一値を、対象行ごとに計算して更新できます。ポイントは「各行に対して1行(1値)だけ返す」ように設計することです。多行を返すとエラーや意図しない結果になります。

-- 例1: 注文ごとの合計金額を明細から集計して更新(相関サブクエリ)
UPDATE orders o
SET total_amount = (
  SELECT COALESCE(SUM(oi.amount), 0)
  FROM order_items oi
  WHERE oi.order_id = o.id
)
WHERE o.status = 'OPEN';

-- 例2: 部署平均より低い給与の人だけ平均まで引き上げ
UPDATE employees e
SET salary = (
  SELECT AVG(e2.salary)
  FROM employees e2
  WHERE e2.dept_id = e.dept_id
)
WHERE e.salary < (
  SELECT AVG(e3.salary)
  FROM employees e3
  WHERE e3.dept_id = e.dept_id
);
  • 単一値制約: サブクエリは1行1列を返す構造にする(集約関数やLIMITなどで制御)。
  • NULL対策: COALESCEで未一致・空集合時のNULLを既定値に置き換える。
  • 性能の勘所: 相関サブクエリは行ごとに実行されるため、結合更新に置換できるならJOINへ(後述)。適切なインデックス(例: order_items.order_id)で大幅に改善します。

テーブル結合(JOIN)を用いた更新

JOINを使った更新は、別テーブルの列で上書き・補完・マッピングするケースで強力です。RDBMSにより書き方が異なるため、代表的な方言を押さえておきましょう。

-- PostgreSQL(FROM句)
UPDATE products p
SET price = np.new_price
FROM new_prices np
WHERE p.sku = np.sku;

-- MySQL(JOIN句)
UPDATE products p
JOIN new_prices np ON p.sku = np.sku
SET p.price = np.new_price;

-- SQL Server(FROM + JOIN)
UPDATE p
SET p.price = np.new_price
FROM dbo.products p
JOIN dbo.new_prices np ON p.sku = np.sku;

一対多の結合で重複マッチが発生すると、どの行で更新するかが曖昧になります。ユニーク制約で重複を防ぐか、集計・絞り込みで一意にしましょう。

-- 重複マッチを避けるため最新レコードだけに絞る(PostgreSQL例)
WITH latest AS (
  SELECT sku, new_price,
         ROW_NUMBER() OVER (PARTITION BY sku ORDER BY effective_at DESC) AS rn
  FROM new_prices
)
UPDATE products p
SET price = l.new_price
FROM latest l
WHERE p.sku = l.sku
  AND l.rn = 1;
  • 一意性の担保: 結合キーにユニーク制約がない場合は、ウィンドウ関数やサブクエリで1行に絞る。
  • 部分更新: SET句で更新対象を最小限にするとロックとログ量を抑えられます。
  • 検証のコツ: 同じJOIN条件でまずSELECTしてヒット件数と値の対応を確認してから実行するのが定石です。

CASE式を組み合わせた条件分岐更新

CASE式をSET句に埋め込むと、行ごとに更新ロジックを切り替えられます。複数条件の優先順位を明示でき、if-else的な分岐を1回のsql updateで実現できます。

-- 例1: ステータスの自動判定
UPDATE orders
SET status = CASE
  WHEN canceled_at IS NOT NULL THEN 'CANCELED'
  WHEN shipped_at  IS NOT NULL THEN 'SHIPPED'
  WHEN paid_at     IS NOT NULL THEN 'PAID'
  ELSE 'PENDING'
END
WHERE status <> 'CANCELED';

-- 例2: 在庫の安全在庫割れは再発注、それ以外は微調整
UPDATE inventories i
SET quantity = CASE
  WHEN i.quantity < i.safety_stock THEN i.quantity + i.reorder_qty
  WHEN i.quantity BETWEEN i.safety_stock AND i.safety_stock * 2 THEN i.quantity + 5
  ELSE i.quantity
END;

-- 例3: 値上げ・値下げ・据え置きを一括で
UPDATE products
SET price = CASE
  WHEN category = 'CLEARANCE' THEN price * 0.9
  WHEN category = 'PREMIUM'   THEN price * 1.05
  ELSE price
END;

-- 例4: 0除算や型不一致を避ける安全な分岐
UPDATE kpis
SET ratio = CASE
  WHEN denominator = 0 OR denominator IS NULL THEN NULL
  ELSE numerator * 1.0 / NULLIF(denominator, 0)
END;
  • フォールバック: ELSEで「元の値(列名)」を返すと、条件外の行は変更されません。
  • 優先順位: 条件は上から評価されるため、互いに重なる場合は厳しい条件を先に書くと意図が明確。
  • 型整合性: CASEの各分岐は同じ型に統一(必要ならCAST)。混在すると暗黙の型変換で予期せぬ丸めが起こり得ます。

CASE式の基礎とUPDATEでの活用

sql+update+case

CASE式はSQL標準の条件分岐構文で、複数条件に応じた値の切り替えを1文で表現できます。特に sql update と組み合わせると、レコードごとに異なる更新値を柔軟に割り当てられ、複数のUPDATEを一つに集約できます。ここでは、CASE式の基本から、UPDATEでの具体的な活用までを整理します。

CASE式の基本(単純CASEと検索CASE)

CASEには大きく2種類あります。比較対象を1つに固定する「単純CASE」と、任意の論理式で分岐する「検索CASE」です。どちらも最初に一致したWHEN句が採用され、どれにも一致しない場合はELSE(省略時はNULL)が返ります。

単純CASEの書き方と適用例

単純CASEは「ある式=値」の一致で分岐します。コード体系の変換やラベル付けに向いています。

-- 単純CASEの基本形
CASE 式
  WHEN 値1 THEN 結果1
  WHEN 値2 THEN 結果2
  ELSE デフォルト結果
END

-- 適用例:カテゴリコードを人が読めるラベルへ変換
SELECT product_id,
       CASE category_code
         WHEN 'A' THEN 'Premium'
         WHEN 'B' THEN 'Standard'
         WHEN 'C' THEN 'Economy'
         ELSE 'Other'
       END AS category_label
FROM products;

ポイント:

  • 比較は「=」で行われます。範囲判定や複合条件には不向きです。
  • ELSEを省略すると一致なしはNULLになります。明示的なELSEを推奨します。

検索CASEの書き方と適用例

検索CASEは各WHENに任意の条件式を置けるため、範囲・複合条件・NULL判定など幅広く使えます。

-- 検索CASEの基本形
CASE
  WHEN 条件1 THEN 結果1
  WHEN 条件2 THEN 結果2
  ELSE デフォルト結果
END

-- 適用例:購入金額に応じて会員ランクを判定
SELECT customer_id,
       CASE
         WHEN total_amount >= 100000 THEN 'Platinum'
         WHEN total_amount >= 50000  THEN 'Gold'
         WHEN total_amount >= 10000  THEN 'Silver'
         ELSE 'Bronze'
       END AS membership_tier
FROM orders;

ポイント:

  • 先に書いた条件が優先されます(上から評価)。閾値の重なりに注意。
  • NULLの扱いは演算子に依存します。IS NULL/IS NOT NULLで明示的に扱うと安全です。

LIKEを使った曖昧一致の条件分岐

部分一致・前方/後方一致の判定をCASEに組み込むと、テキストパターンに応じた分類や更新が可能です。

-- メールドメインや接頭辞でタイプ分類
SELECT customer_id,
       CASE
         WHEN email LIKE '%@example.com' THEN 'Corporate'
         WHEN email LIKE 'test_%'         THEN 'Internal Test'
         ELSE 'General'
       END AS email_type
FROM customers;
  • %は任意長のワイルドカード、_は任意1文字。必要に応じてエスケープ文字を指定します。
  • 大文字小文字の扱いはDBごとに異なる場合があります。必要ならLOWER()などで正規化します。

CASEの入れ子と優先順位の整理

複雑なロジックはCASEの入れ子で表現できますが、読みやすさと評価順の明確化が重要です。

-- ステータスと配送状況による多段ロジック
SELECT order_id,
       CASE
         WHEN status = 'CANCELLED' THEN 'N/A'
         WHEN status = 'SHIPPED' THEN
           CASE
             WHEN delivered_at IS NOT NULL THEN 'Delivered'
             ELSE 'In transit'
           END
         ELSE 'Pending'
       END AS fulfillment_state
FROM orders;
  • 「最も限定的」「ビジネス上の優先度が高い」条件を上に置くと誤分類を防げます。
  • 入れ子が深くなる場合は、共通条件を上位のWHENへ寄せる、またはロジックを関数化(DB依存)して可読性を保ちます。
  • ELSEを忘れると想定外のNULLが発生します。特に UPDATE では注意が必要です。

UPDATE文へのCASE式の組み込みパターン

CASEはUPDATEのSET句に直接埋め込み、行ごとに更新値を切り替えられます。これにより、複数の sql update を1回にまとめ、トランザクション回数やロック時間を抑制できます。

-- パターン1:数値の閾値でラベルを一括更新
UPDATE users
SET rank = CASE
             WHEN score >= 1000 THEN 'S'
             WHEN score >= 700  THEN 'A'
             WHEN score >= 400  THEN 'B'
             ELSE 'C'
           END
WHERE active = TRUE;
-- パターン2:単純CASEでコード値を定数にマップ
UPDATE orders
SET shipping_fee = CASE shipping_method
                     WHEN 'EXPRESS'  THEN 1200
                     WHEN 'STANDARD' THEN  500
                     ELSE 0
                   END;
-- パターン3:複数カラムを独立したCASEで同時更新
UPDATE orders
SET shipping_fee  = CASE shipping_method
                      WHEN 'EXPRESS' THEN 1200
                      WHEN 'STANDARD' THEN 500
                      ELSE 0
                    END,
    delivery_days = CASE
                      WHEN shipping_method = 'EXPRESS'  THEN 1
                      WHEN shipping_method = 'STANDARD' THEN 3
                      ELSE 5
                    END;
-- パターン4:LIKEで曖昧一致しつつ、該当なしは現値維持
UPDATE products
SET brand = CASE
              WHEN name LIKE 'Apple %'   THEN 'Apple'
              WHEN name LIKE 'Samsung %' THEN 'Samsung'
              ELSE brand  -- マッチしない行は既存値を保持
            END;
-- パターン5:条件付きの増減(在庫の拠点別調整など)
UPDATE inventory
SET stock = stock + CASE
                      WHEN location = 'TOKYO' THEN  10
                      WHEN location = 'OSAKA' THEN  -5
                      ELSE 0
                    END;
  • ELSEで「現値(列名)」を返すと、非該当行の値保持が明示でき安全です。
  • CASEはSET句内であれば複数回使えます。列ごとに独立して分岐可能です。
  • 評価は上から順に行われ、最初に一致したWHENが採用されます。閾値やLIKEの順番に注意してください。

使用上の注意

sql+update+database

SQL UPDATE(sql update)は強力な一括変更が可能な反面、誤用すると大規模なデータ破壊や長時間ロックを招きます。以下では、実運用での事故を避けるための必須チェックと、安全に実行するための手順・テクニックを整理します。

WHERE句の漏れによる全件更新の防止策

UPDATEにWHERE句が無い、または条件が広すぎると、全件更新・想定外の大量更新につながります。次の原則でガードを固めましょう。

  • SELECTから書き始める: まず対象行を抽出するSELECTを完成させ、件数や内訳を確認してからUPDATEに置き換えます。
  • 現値を条件に含める: 「WHERE id=…」に加え「AND status=’pending’」のように現状の値で絞ると誤爆が減ります。
  • 主キー・ユニークキーで限定: 曖昧な条件よりも、IDやユニークキーのリストで対象を確定します。
  • トランザクションで影響件数を確認: BEGIN後にUPDATEし、影響行数が想定外なら即ROLLBACKします(詳細は後述)。
  • 開発時のフェイルクローズ: 構築中は「AND 1=0」を一時的に入れておき、条件が固まってから外すと誤実行を防げます。
  • 権限・ポリシーによる防御: UPDATE可能なカラム・テーブルを最小限にし、「WHEREのないUPDATEを拒否する」ルールを運用に組み込む(コードレビュー・Linter・ルールベースの運用)
-- 1) まず対象確認(期待する件数・内訳をチェック)
SELECT id, status
FROM users
WHERE status = 'inactive' AND last_login >= DATE '2024-01-01';

-- 2) 更新(現値条件を含める、トランザクション下で件数確認)
BEGIN;
UPDATE users
SET status = 'active',
    updated_at = CURRENT_TIMESTAMP
WHERE status = 'inactive'
  AND last_login >= DATE '2024-01-01';
-- 影響件数を確認して想定通りなら
COMMIT; -- 想定外なら ROLLBACK;

補足: 一部の製品には安全モード(例: WHEREなしUPDATEの抑止)や「LIMIT付きUPDATE」がありますが、DBMSごとに仕様差が大きいため、本番の保険は「SELECTでの検証」「トランザクション」「権限設計」で担保するのが堅実です。

トランザクションとロックの留意点

sql updateは対象行にロックを獲得します。長時間のトランザクションやインデックス不備は、競合・待ち時間・デッドロックを誘発します。以下のポイントを押さえましょう。

  • トランザクションは短く小さく: 不必要な処理を同一トランザクションに入れない。更新対象を絞り、即時COMMIT。
  • 適切なインデックス: WHERE句に使う列へインデックスがないと全表走査になり、ロック保持時間が伸びます。
  • 一括より分割: 大量更新は主キー範囲などでバッチに分け、短いトランザクションを繰り返します。
  • 一貫したロック順序: 複数テーブル・複数行を更新する場合は、同じ順序・同じキーの並びで取得してデッドロックを減らす。
  • 隔離レベルの影響を理解: 高い隔離レベルほど競合に敏感。要件に応じて設定を見直し、必要最小限に。
  • ロック待ち/タイムアウトの設定: 待ち時間の上限を設定し、タイムアウト時は安全にリトライできる設計に。
  • SELECT FOR UPDATEなどの明示的ロック: 先に対象行を確定・ロックしてからUPDATEすると対象ぶれや競合を抑制できます(DBのサポートに依存)。
-- 例) 大量更新を範囲で小分けにする(短いトランザクションで回す)
BEGIN;
UPDATE orders
SET status = 'archived'
WHERE id BETWEEN 100001 AND 101000
  AND status = 'shipped';
COMMIT;

-- 次のバッチへ(100001ずつなど、一定幅で繰り返す)

ベストプラクティス: 更新対象の列・結合列に適切なインデックスを用意し、実行前に実行計画を確認。ピーク時間帯は避け、必要ならメンテナンスウィンドウを確保します。

実行前の検証(SELECTで対象確認、バックアップ)

実行前の検証が、sql updateの最大の保険です。対象の可視化、件数の見積もり、復旧手段の準備を怠らないでください。

  1. 対象の見える化
    • 件数確認: SELECT COUNT(*) … WHERE …
    • 属性の内訳: GROUP BYで状態別件数を確認
    • サンプル確認: ORDER/LIMIT等で代表行を目視確認(DBによって書き方は異なる)
  2. バックアップ/ロールバック手段
    • 対象行の退避: 一時/バックアップテーブルへ対象行をコピーしておく(CTASや一時テーブル等、DBに応じた方法)。
    • スナップショット/ポイントインタイムリカバリ: プラットフォームのPITRやスナップショットを計画。
    • 復旧SQLの用意: 重要カラムの旧値を保持し、戻し用UPDATEを事前に作成。
  3. ドライラン/本番前検証
    • 検証環境で同条件を実行し、件数・所要時間・ロック影響を計測。
    • 実行計画(EXPLAIN等)でインデックス利用を確認。
    • トランザクション内で影響件数を確認し、想定内ならCOMMIT、想定外は即ROLLBACK。
-- 対象件数と内訳の確認
SELECT status, COUNT(*)
FROM users
WHERE status = 'inactive' AND last_login >= DATE '2024-01-01'
GROUP BY status;

-- バックアップ(DB/権限に応じた方法で)
CREATE TABLE users_backup_yyyymmdd AS
SELECT * FROM users
WHERE status = 'inactive' AND last_login >= DATE '2024-01-01';

-- トランザクション内で実行し、影響件数を確認
BEGIN;
UPDATE users
SET status = 'active'
WHERE status = 'inactive'
  AND last_login >= DATE '2024-01-01';
-- 影響行数が想定通りか確認後に
COMMIT; -- 想定外なら ROLLBACK;

最後に、作業ログ(誰が・いつ・どの条件で実行したか)を残し、必要に応じて監査可能な形で記録しておくと、万一のトラブル時の原因究明と復旧がスムーズです。

アンチパターンと回避策

sql+database+update

SQL UPDATE(以下、sql update)は強力ですが、1本の文で「結合・集計・条件分岐・副作用管理」をすべて抱え込むと、保守性と性能を急速に損ないます。本節では、複雑すぎるUPDATEのアンチパターンを整理し、実務で使える回避策として「分割更新」と「一時テーブル(ステージング)」の活用方法を解説します。

複雑すぎるUPDATEの問題点

「一発でやり切る」巨大なsql updateは、次のようなトラブルを招きがちです。

  • 可読性・保守性の崩壊:JOINが多段、CASEが入れ子、相関サブクエリが複数……といった文は、レビューが難しく属人化します。仕様変更への追随も困難になります。
  • 誤更新リスクの増大:結合キーの重複で同一行に複数マッチ、CASEの網羅漏れ、NULL比較の抜けなど、テストをすり抜ける論理バグが増えます。行の一意性が担保されないJOINは特に危険です。
  • 性能劣化と長時間ロック:巨大な結合+相関サブクエリ+関数適用によりインデックスが効かず全表スキャン、更新対象が多くなると長時間の行/ページ/テーブルロック、書き込みログやUndoの肥大化、断片化の進行などが起こります。結果としてブロッキングやデッドロックの温床になります。
  • 副作用の増幅:トリガや連鎖更新(FKのON UPDATE)を大量に発火させ、想定以上のI/Oと待機が発生。観測やロールバックのコストも跳ね上がります。
  • 最適化誤りの誘発:多段JOINや複雑なCASEによりカーディナリティ推定が外れ、誤った実行計画を選択。微細なデータ分布の変化で性能が乱高下します。
  • 検証・監査の困難:対象件数や差分の事前把握・事後確認が難しく、障害時の原因追跡や再実行も難度が上がります。

例えば次のような「何でも盛り」なUPDATEは、可読性・性能・安全性の面でアンチパターンになりがちです。

-- アンチパターン例:多段JOIN+相関集計+入れ子CASEを1本で実行
UPDATE orders o
SET status = CASE
               WHEN EXISTS (SELECT 1 FROM payments p
                            WHERE p.order_id = o.id AND p.state = 'paid') THEN 'paid'
               WHEN EXISTS (SELECT 1 FROM refunds r
                            WHERE r.order_id = o.id AND r.approved = 1) THEN 'refunded'
               ELSE o.status
             END,
    total_amount = (
      SELECT SUM(oi.qty * i.price)
      FROM order_items oi
      JOIN items i ON i.id = oi.item_id
      WHERE oi.order_id = o.id
    )
FROM customers c
JOIN regions rg ON rg.id = c.region_id
LEFT JOIN promotions pr ON pr.customer_id = c.id AND pr.active = 1
WHERE o.customer_id = c.id
  AND (o.updated_at < CURRENT_DATE - INTERVAL '30 days' OR pr.id IS NOT NULL);

一見スマートでも、対象・副作用・コストが見通しづらく、運用で痛みやすい構造です。

分割更新や一時テーブルの活用で複雑さを抑える

複雑さは「対象の固定化」と「処理の分解」で大幅に下げられます。次の手順が実務では堅実です。

  • 対象集合と更新値を先に確定(ステージング)

    • 重いJOINや集計はSELECTで先に解決し、一時テーブル(TEMP/ステージング)に格納します。ここで主キーを一意にし、必要なインデックスを付与します。
    -- 対象と新値を固定化
    CREATE TEMP TABLE upd_target AS
    SELECT
      o.id            AS order_id,
      CASE
        WHEN EXISTS (SELECT 1 FROM payments p WHERE p.order_id = o.id AND p.state = 'paid') THEN 'paid'
        WHEN EXISTS (SELECT 1 FROM refunds  r WHERE r.order_id = o.id AND r.approved = 1)  THEN 'refunded'
        ELSE o.status
      END             AS new_status,
      (
        SELECT SUM(oi.qty * i.price)
        FROM order_items oi
        JOIN items i ON i.id = oi.item_id
        WHERE oi.order_id = o.id
      )               AS new_total
    FROM orders o
    WHERE o.updated_at < CURRENT_DATE - INTERVAL '30 days';
    
    -- 一意性と結合性能を担保
    CREATE INDEX ON upd_target(order_id);
    
  • 本体更新は「シンプルな結合だけ」に限定

    • 本体テーブルはステージングと主キーで結合し、純粋な値の適用だけを行います。副作用や高コスト演算はここに含めません。
    -- シンプルなsql updateに分解
    UPDATE orders o
    SET status       = t.new_status,
        total_amount = t.new_total
    FROM upd_target t
    WHERE o.id = t.order_id
      AND (o.status       IS DISTINCT FROM t.new_status
           OR o.total_amount IS DISTINCT FROM t.new_total);
    
  • バッチ更新でロックとログを制御

    • 更新件数が多い場合は、キー範囲やステージング側のフラグで分割し、短いトランザクションを積み上げます。安定した順序(主キー昇順など)で処理し、コミット単位を決めるのがコツです。
    -- 例:ステージング側で未処理フラグを持たせ、少量ずつ適用
    -- (疑似的なイメージ。実際は環境に合わせて制御)
    UPDATE orders o
    SET status       = t.new_status,
        total_amount = t.new_total
    FROM (
      SELECT order_id, new_status, new_total
      FROM upd_target
      WHERE processed = false
      ORDER BY order_id
      LIMIT 10000
    ) t
    WHERE o.id = t.order_id;
    
    -- 適用済みフラグを更新(再実行性と安全性の確保)
    UPDATE upd_target
    SET processed = true
    WHERE order_id IN (
      SELECT order_id FROM upd_target WHERE processed = false ORDER BY order_id LIMIT 10000
    );
    
  • 再実行性・安全性の担保

    • 更新前に件数や差分を計測し、更新条件に「値が変わる場合のみ」を明示。これにより再実行しても同じ結果になる(冪等性)設計にします。必要に応じて更新キーを監査用テーブルに残すと可観測性が向上します。
  • 補助的な最適化

    • ステージングの結合キーにインデックスを付与、関数適用は可能な限り事前計算、統計情報を最新に保つなど、プラン安定化に寄与する施策を併用します。
  • 構文の選択肢

    • 環境が対応していればMERGEで「一致時は更新、非一致時は挿入」を明示するのも一案。ただし、複雑化しすぎないようにステージングと併用するのが無難です。

要点は、重いロジックを「選抜(SELECT)」で完結させ、更新は「短く・決定的に・段階的に」行うことです。これにより、sql updateの可読性、検証容易性、性能、そして運用の安全性が大きく向上します。

サンプルコード集

sql+update+join

ここでは、現場でそのまま使える sql update(SQL UPDATE)文のサンプルを用途別にまとめました。主要RDBMSごとの書き方の違いも最小限に触れつつ、コピペで試せるコード中心に整理しています。

基本構文のサンプル

もっともシンプルな1テーブル更新の例です。複数カラム更新や実行結果確認(RETURNING/OUTPUT)まで含めた最小セットを示します。

汎用(多くのDBで動作)

-- 顧客ステータスを更新(単一行)
UPDATE customers
SET status = 'active',
    updated_at = CURRENT_TIMESTAMP
WHERE customer_id = 1001;

PostgreSQL(更新結果を直ちに確認)

UPDATE customers
SET status = 'active',
    updated_at = NOW()
WHERE last_login_at >= DATE '2025-01-01'
RETURNING customer_id, status, updated_at;

SQL Server(OUTPUTで更新後の値を取得)

UPDATE dbo.Customers
SET Status = 'active',
    UpdatedAt = SYSUTCDATETIME()
OUTPUT inserted.CustomerID, inserted.Status, inserted.UpdatedAt
WHERE LastLoginAt >= '2025-01-01';

MySQL / MariaDB(基本形。影響件数は ROW_COUNT() で確認可能)

UPDATE customers
SET status = 'active',
    updated_at = NOW()
WHERE last_login_at >= '2025-01-01';
-- SELECT ROW_COUNT(); -- 直後に影響件数を取得

Oracle Database(基本形)

UPDATE customers
SET status = 'ACTIVE',
    updated_at = SYSTIMESTAMP
WHERE last_login_at >= TIMESTAMP '2025-01-01 00:00:00';

条件付き更新(計算・CASE)のサンプル

計算式での増減や、CASE式でのランク付けなど、日常的に使う「条件付きの sql update」例です。

価格をカテゴリAのみ一括で10%値上げ(小数2桁に丸め)

-- 多くのDBで動作(ROUNDの挙動はDBごとに僅差あり)
UPDATE products
SET price = ROUND(price * 1.10, 2)
WHERE category = 'A';

顧客の購買累計に応じた会員ティアをCASEで更新

UPDATE customers
SET tier = CASE
             WHEN total_spend >= 300000 THEN 'Platinum'
             WHEN total_spend >= 100000 THEN 'Gold'
             WHEN total_spend >=  50000 THEN 'Silver'
             ELSE 'Bronze'
           END
WHERE country = 'JP';

重量に応じて送料を加算(NULL対策にCOALESCEを使用)

UPDATE orders
SET shipping_fee = COALESCE(shipping_fee, 0)
                   + CASE WHEN weight_kg > 10 THEN 800 ELSE 0 END
WHERE status = 'READY_TO_SHIP';

別テーブル参照による更新のサンプル

他テーブルの値に基づいて更新する代表的なパターンです。RDBMSごとに構文が異なるため、主要DBの書き分けをまとめています。

PostgreSQL(FROM句でJOINして更新)

UPDATE products p
SET price    = pb.price,
    currency = pb.currency
FROM price_book pb
WHERE pb.product_id = p.product_id
  AND pb.region = 'JP';

SQL Server(UPDATE … FROM でJOIN)

UPDATE p
SET p.Price    = pb.Price,
    p.Currency = pb.Currency
FROM dbo.Products AS p
JOIN dbo.PriceBook AS pb
  ON pb.ProductID = p.ProductID
WHERE pb.Region = 'JP';

MySQL / MariaDB(UPDATE … JOIN で更新)

UPDATE products p
JOIN price_book pb
  ON pb.product_id = p.product_id
SET p.price    = pb.price,
    p.currency = pb.currency
WHERE pb.region = 'JP';

Oracle Database(MERGEによる更新)

MERGE INTO products p
USING price_book pb
   ON (pb.product_id = p.product_id AND pb.region = 'JP')
WHEN MATCHED THEN
  UPDATE SET p.price = pb.price,
             p.currency = pb.currency;

SQLite(相関サブクエリで代入+EXISTSで安全に制限)

UPDATE products
SET price = (
  SELECT pb.price
  FROM price_book pb
  WHERE pb.product_id = products.product_id
    AND pb.region = 'JP'
)
WHERE EXISTS (
  SELECT 1
  FROM price_book pb
  WHERE pb.product_id = products.product_id
    AND pb.region = 'JP'
);

いずれのパターンでも、結合キー(例: product_id)にはインデックスを用意しておくと大規模データでの sql update の速度・ロック時間短縮に有効です。

SnowflakeにおけるUPDATE

sql+update+snowflake

SnowflakeのUPDATEは、クラウドネイティブなストレージ構造と最適化エンジンに合わせて設計されたsql updateの実装です。ジョイン可能なFROM句、CTE(WITH句)、RETURNINGなどを活用しつつ、マイクロパーティションの書き換えという物理特性を意識した運用が鍵になります。

構文

-- CTEは任意
WITH cte_name AS (
  SELECT ...
)
UPDATE [ <db>.<schema>. ]<table_name> [ AS <alias> ]
SET
  <col_name> = <expr> [ , <col_name> = <expr> ... ]
[ FROM <table_or_cte> [ AS <alias> ] [ , ... ] ]
[ WHERE <predicate> ]
[ RETURNING <expr_or_column> [ AS <alias> ] [ , ... ] ] ;

ポイント:

  • ターゲット表に別名を付けた場合、SET/WHEREでは別名を参照します。
  • FROM句にテーブルやCTEを指定して結合更新が可能です。
  • WHEREを省略すると全行が更新対象になります。
  • RETURNINGで更新後の列や式を結果セットとして返せます。

必須パラメータ

SnowflakeでUPDATEを実行するために最低限必要な要素は次のとおりです。

  • ターゲットテーブル
    • 完全修飾名(database.schema.table)または現在のコンテキストに依存した名前。
    • 必要権限: USAGE(database/schema)、UPDATE(table)。
  • SET句
    • 1つ以上の列に対する代入(col = expr)。
    • 式にはリテラル、関数、同一行の他列、スカラーサブクエリが利用可能。
    • ターゲットテーブルに別名を付けた場合は、その別名経由で列を参照します(例: t.col)。

オプションのパラメータ

要件に応じて次の句を組み合わせると、柔軟なsql updateが実現できます。

  • WHERE句
    • 更新対象行の絞り込み。省略時は全行更新。
  • FROM句
    • 他テーブルやCTEと結合して値を参照・反映(JOIN条件はWHEREまたはON相当の述語で表現)。
  • WITH(CTE)
    • 更新に用いる一時的な結果集合を定義して可読性・再利用性を向上。
  • RETURNING句
    • 更新後の値や計算結果を行集合として返します(監査、動作確認、アプリ連携に便利)。

使用上の注意

SnowflakeのUPDATEは論理的には行更新でも、物理的にはマイクロパーティションの再書き込みが発生します。性能とガバナンスの観点で次に留意してください。

  • パフォーマンス/コスト
    • 大規模な全件更新はコストが嵩みやすい。必要な行をWHEREで厳密に絞るか、場合によってはCTAS+スワップやMERGEの検討。
    • クラスタリングキーを設定している場合、更新により再クラスタリングが発生する可能性。
  • トランザクションと原子性
    • BEGIN/COMMITで明示的にトランザクション管理が可能。デフォルトはクライアントにより自動コミット。
    • UPDATEはACIDを満たし、失敗時は自動的にロールバックされます。
  • 権限
    • ターゲットへのUPDATE、参照先(FROM句)のSELECT、関連するDB/スキーマ/ウェアハウスのUSAGEが必要。
  • 関数の評価タイミング
    • CURRENT_TIMESTAMPなどは文内で一貫した値、RANDOM()などの非決定的関数は行ごとに異なる値になりえます。
  • セミ構造化データ更新
    • VARIANT/OBJECT/ARRAYの特定パスを直接代入するのではなく、OBJECT_INSERT/OBJECT_DELETEなどでオブジェクトを再構築するのが基本。
  • 安全策
    • 本番実行前に同一条件のSELECTで対象件数を確認。
    • 必要に応じてトランザクション内でテストし、問題があればROLLBACK。
    • WHEREの書き忘れによる全件更新に注意。

代表的なSnowflakeのUPDATEパターンを示します。

-- 1) 基本的な条件付き更新
UPDATE sales.orders
SET status = 'SHIPPED',
    shipped_at = CURRENT_TIMESTAMP()
WHERE order_id = 12345;

-- 2) 結合(FROM句)を用いた更新
UPDATE sales.customers AS c
SET region_name = r.region_name
FROM ref.regions AS r
WHERE c.region_id = r.region_id
  AND c.region_name IS NULL;

-- 3) CTEで集計結果を反映
WITH latest AS (
  SELECT customer_id, MAX(order_date) AS last_order_at
  FROM sales.orders
  GROUP BY customer_id
)
UPDATE sales.customers AS c
SET last_order_at = l.last_order_at
FROM latest AS l
WHERE c.customer_id = l.customer_id;

-- 4) RETURNINGで更新結果を取得
UPDATE sales.products
SET price = price * 1.05
WHERE category = 'Gadget'
RETURNING product_id, price AS new_price;

-- 5) VARIANT列(セミ構造化)のフィールドを追加/上書き
--    payloadにsource='batch'を付与(既存キーは上書き)
UPDATE raw.events
SET payload = OBJECT_INSERT(payload, 'source', 'batch', TRUE)
WHERE event_type = 'ingest';

これらを組み合わせることで、Snowflakeに最適化された高可読・高信頼なsql updateを実装できます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です