「条件によって異なる値で更新したい」
「複数のUPDATE文を実行するのは非効率的…」
「IF文のような条件分岐をUPDATEで使いたい」
「一括で複数パターンの更新を行いたい」
そんな要望を叶えるのがUPDATE文でのCASE式です!
CASE式を使えば、1つのUPDATE文で複雑な条件分岐を実現でき、パフォーマンスも大幅に向上します。
この記事では、UPDATE CASEの基本から実務で使える高度なテクニックまで、豊富な実例とともに解説していきます!
UPDATE CASEの基本構文

基本的な書き方
UPDATE テーブル名
SET カラム名 = CASE
WHEN 条件1 THEN 値1
WHEN 条件2 THEN 値2
WHEN 条件3 THEN 値3
ELSE デフォルト値
END
WHERE 更新対象の条件;
シンプルな例で理解する
-- 商品テーブルの価格を条件によって更新
UPDATE products
SET price = CASE
WHEN category = '食品' THEN price * 1.08 -- 8%値上げ
WHEN category = '家電' THEN price * 0.95 -- 5%値下げ
WHEN category = '衣類' THEN price * 1.10 -- 10%値上げ
ELSE price -- その他は変更なし
END;
-- 成績によって評価を設定
UPDATE students
SET grade = CASE
WHEN score >= 90 THEN 'A'
WHEN score >= 80 THEN 'B'
WHEN score >= 70 THEN 'C'
WHEN score >= 60 THEN 'D'
ELSE 'F'
END
WHERE exam_id = 101;
実践!よく使うUPDATE CASEパターン
パターン1:ステータス更新
-- 注文テーブル
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
order_date DATE,
shipped_date DATE,
delivered_date DATE,
status VARCHAR(20),
total_amount DECIMAL(10,2)
);
-- 日付に基づいてステータスを自動更新
UPDATE orders
SET status = CASE
WHEN delivered_date IS NOT NULL THEN '配送完了'
WHEN shipped_date IS NOT NULL AND delivered_date IS NULL THEN '配送中'
WHEN order_date < CURRENT_DATE - INTERVAL '7 days' AND shipped_date IS NULL THEN '処理遅延'
WHEN shipped_date IS NULL THEN '処理中'
ELSE '不明'
END
WHERE status IS NULL OR status = '';
-- 金額によって配送優先度を設定
UPDATE orders
SET priority = CASE
WHEN total_amount >= 10000 THEN 'HIGH'
WHEN total_amount >= 5000 THEN 'MEDIUM'
ELSE 'LOW'
END
WHERE shipped_date IS NULL;
パターン2:段階的な値の調整
-- 在庫テーブル
CREATE TABLE inventory (
product_id INTEGER PRIMARY KEY,
stock_quantity INTEGER,
reorder_level INTEGER,
max_stock INTEGER,
stock_status VARCHAR(20)
);
-- 在庫状況に応じた発注数量の設定
UPDATE inventory
SET reorder_quantity = CASE
WHEN stock_quantity = 0 THEN max_stock -- 在庫切れは最大まで発注
WHEN stock_quantity < reorder_level * 0.5 THEN max_stock - stock_quantity -- 危険水準
WHEN stock_quantity < reorder_level THEN (max_stock - stock_quantity) * 0.7 -- 通常発注
ELSE 0 -- 発注不要
END,
stock_status = CASE
WHEN stock_quantity = 0 THEN '在庫切れ'
WHEN stock_quantity < reorder_level * 0.5 THEN '至急発注'
WHEN stock_quantity < reorder_level THEN '要発注'
WHEN stock_quantity > max_stock * 0.9 THEN '過剰在庫'
ELSE '正常'
END;
パターン3:複数カラムの同時更新
-- 従業員テーブル
CREATE TABLE employees (
employee_id SERIAL PRIMARY KEY,
salary DECIMAL(10,2),
bonus DECIMAL(10,2),
performance_score INTEGER,
department VARCHAR(50),
years_of_service INTEGER
);
-- 評価に基づいて給与とボーナスを同時更新
UPDATE employees
SET
salary = CASE
WHEN performance_score >= 90 THEN salary * 1.10
WHEN performance_score >= 70 THEN salary * 1.05
WHEN performance_score >= 50 THEN salary * 1.02
ELSE salary
END,
bonus = CASE
WHEN performance_score >= 90 THEN salary * 2.0
WHEN performance_score >= 70 THEN salary * 1.5
WHEN performance_score >= 50 THEN salary * 1.0
ELSE salary * 0.5
END,
salary_grade = CASE
WHEN salary * CASE
WHEN performance_score >= 90 THEN 1.10
WHEN performance_score >= 70 THEN 1.05
WHEN performance_score >= 50 THEN 1.02
ELSE 1
END > 100000 THEN 'A'
WHEN salary * CASE
WHEN performance_score >= 90 THEN 1.10
WHEN performance_score >= 70 THEN 1.05
WHEN performance_score >= 50 THEN 1.02
ELSE 1
END > 70000 THEN 'B'
ELSE 'C'
END
WHERE department IN ('営業部', '開発部');
高度なCASE式の活用法
他のテーブルを参照する条件
-- カテゴリマスタを参照して価格を更新
UPDATE products p
SET price = CASE
WHEN c.category_type = 'premium' THEN p.price * 1.20
WHEN c.category_type = 'standard' THEN p.price * 1.00
WHEN c.category_type = 'discount' THEN p.price * 0.80
ELSE p.price
END
FROM categories c
WHERE p.category_id = c.category_id
AND p.last_updated < CURRENT_DATE - INTERVAL '30 days';
-- 売上実績に基づいた在庫調整
UPDATE inventory i
SET reorder_level = CASE
WHEN s.total_sold > 1000 THEN 200
WHEN s.total_sold > 500 THEN 100
WHEN s.total_sold > 100 THEN 50
ELSE 20
END
FROM (
SELECT product_id, SUM(quantity) as total_sold
FROM sales
WHERE sale_date >= CURRENT_DATE - INTERVAL '3 months'
GROUP BY product_id
) s
WHERE i.product_id = s.product_id;
サブクエリを使った複雑な条件
-- 平均値との比較による評価更新
UPDATE students s
SET relative_grade = CASE
WHEN s.score > (SELECT AVG(score) * 1.2 FROM students WHERE class_id = s.class_id) THEN '優秀'
WHEN s.score > (SELECT AVG(score) FROM students WHERE class_id = s.class_id) THEN '平均以上'
WHEN s.score > (SELECT AVG(score) * 0.8 FROM students WHERE class_id = s.class_id) THEN '平均'
ELSE '要努力'
END
WHERE exam_id = 2023;
-- ランキングに基づくボーナス計算
UPDATE sales_staff ss
SET bonus_rate = CASE
WHEN (
SELECT COUNT(*)
FROM sales_staff ss2
WHERE ss2.sales_amount > ss.sales_amount
AND ss2.department = ss.department
) < 3 THEN 0.20 -- 部門TOP3
WHEN (
SELECT COUNT(*)
FROM sales_staff ss2
WHERE ss2.sales_amount > ss.sales_amount
AND ss2.department = ss.department
) < 10 THEN 0.10 -- 部門TOP10
ELSE 0.05
END
WHERE period = '2024Q1';
日付や時間による条件分岐
-- 時間帯による料金設定
UPDATE parking_fees
SET fee = CASE
-- 平日
WHEN EXTRACT(DOW FROM entry_time) BETWEEN 1 AND 5 THEN
CASE
WHEN EXTRACT(HOUR FROM entry_time) BETWEEN 7 AND 9 THEN 500 -- 朝ラッシュ
WHEN EXTRACT(HOUR FROM entry_time) BETWEEN 17 AND 19 THEN 500 -- 夕ラッシュ
ELSE 300 -- 通常時間
END
-- 週末
ELSE
CASE
WHEN EXTRACT(HOUR FROM entry_time) BETWEEN 10 AND 20 THEN 400
ELSE 200
END
END
WHERE fee IS NULL;
-- 季節による価格調整
UPDATE products
SET seasonal_price = CASE
WHEN category = 'エアコン' THEN
CASE
WHEN EXTRACT(MONTH FROM CURRENT_DATE) IN (6,7,8) THEN price * 1.2 -- 夏
WHEN EXTRACT(MONTH FROM CURRENT_DATE) IN (12,1,2) THEN price * 1.1 -- 冬
ELSE price
END
WHEN category = 'ストーブ' THEN
CASE
WHEN EXTRACT(MONTH FROM CURRENT_DATE) IN (11,12,1,2) THEN price * 1.15
ELSE price * 0.9
END
ELSE price
END;
CASE式の種類と使い分け

