「複数のカラムを更新するのに、何回もUPDATE文を実行している…」 「サブクエリの結果を複数カラムに反映させたい」 「他のテーブルのデータで一括更新する方法が分からない」
こんな悩みを抱えていませんか?
PostgreSQLなら、1つのUPDATE文で複数カラムを効率的に更新できます。しかも、思っているより簡単なんです!
この記事を読めば、複雑な更新処理もスマートに書けるようになります。一緒に、UPDATE文の達人を目指しましょう!
基本構文:複数カラム更新の3つの書き方

方法1:カンマ区切りで列挙(最も基本的)
UPDATE テーブル名
SET
カラム1 = 値1,
カラム2 = 値2,
カラム3 = 値3
WHERE 条件;
実例:
-- 従業員情報を更新
UPDATE employees
SET
salary = 350000,
position = 'シニアエンジニア',
updated_at = CURRENT_TIMESTAMP
WHERE employee_id = 101;
方法2:括弧を使った記法(PostgreSQL特有)
UPDATE テーブル名
SET (カラム1, カラム2, カラム3) = (値1, 値2, 値3)
WHERE 条件;
実例:
UPDATE employees
SET (salary, position, updated_at) = (350000, 'シニアエンジニア', CURRENT_TIMESTAMP)
WHERE employee_id = 101;
方法3:サブクエリを使った一括更新
UPDATE テーブル名
SET (カラム1, カラム2) = (
SELECT 計算値1, 計算値2
FROM 別テーブル
WHERE 条件
)
WHERE 条件;
どの方法を選ぶべきか?次のセクションで詳しく見ていきましょう!
実践例1:基本的な複数カラム更新
サンプルテーブルの準備
-- 商品テーブルの作成
CREATE TABLE products (
product_id SERIAL PRIMARY KEY,
product_name VARCHAR(100),
price DECIMAL(10,2),
stock_quantity INTEGER,
category VARCHAR(50),
is_active BOOLEAN DEFAULT true,
last_updated TIMESTAMP,
updated_by VARCHAR(50)
);
-- サンプルデータの挿入
INSERT INTO products (product_name, price, stock_quantity, category, last_updated, updated_by) VALUES
('ノートPC', 120000, 50, '電子機器', '2024-01-01', 'admin'),
('ワイヤレスマウス', 3500, 200, '周辺機器', '2024-01-01', 'admin'),
('USBメモリ 32GB', 2000, 150, '記憶媒体', '2024-01-01', 'admin'),
('モニター 27インチ', 45000, 30, '周辺機器', '2024-01-01', 'admin'),
('キーボード', 8000, 100, '周辺機器', '2024-01-01', 'admin');
基本的な更新:価格改定と在庫調整
-- 特定商品の価格と在庫を同時更新
UPDATE products
SET
price = price * 1.1, -- 10%値上げ
stock_quantity = stock_quantity - 10, -- 在庫を10個減らす
last_updated = CURRENT_TIMESTAMP,
updated_by = 'sales_team'
WHERE product_id = 1;
条件付き一括更新
-- カテゴリごとに異なる更新を実行
UPDATE products
SET
price = CASE
WHEN category = '電子機器' THEN price * 1.15
WHEN category = '周辺機器' THEN price * 1.08
ELSE price * 1.05
END,
stock_quantity = CASE
WHEN stock_quantity < 50 THEN stock_quantity + 100 -- 在庫補充
ELSE stock_quantity
END,
last_updated = CURRENT_TIMESTAMP,
updated_by = 'system'
WHERE is_active = true;
実践例2:サブクエリを使った高度な更新
他テーブルのデータで更新
-- 売上統計テーブル
CREATE TABLE sales_stats (
product_id INTEGER,
total_sales INTEGER,
average_rating DECIMAL(3,2),
last_sale_date DATE
);
-- 売上データを基に商品情報を更新
UPDATE products p
SET
stock_quantity = stock_quantity - s.total_sales,
is_active = CASE
WHEN s.average_rating < 3.0 THEN false
ELSE true
END,
last_updated = CURRENT_TIMESTAMP
FROM sales_stats s
WHERE p.product_id = s.product_id
AND s.last_sale_date >= CURRENT_DATE - INTERVAL '30 days';
サブクエリで複数カラムを一括取得
-- 価格調整テーブル
CREATE TABLE price_adjustments (
product_id INTEGER,
new_price DECIMAL(10,2),
discount_percentage INTEGER,
effective_date DATE
);
-- PostgreSQL特有の書き方(ROW型を使用)
UPDATE products
SET (price, last_updated, updated_by) = (
SELECT
new_price * (1 - discount_percentage/100.0),
CURRENT_TIMESTAMP,
'pricing_system'
FROM price_adjustments
WHERE price_adjustments.product_id = products.product_id
AND effective_date = CURRENT_DATE
)
WHERE EXISTS (
SELECT 1 FROM price_adjustments
WHERE price_adjustments.product_id = products.product_id
AND effective_date = CURRENT_DATE
);
実践例3:JOINを使った複雑な更新
USING句を使った更新
-- 部門情報テーブル
CREATE TABLE departments (
dept_id INTEGER PRIMARY KEY,
dept_name VARCHAR(100),
budget DECIMAL(12,2),
manager_id INTEGER
);
-- 従業員テーブル
CREATE TABLE employees (
emp_id SERIAL PRIMARY KEY,
emp_name VARCHAR(100),
dept_id INTEGER,
salary DECIMAL(10,2),
bonus DECIMAL(10,2),
performance_score INTEGER
);
-- 部門予算に基づいてボーナスを計算・更新
UPDATE employees e
SET
bonus = d.budget * 0.01 * e.performance_score / 100,
salary = salary * CASE
WHEN e.performance_score >= 90 THEN 1.10
WHEN e.performance_score >= 70 THEN 1.05
ELSE 1.00
END,
last_modified = CURRENT_TIMESTAMP
FROM departments d
WHERE e.dept_id = d.dept_id
AND d.budget > 1000000;
複数テーブルの情報を統合して更新
-- 顧客の購入履歴を基にステータスを更新
UPDATE customers c
SET
customer_level = CASE
WHEN total_purchase >= 1000000 THEN 'プラチナ'
WHEN total_purchase >= 500000 THEN 'ゴールド'
WHEN total_purchase >= 100000 THEN 'シルバー'
ELSE 'ブロンズ'
END,
discount_rate = CASE
WHEN total_purchase >= 1000000 THEN 15
WHEN total_purchase >= 500000 THEN 10
WHEN total_purchase >= 100000 THEN 5
ELSE 0
END,
last_purchase_date = latest_date,
updated_at = CURRENT_TIMESTAMP
FROM (
SELECT
customer_id,
SUM(amount) as total_purchase,
MAX(purchase_date) as latest_date
FROM orders
WHERE purchase_date >= CURRENT_DATE - INTERVAL '1 year'
GROUP BY customer_id
) AS purchase_summary
WHERE c.customer_id = purchase_summary.customer_id;
RETURNING句で更新結果を確認
更新した値を即座に取得
-- 更新前後の値を比較
UPDATE products
SET
price = price * 1.2,
stock_quantity = stock_quantity + 50,
last_updated = CURRENT_TIMESTAMP
WHERE category = '電子機器'
RETURNING
product_id,
product_name,
price AS new_price,
price / 1.2 AS old_price, -- 計算で旧価格を表示
stock_quantity AS new_stock;
更新件数と詳細を同時に確認
WITH updated_rows AS (
UPDATE employees
SET
salary = salary * 1.05,
bonus = 10000,
review_date = CURRENT_DATE
WHERE performance_score >= 80
RETURNING *
)
SELECT
COUNT(*) AS 更新件数,
AVG(salary) AS 平均給与,
SUM(bonus) AS ボーナス総額
FROM updated_rows;
パフォーマンスを考慮した更新テクニック
インデックスを活用した高速更新
-- 更新対象を絞るためのインデックス作成
CREATE INDEX idx_products_category ON products(category);
CREATE INDEX idx_products_active ON products(is_active) WHERE is_active = true;
-- インデックスを活用した効率的な更新
UPDATE products
SET
price = price * 1.05,
last_updated = CURRENT_TIMESTAMP
WHERE category = '周辺機器'
AND is_active = true;
バッチ更新で大量データを処理
-- 大量データを分割して更新(メモリ使用量を抑える)
DO $$
DECLARE
batch_size INTEGER := 1000;
offset_val INTEGER := 0;
updated_count INTEGER;
BEGIN
LOOP
WITH batch AS (
SELECT product_id
FROM products
WHERE needs_update = true
ORDER BY product_id
LIMIT batch_size
OFFSET offset_val
)
UPDATE products p
SET
price = price * 1.1,
needs_update = false,
updated_at = CURRENT_TIMESTAMP
FROM batch b
WHERE p.product_id = b.product_id;
GET DIAGNOSTICS updated_count = ROW_COUNT;
EXIT WHEN updated_count < batch_size;
offset_val := offset_val + batch_size;
-- 負荷軽減のため少し待機
PERFORM pg_sleep(0.1);
END LOOP;
END $$;
不要なトリガーを一時的に無効化
-- トリガーの無効化(大量更新時)
ALTER TABLE products DISABLE TRIGGER ALL;
-- 大量更新実行
UPDATE products
SET
price = price * 1.05,
category = UPPER(category),
last_updated = CURRENT_TIMESTAMP;
-- トリガーの有効化
ALTER TABLE products ENABLE TRIGGER ALL;
よくある間違いと対処法
間違い1:WHERE句の書き忘れ
-- 危険!全レコードが更新される
UPDATE products
SET price = 0; -- WHERE句なし!
-- 安全対策:トランザクションを使う
BEGIN;
UPDATE products
SET price = price * 1.1
WHERE category = '電子機器';
-- 確認
SELECT * FROM products WHERE category = '電子機器';
-- 問題なければコミット、問題があればロールバック
COMMIT; -- または ROLLBACK;
間違い2:サブクエリが複数行を返す
-- エラーになる例
UPDATE products
SET price = (
SELECT price FROM price_list -- 複数行返される可能性
WHERE category = products.category
);
-- 正しい書き方(集約関数を使う)
UPDATE products
SET price = (
SELECT AVG(price) FROM price_list
WHERE category = products.category
);
-- またはLIMIT 1を使う
UPDATE products
SET price = (
SELECT price FROM price_list
WHERE category = products.category
ORDER BY effective_date DESC
LIMIT 1
);
間違い3:NULL値の扱い
-- NULL値で上書きしてしまう危険
UPDATE products p
SET (price, stock_quantity) = (
SELECT new_price, new_stock
FROM temp_updates t
WHERE t.product_id = p.product_id
);
-- NULL値を防ぐ安全な書き方
UPDATE products p
SET
price = COALESCE(t.new_price, p.price),
stock_quantity = COALESCE(t.new_stock, p.stock_quantity)
FROM temp_updates t
WHERE t.product_id = p.product_id;
実用的なユースケース集
在庫管理システム
-- 注文処理時の在庫更新
UPDATE products p
SET
stock_quantity = p.stock_quantity - o.quantity,
reserved_quantity = p.reserved_quantity + o.quantity,
last_sold = CURRENT_TIMESTAMP,
total_sales = p.total_sales + o.quantity
FROM order_items o
WHERE p.product_id = o.product_id
AND o.order_id = 12345
AND p.stock_quantity >= o.quantity;
ユーザーステータス管理
-- 最終ログイン時刻とアクティブ状態を更新
UPDATE users
SET
last_login = CURRENT_TIMESTAMP,
login_count = login_count + 1,
is_active = true,
session_token = encode(gen_random_bytes(32), 'hex'),
ip_address = '192.168.1.1'
WHERE email = 'user@example.com'
AND password_hash = crypt('password', password_hash)
RETURNING user_id, session_token;
定期バッチ処理
-- 月次の統計情報更新
UPDATE monthly_summary
SET
total_revenue = subq.revenue,
order_count = subq.orders,
average_order_value = subq.revenue / NULLIF(subq.orders, 0),
top_product = subq.best_seller,
calculated_at = CURRENT_TIMESTAMP
FROM (
SELECT
DATE_TRUNC('month', order_date) as month,
SUM(total_amount) as revenue,
COUNT(*) as orders,
MODE() WITHIN GROUP (ORDER BY product_name) as best_seller
FROM orders
WHERE order_date >= DATE_TRUNC('month', CURRENT_DATE)
GROUP BY DATE_TRUNC('month', order_date)
) AS subq
WHERE monthly_summary.month = subq.month;
まとめ:複数カラム更新をマスターしよう!
今日学んだポイントを振り返りましょう:
✅ 3つの基本構文(カンマ区切り、括弧記法、サブクエリ) ✅ FROM句でJOINして他テーブルのデータを利用 ✅ CASE文で条件付き更新を実現 ✅ RETURNING句で更新結果を即座に確認 ✅ トランザクションで安全に更新 ✅ バッチ処理で大量データに対応 ✅ COALESCEでNULL対策
複数カラムの更新は、最初は「複雑そう…」と感じるかもしれません。
でも、基本パターンを理解すれば、効率的で読みやすいSQLが書けるようになります。1つずつUPDATE文を書いていた時間が、嘘のように短縮されるはずです。
今日から、スマートなUPDATE文で、データベース操作を効率化しましょう!
Happy Updating!
次のステップにおすすめ:
- PostgreSQLのMERGE文(UPSERT)の使い方
- CTEを使った複雑な更新処理
- トリガーによる自動更新の実装
コメント