Simple CASE vs Searched CASE
-- Simple CASE(単純な等価比較)
UPDATE orders
SET shipping_cost = CASE region
WHEN '北海道' THEN 1000
WHEN '沖縄' THEN 1200
WHEN '関東' THEN 500
WHEN '関西' THEN 600
ELSE 700
END;
-- Searched CASE(複雑な条件)
UPDATE orders
SET shipping_cost = CASE
WHEN region IN ('北海道', '沖縄') AND weight > 10 THEN 2000
WHEN region IN ('北海道', '沖縄') THEN 1000
WHEN distance > 500 THEN 800
WHEN weight > 20 THEN 900
ELSE 500
END;
複数CASE式の組み合わせ
-- ネストしたCASE式
UPDATE products
SET final_price =
base_price *
CASE -- カテゴリによる係数
WHEN category = 'luxury' THEN 1.5
WHEN category = 'standard' THEN 1.0
ELSE 0.8
END *
CASE -- 在庫による係数
WHEN stock < 10 THEN 1.2
WHEN stock > 100 THEN 0.9
ELSE 1.0
END *
CASE -- 季節による係数
WHEN is_seasonal AND EXTRACT(MONTH FROM CURRENT_DATE) IN (12,1,2) THEN 1.1
ELSE 1.0
END;
パフォーマンス最適化のテクニック
インデックスを活用した高速化
-- インデックスを作成
CREATE INDEX idx_products_category ON products(category);
CREATE INDEX idx_orders_status ON orders(status) WHERE status IS NULL;
-- インデックスが効く書き方
UPDATE products
SET price = CASE
WHEN category = '家電' THEN price * 0.9
ELSE price
END
WHERE category = '家電'; -- WHERE句で絞り込み
-- 非効率な書き方(全行スキャン)
UPDATE products
SET price = CASE
WHEN category = '家電' THEN price * 0.9
ELSE price -- 変更なしでも全行更新
END;
バッチ処理での効率化
-- 一時テーブルを使った大量更新
CREATE TEMP TABLE update_batch AS
SELECT
product_id,
CASE
WHEN total_sales > 1000 THEN 'ベストセラー'
WHEN total_sales > 100 THEN '人気商品'
ELSE '通常商品'
END as new_status
FROM (
SELECT product_id, SUM(quantity) as total_sales
FROM sales
GROUP BY product_id
) s;
-- インデックスを作成してから更新
CREATE INDEX ON update_batch(product_id);
-- 効率的な更新
UPDATE products p
SET status = u.new_status
FROM update_batch u
WHERE p.product_id = u.product_id;
よくあるエラーと対処法
データ型の不一致
-- エラーになる例
UPDATE users
SET status = CASE
WHEN age >= 20 THEN 1 -- 数値
ELSE 'minor' -- 文字列(型が違う!)
END;
-- 正しい例(型を統一)
UPDATE users
SET status = CASE
WHEN age >= 20 THEN 'adult'
ELSE 'minor'
END;
-- または型変換を使用
UPDATE users
SET status_code = CASE
WHEN age >= 20 THEN 1
ELSE 0
END::INTEGER;
NULL値の扱い
-- NULLの比較は要注意
UPDATE accounts
SET category = CASE
WHEN balance = NULL THEN 'unknown' -- 動作しない!
WHEN balance IS NULL THEN 'unknown' -- 正しい
WHEN balance > 10000 THEN 'premium'
ELSE 'standard'
END;
-- COALESCEと組み合わせる
UPDATE accounts
SET fee = CASE
WHEN COALESCE(balance, 0) >= 100000 THEN 0
WHEN COALESCE(balance, 0) >= 10000 THEN 100
ELSE 300
END;
更新対象の絞り込み忘れ
-- 危険:全レコードが更新される
UPDATE orders
SET status = CASE
WHEN shipped_date IS NOT NULL THEN '発送済み'
ELSE status -- 元の値のままだが、更新は実行される
END;
-- 安全:必要なレコードのみ更新
UPDATE orders
SET status = '発送済み'
WHERE shipped_date IS NOT NULL
AND status != '発送済み'; -- 既に更新済みは除外
実践的な応用例
在庫と価格の動的調整
-- 在庫連動型の価格調整システム
WITH stock_levels AS (
SELECT
product_id,
stock_quantity,
AVG(stock_quantity) OVER (PARTITION BY category_id) as avg_stock,
daily_sales_avg
FROM inventory
)
UPDATE products p
SET
dynamic_price = p.base_price * CASE
-- 在庫切れ間近
WHEN sl.stock_quantity < sl.daily_sales_avg * 3 THEN 1.15
-- 在庫過多
WHEN sl.stock_quantity > sl.avg_stock * 2 THEN 0.85
-- 通常
ELSE 1.0
END,
price_updated_at = CURRENT_TIMESTAMP,
price_reason = CASE
WHEN sl.stock_quantity < sl.daily_sales_avg * 3 THEN '在庫僅少'
WHEN sl.stock_quantity > sl.avg_stock * 2 THEN '在庫調整セール'
ELSE '通常価格'
END
FROM stock_levels sl
WHERE p.product_id = sl.product_id
AND p.auto_pricing = true;
ポイント計算システム
-- 購買履歴に基づくポイント付与
UPDATE customer_points cp
SET
points = cp.points + CASE
-- VIP会員
WHEN c.membership_level = 'VIP' THEN
CASE
WHEN purchase_amount >= 10000 THEN purchase_amount * 0.05
WHEN purchase_amount >= 5000 THEN purchase_amount * 0.03
ELSE purchase_amount * 0.02
END
-- 一般会員
ELSE
CASE
WHEN purchase_amount >= 10000 THEN purchase_amount * 0.02
ELSE purchase_amount * 0.01
END
END,
tier = CASE
WHEN cp.points + (calculated_points) >= 100000 THEN 'Platinum'
WHEN cp.points + (calculated_points) >= 50000 THEN 'Gold'
WHEN cp.points + (calculated_points) >= 10000 THEN 'Silver'
ELSE 'Bronze'
END
FROM customers c,
purchases p
WHERE cp.customer_id = c.customer_id
AND cp.customer_id = p.customer_id
AND p.purchase_date = CURRENT_DATE;
トランザクション管理のベストプラクティス
-- 安全な更新処理
BEGIN;
-- 更新前の状態を確認
SELECT COUNT(*), status
FROM orders
WHERE order_date < CURRENT_DATE - INTERVAL '30 days'
GROUP BY status;
-- 条件付き更新を実行
UPDATE orders
SET status = CASE
WHEN status = 'pending' AND order_date < CURRENT_DATE - INTERVAL '30 days' THEN 'cancelled'
WHEN status = 'processing' AND order_date < CURRENT_DATE - INTERVAL '7 days' THEN 'delayed'
ELSE status
END
WHERE order_date < CURRENT_DATE - INTERVAL '7 days';
-- 更新結果を確認
SELECT COUNT(*), status
FROM orders
WHERE order_date < CURRENT_DATE - INTERVAL '30 days'
GROUP BY status;
-- 問題なければコミット、問題があればロールバック
COMMIT; -- または ROLLBACK;
まとめ – UPDATE CASEで効率的なデータ更新を実現!
PostgreSQLのUPDATE CASE文について、基本から応用まで詳しく解説してきました。
重要ポイント:
- CASE式で複雑な条件分岐を1つのUPDATE文で実現
- 複数カラムの同時更新も可能
- サブクエリや他テーブルとの結合も活用できる
- パフォーマンスを考慮したWHERE句の使用が重要
- トランザクション管理で安全な更新を
使いどころ:
- ステータスの一括更新
- 条件による価格調整
- 評価やランクの自動計算
- 在庫管理の自動化
- ポイントやボーナスの計算
UPDATE CASE文をマスターすれば、複雑なビジネスロジックもSQLだけで実装でき、アプリケーション側の処理を大幅に簡略化できます。
ぜひ実際のプロジェクトで活用してみてください!
コメント