SQLのUPDATE文は、テーブルに格納されている既存のデータを変更するための命令です。INSERT(追加)・SELECT(取得)・DELETE(削除)と並ぶ、SQL四大操作(CRUD)の一つであり、データベース管理において最も頻繁に使われる操作です。
「特定の顧客の住所を変更したい」「商品の価格を一括で10%値上げしたい」「別テーブルの値を参照して更新したい」など、実務では様々なUPDATE処理が求められます。
この記事では、UPDATE文の基本構文から、サブクエリやJOINを使った高度な更新、一括更新時の注意点、RDBMS別の違いまで、実務で必要な知識を体系的に解説します。
この記事で学べること
- UPDATE文の基本構文とSET句・WHERE句の使い方
- 複数カラムを同時に更新する方法
- WHERE句の条件パターン(比較・LIKE・IN・BETWEEN・IS NULL)
- サブクエリを使った更新(別テーブル参照・集計結果で更新)
- JOINを使った更新(MySQL / PostgreSQL / SQL Server)
- CASE式で条件分岐しながら更新する方法
- NULL値の更新と注意点
- 一括更新のリスクと安全な実行手順
- UPDATE文のよくあるエラーと対処法
- RDBMS別のUPDATE構文の違い
サンプルデータの準備
この記事では、以下の2つのテーブルを使って解説します。
CREATE TABLE + INSERT文(クリックで展開)
-- 社員テーブル
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(50),
department VARCHAR(30),
salary INT,
email VARCHAR(100),
hire_date DATE,
status VARCHAR(10) DEFAULT 'active'
);
INSERT INTO employees VALUES
(1, '田中太郎', '営業部', 350000, 'tanaka@example.com', '2020-04-01', 'active'),
(2, '佐藤花子', '開発部', 420000, 'sato@example.com', '2019-07-15', 'active'),
(3, '鈴木一郎', '営業部', 300000, 'suzuki@example.com', '2021-01-10', 'active'),
(4, '高橋美咲', '人事部', 380000, 'takahashi@example.com', '2018-10-01', 'active'),
(5, '伊藤健太', '開発部', 450000, 'ito@example.com', '2017-04-01', 'active'),
(6, '渡辺直美', '総務部', 320000, 'watanabe@example.com', '2022-04-01', 'active'),
(7, '山本翔太', '営業部', 280000, NULL, '2023-04-01', 'active'),
(8, '中村悠太', '開発部', 400000, 'nakamura@example.com', '2020-10-01', 'inactive');
-- 部署マスタテーブル
CREATE TABLE departments (
dept_name VARCHAR(30) PRIMARY KEY,
base_salary INT,
bonus_rate DECIMAL(3,2)
);
INSERT INTO departments VALUES
('営業部', 300000, 1.20),
('開発部', 400000, 1.15),
('人事部', 350000, 1.10),
('総務部', 320000, 1.10);
employees テーブル(社員テーブル)
| id |
name |
department |
salary |
email |
hire_date |
status |
| 1 |
田中太郎 |
営業部 |
350000 |
tanaka@example.com |
2020-04-01 |
active |
| 2 |
佐藤花子 |
開発部 |
420000 |
sato@example.com |
2019-07-15 |
active |
| 3 |
鈴木一郎 |
営業部 |
300000 |
suzuki@example.com |
2021-01-10 |
active |
| 4 |
高橋美咲 |
人事部 |
380000 |
takahashi@example.com |
2018-10-01 |
active |
| 5 |
伊藤健太 |
開発部 |
450000 |
ito@example.com |
2017-04-01 |
active |
| 6 |
渡辺直美 |
総務部 |
320000 |
watanabe@example.com |
2022-04-01 |
active |
| 7 |
山本翔太 |
営業部 |
280000 |
NULL |
2023-04-01 |
active |
| 8 |
中村悠太 |
開発部 |
400000 |
nakamura@example.com |
2020-10-01 |
inactive |
departments テーブル(部署マスタ)
| dept_name |
base_salary |
bonus_rate |
| 営業部 |
300000 |
1.20 |
| 開発部 |
400000 |
1.15 |
| 人事部 |
350000 |
1.10 |
| 総務部 |
320000 |
1.10 |
UPDATE文の基本構文
UPDATE文の基本的な構文は以下のとおりです。
UPDATE文の基本構文
UPDATE テーブル名
SET カラム名1 = 値1,
カラム名2 = 値2,
...
WHERE 条件式;
| キーワード |
役割 |
| UPDATE |
更新対象のテーブルを指定 |
| SET |
変更するカラムと新しい値を指定(カンマ区切りで複数指定可) |
| WHERE |
更新対象の行を絞り込む条件(省略すると全行が対象) |
注意:WHERE句を省略すると、テーブル内の全レコードが更新されます。意図しない一括更新を防ぐため、UPDATE文にはWHERE句を付けることを習慣にしましょう。
1つのカラムを更新する
最もシンプルなUPDATE文です。WHERE句で対象行を特定し、SET句で値を変更します。
特定の社員の名前を変更する
SQL
UPDATE employees
SET name = '田中次郎'
WHERE id = 1;
id が 1 の社員(田中太郎)の名前を「田中次郎」に変更します。WHERE句で id = 1 と指定しているため、1行だけが更新されます。
実行結果
Query OK, 1 row affected
数値を更新する
SQL
UPDATE employees
SET salary = 400000
WHERE id = 3;
数値カラムの場合、値をクォーテーションで囲む必要はありません。id が 3 の鈴木一郎の給料を 300,000 から 400,000 に変更します。
ポイント:文字列はシングルクォーテーションで囲みますが、数値はクォーテーション不要です。日付型は RDBMS によりますが、一般的にはシングルクォーテーションで囲みます。
複数のカラムを同時に更新する
SET句でカンマ区切りにすることで、1つのUPDATE文で複数カラムを同時に更新できます。
部署と給料を同時に変更
UPDATE employees
SET department = '開発部',
salary = 380000,
email = 'suzuki.dev@example.com'
WHERE id = 3;
鈴木一郎の所属部署を「営業部」から「開発部」に異動させ、同時に給料とメールアドレスも更新しています。このように関連する変更は1つのUPDATE文にまとめるのが効率的です。
注意:SET句の最後のカラムの後にカンマを付けるとエラーになります。SET a = 1, b = 2,のように末尾にカンマが残らないよう注意しましょう。
計算式を使った更新
SET句では、固定値だけでなく計算式を使って値を更新できます。現在の値を基に加減算したり、関数を使ったりすることが可能です。
現在の値に加算・減算する
給料を10%アップ
UPDATE employees
SET salary = salary * 1.1
WHERE id = 1;
salary = salary * 1.1 で現在の給料に 1.1 を掛けて 10% アップしています。SET句の右辺では、更新前の値を参照できるのがポイントです。
文字列を結合して更新する
MySQL / PostgreSQL
-- MySQL
UPDATE employees
SET name = CONCAT(name, '(退職)')
WHERE status = 'inactive';
-- PostgreSQL
UPDATE employees
SET name = name || '(退職)'
WHERE status = 'inactive';
status が「inactive」の社員の名前の末尾に「(退職)」を追加しています。MySQLでは CONCAT() 関数、PostgreSQLでは || 演算子で文字列を結合します。
日付を更新する
SQL
-- 固定の日付を設定
UPDATE employees
SET hire_date = '2024-04-01'
WHERE id = 7;
-- 現在日時を設定(MySQL)
UPDATE employees
SET hire_date = CURDATE()
WHERE id = 7;
日付型のカラムには 'YYYY-MM-DD' 形式の文字列か、CURDATE()(MySQL)や CURRENT_DATE(標準SQL)などの関数を使います。
| パターン |
SET句の書き方 |
説明 |
| 加算 |
salary = salary + 10000 |
10,000円を加算 |
| 減算 |
salary = salary - 5000 |
5,000円を減算 |
| 乗算(割合) |
salary = salary * 1.1 |
10%アップ |
| 除算 |
salary = salary / 2 |
半額にする |
| 文字列結合 |
name = CONCAT(name, '様') |
末尾に文字列追加 |
| 現在日時 |
updated_at = NOW() |
現在のタイムスタンプ |
| NULLをセット |
email = NULL |
値をNULLにクリア |
WHERE句の条件パターン
UPDATE文でどの行を更新するかを決めるのがWHERE句です。SELECT文と同じ条件式が使えます。ここでは実務でよく使うパターンを紹介します。
比較演算子(=, !=, <, >, <=, >=)
比較演算子の例
-- 給料が300,000未満の社員を310,000に更新
UPDATE employees
SET salary = 310000
WHERE salary < 300000;
LIKE(部分一致検索)
LIKE句の例
-- メールアドレスのドメインを変更
UPDATE employees
SET email = REPLACE(email, '@example.com', '@newdomain.com')
WHERE email LIKE '%@example.com';
% は0文字以上の任意の文字列にマッチするワイルドカードです。_ は1文字にマッチします。
IN(複数の値にマッチ)
IN句の例
-- 営業部と総務部の社員を一律5%昇給
UPDATE employees
SET salary = salary * 1.05
WHERE department IN ('営業部', '総務部');
IN句を使うと、複数の値のいずれかにマッチする行を対象にできます。OR を複数書くよりも簡潔です。
BETWEEN(範囲指定)
BETWEEN句の例
-- 入社日が2020年の社員のステータスを更新
UPDATE employees
SET status = 'veteran'
WHERE hire_date BETWEEN '2020-01-01' AND '2020-12-31';
BETWEEN は両端を含む範囲指定です。hire_date >= '2020-01-01' AND hire_date <= '2020-12-31' と同じ意味になります。
IS NULL / IS NOT NULL
NULLの判定
-- メールアドレスが未登録の社員にデフォルト値を設定
UPDATE employees
SET email = 'unknown@example.com'
WHERE email IS NULL;
注意:NULLの比較には = NULL ではなく、必ず IS NULL を使います。WHERE email = NULL は常に偽となり、1行も更新されません。これはSQLの三値論理によるもので、NULL との比較は常に UNKNOWN(不明)となるためです。
AND / OR(複合条件)
複合条件の例
-- 開発部で給料が400,000以上の社員をリーダーに昇進
UPDATE employees
SET status = 'leader'
WHERE department = '開発部'
AND salary >= 400000
AND status = 'active';
AND で条件をすべて満たす行、OR でいずれかの条件を満たす行が対象になります。AND と OR を混在させる場合は、括弧 () で優先順位を明示しましょう。
| 演算子 |
使い方 |
説明 |
= |
WHERE id = 1 |
等しい |
!= / <> |
WHERE status != 'active' |
等しくない |
<, >, <=, >= |
WHERE salary >= 300000 |
比較 |
LIKE |
WHERE name LIKE '田中%' |
パターンマッチ |
IN |
WHERE id IN (1, 2, 3) |
リスト内の値 |
BETWEEN |
WHERE salary BETWEEN 300000 AND 400000 |
範囲(両端含む) |
IS NULL |
WHERE email IS NULL |
NULLである |
IS NOT NULL |
WHERE email IS NOT NULL |
NULLでない |
CASE式で条件に応じた値に更新する
SET句の中でCASE式を使うと、条件に応じて異なる値を設定できます。複数のUPDATE文を1つにまとめられるため、効率的です。
部署ごとに異なる昇給率を適用する
CASE式で条件分岐
UPDATE employees
SET salary = CASE
WHEN department = '営業部' THEN salary * 1.10
WHEN department = '開発部' THEN salary * 1.15
WHEN department = '人事部' THEN salary * 1.05
ELSE salary -- 条件に合わない場合は変更なし
END
WHERE status = 'active';
営業部は10%、開発部は15%、人事部は5%とそれぞれ異なる昇給率を1つのUPDATE文で適用しています。ELSE salary を書くことで、条件に合わない部署(総務部など)の給料は変更されません。
ポイント:CASE式で ELSE を省略すると、条件に合わない行はNULLに更新されてしまいます。意図しないデータ消失を防ぐため、ELSE句は必ず書くことをおすすめします。
複数カラムをCASE式で同時に更新する
複数カラムのCASE式
UPDATE employees
SET
salary = CASE
WHEN id = 1 THEN 360000
WHEN id = 2 THEN 430000
WHEN id = 3 THEN 310000
ELSE salary
END,
department = CASE
WHEN id = 3 THEN '開発部'
ELSE department
END
WHERE id IN (1, 2, 3);
id ごとに給料を個別設定し、さらに id=3 の部署も同時に変更しています。このパターンは、バッチ処理などで行ごとに異なる値を一括更新したい場合に便利です。
サブクエリを使ったUPDATE
SET句やWHERE句にサブクエリ(副問合せ)を書くことで、別のテーブルや集計結果を参照して更新できます。
SET句でサブクエリを使う
別テーブルの値を参照して、更新先のカラムに代入するパターンです。
部署マスタの基本給で更新
UPDATE employees e
SET salary = (
SELECT d.base_salary
FROM departments d
WHERE d.dept_name = e.department
)
WHERE status = 'active';
departments テーブルから部署名が一致する行の base_salary を取得し、employees の salary にセットしています。サブクエリが1行1列の値を返す必要がある点に注意してください。
注意:SET句のサブクエリが複数行を返すとエラーになります。サブクエリが必ず1行だけ返すよう、結合条件を正しく指定しましょう。マッチしない場合はNULLがセットされます。
WHERE句でサブクエリを使う
WHERE句のサブクエリは、更新対象の行を絞り込むために使います。
平均以下の給料を底上げ
UPDATE employees
SET salary = salary * 1.05
WHERE salary < (
SELECT AVG(salary)
FROM employees
);
社員全体の平均給料を算出し、それ未満の社員を5%昇給させています。
注意(MySQL):MySQLでは、UPDATE対象のテーブル自身をサブクエリで直接参照するとエラーになります。その場合は、サブクエリをさらにラップして派生テーブルにします。
MySQL向け:派生テーブルでラップ
UPDATE employees
SET salary = salary * 1.05
WHERE salary < (
SELECT avg_sal FROM (
SELECT AVG(salary) AS avg_sal FROM employees
) tmp
);
EXISTS を使った条件指定
EXISTS句の例
-- 部署マスタに存在する部署の社員だけ更新
UPDATE employees e
SET salary = salary * 1.03
WHERE EXISTS (
SELECT 1
FROM departments d
WHERE d.dept_name = e.department
);
EXISTS は「サブクエリが1行以上返すかどうか」を判定します。IN と似ていますが、大量データの場合はEXISTSの方がパフォーマンスが良いことが多いです。
JOINを使ったUPDATE(別テーブルと結合して更新)
サブクエリよりも直感的で高速な方法として、JOINを使ったUPDATEがあります。ただし、構文はRDBMSごとに異なるため注意が必要です。
MySQL の場合
MySQLでは UPDATE ... JOIN ... SET ... の構文を使います。
MySQL – JOINを使ったUPDATE
UPDATE employees e
INNER JOIN departments d
ON e.department = d.dept_name
SET e.salary = d.base_salary * d.bonus_rate;
employees テーブルと departments テーブルを department で結合し、基本給 x ボーナス率 で各社員の給料を更新しています。MySQLの JOIN UPDATE は SET句がJOINの後に来るのが特徴です。
PostgreSQL の場合
PostgreSQLでは UPDATE ... SET ... FROM ... WHERE ... の構文を使います。
PostgreSQL – FROM句を使ったUPDATE
UPDATE employees e
SET salary = d.base_salary * d.bonus_rate
FROM departments d
WHERE e.department = d.dept_name;
PostgreSQLでは FROM 句で結合先テーブルを指定し、WHERE 句で結合条件を書きます。MySQLとは構文が大きく異なる点に注意してください。
SQL Server の場合
SQL Server – FROM句 + JOINを使ったUPDATE
UPDATE e
SET e.salary = d.base_salary * d.bonus_rate
FROM employees e
INNER JOIN departments d
ON e.department = d.dept_name;
SQL Serverでは UPDATE エイリアス SET ... FROM テーブル JOIN ... の構文です。UPDATE句にはテーブルのエイリアスを書きます。
Oracle の場合(MERGE文)
OracleにはJOIN UPDATEの直接的な構文がないため、MERGE文を使うのが一般的です。
Oracle – MERGE文
MERGE INTO employees e
USING departments d
ON (e.department = d.dept_name)
WHEN MATCHED THEN
UPDATE SET e.salary = d.base_salary * d.bonus_rate;
| RDBMS |
JOIN UPDATEの構文 |
| MySQL |
UPDATE t1 JOIN t2 ON ... SET t1.col = t2.col |
| PostgreSQL |
UPDATE t1 SET col = t2.col FROM t2 WHERE ... |
| SQL Server |
UPDATE t1 SET t1.col = t2.col FROM t1 JOIN t2 ON ... |
| Oracle |
MERGE INTO t1 USING t2 ON (...) WHEN MATCHED THEN UPDATE SET ... |
全レコードを一括で更新する(WHERE句省略)
WHERE句を省略すると、テーブル内のすべての行が更新されます。意図的に全行更新したい場合に使いますが、細心の注意が必要です。
全行更新の例
-- 全社員のステータスをactiveにリセット
UPDATE employees
SET status = 'active';
-- 全社員の給料を一律5%カット
UPDATE employees
SET salary = salary * 0.95;
注意:WHERE句を付け忘れて全行更新してしまう事故は、実務で最も多いミスの一つです。UPDATE文を実行する前に、必ず同じWHERE条件でSELECTを実行して対象行を確認する習慣をつけましょう。
NULL値の更新と注意点
NULLは「値が存在しない」ことを表す特殊な値です。UPDATE文でのNULLの扱いには、いくつかの注意点があります。
カラムをNULLに更新する
NULLをセット
-- メールアドレスをクリア
UPDATE employees
SET email = NULL
WHERE id = 8;
NULL はクォーテーションで囲みません。'NULL'(文字列の “NULL”)とは別物なので注意してください。
NULLを含む計算の注意
NULLを含む計算
-- NULLに加算するとNULLになる!
UPDATE employees
SET salary = salary + 10000
WHERE id = 9; -- salaryがNULLなら結果もNULL
-- COALESCE()でNULLを安全に処理
UPDATE employees
SET salary = COALESCE(salary, 0) + 10000
WHERE id = 9; -- NULLなら0として計算
NULL + 10000 の結果は NULL です(10000ではありません)。COALESCE() 関数を使って NULL を 0 に変換してから計算すると安全です。
| 式 |
結果 |
理由 |
NULL + 10000 |
NULL |
NULLとの演算結果はNULL |
NULL * 1.1 |
NULL |
NULLとの演算結果はNULL |
CONCAT(NULL, 'abc') |
NULL(MySQL)/ ‘abc’(PostgreSQL) |
RDBMSにより異なる |
NULL = NULL |
UNKNOWN(偽扱い) |
三値論理 |
NULL IS NULL |
TRUE |
IS NULLで正しく判定 |
UPDATE前にSELECTで確認する(安全な実行手順)
実務では、UPDATE文を実行する前に同じWHERE条件でSELECTを実行し、更新対象の行を確認するのが鉄則です。
手順1:SELECTで対象行を確認
Step 1: SELECTで確認
-- まずSELECTで対象を確認
SELECT id, name, salary, department
FROM employees
WHERE department = '営業部'
AND status = 'active';
実行結果
+----+----------+--------+------------+
| id | name | salary | department |
+----+----------+--------+------------+
| 1 | 田中太郎 | 350000 | 営業部 |
| 3 | 鈴木一郎 | 300000 | 営業部 |
| 7 | 山本翔太 | 280000 | 営業部 |
+----+----------+--------+------------+
3 rows in set
手順2:想定通りならUPDATEを実行
Step 2: UPDATEを実行
UPDATE employees
SET salary = salary * 1.10
WHERE department = '営業部'
AND status = 'active';
手順3:更新結果をSELECTで確認
Step 3: 結果を確認
SELECT id, name, salary, department
FROM employees
WHERE department = '営業部';
実行結果
+----+----------+--------+------------+
| id | name | salary | department |
+----+----------+--------+------------+
| 1 | 田中太郎 | 385000 | 営業部 |
| 3 | 鈴木一郎 | 330000 | 営業部 |
| 7 | 山本翔太 | 308000 | 営業部 |
+----+----------+--------+------------+
3 rows in set
ポイント:本番環境では、UPDATE前にトランザクションを開始しておくと、問題があった場合にROLLBACKで元に戻せます。
トランザクションを使った安全なUPDATE
トランザクションを使うと、UPDATE文の実行結果を確認してから確定(COMMIT)するか、取り消し(ROLLBACK)するかを選べます。本番環境でのUPDATE作業には必須のテクニックです。
トランザクションでUPDATEを安全に実行
-- トランザクション開始
BEGIN; -- MySQL: START TRANSACTION; でも可
-- UPDATEを実行
UPDATE employees
SET salary = salary * 1.10
WHERE department = '営業部';
-- 結果を確認
SELECT * FROM employees WHERE department = '営業部';
-- 問題なければ確定
COMMIT;
-- 問題があれば取り消し
-- ROLLBACK;
| コマンド |
動作 |
BEGIN / START TRANSACTION |
トランザクションを開始する |
COMMIT |
変更を確定する(元に戻せなくなる) |
ROLLBACK |
変更をすべて取り消して元に戻す |
注意:MySQLのデフォルト設定では autocommit = ON になっており、各SQL文が自動的にCOMMITされます。トランザクションを使うには、明示的に BEGIN または START TRANSACTION を実行してから UPDATE を行ってください。
実務でよく使うUPDATEパターン集
ここでは、実際の業務で頻繁に使われるUPDATEのパターンを紹介します。
パターン1:フラグの一括切り替え
論理削除の実装例
-- 退職した社員を論理削除(物理削除せずフラグで管理)
UPDATE employees
SET status = 'inactive'
WHERE id = 8;
-- 全フラグをリセット
UPDATE employees
SET status = 'active'
WHERE status = 'inactive';
物理的にDELETEするのではなく、statusカラムで「active / inactive」を切り替える論理削除は、実務で非常によく使われるパターンです。
パターン2:タイムスタンプの自動更新
更新日時の記録
UPDATE employees
SET
salary = 450000,
updated_at = NOW()
WHERE id = 5;
データを変更する際に updated_at = NOW() で更新日時も同時に記録しておくと、「いつ変更されたか」をトレースできます。
パターン3:REPLACE関数で文字列を置換
文字列の一括置換
-- メールアドレスのドメインを一括変更
UPDATE employees
SET email = REPLACE(email, '@example.com', '@newcompany.com')
WHERE email LIKE '%@example.com';
REPLACE関数は文字列内の特定の部分を別の文字列に置換します。会社の合併やドメイン変更時に便利です。
パターン4:LIMITで更新件数を制限(MySQL限定)
MySQL – LIMIT付きUPDATE
-- 最初の3件だけ更新
UPDATE employees
SET salary = salary * 1.05
WHERE department = '営業部'
ORDER BY hire_date ASC
LIMIT 3;
MySQLでは ORDER BY と LIMIT を組み合わせて、更新する行数を制限できます。大量データを少しずつ更新したい場合に使います。PostgreSQLやOracleには、この構文はありません。
パターン5:連番やランクの更新(ユーザー変数を活用)
MySQL – ユーザー変数で連番を付与
SET @rank := 0;
UPDATE employees
SET id = (@rank := @rank + 1)
ORDER BY hire_date ASC;
ユーザー変数 @rank を使って、入社日順にIDを振り直しています。これはMySQL固有の機能であり、他のRDBMSでは異なるアプローチが必要です。
UPDATE文のよくあるエラーと対処法
UPDATE文で遭遇しやすいエラーとその対処法をまとめます。
エラー1:WHERE句の付け忘れ(全行更新)
| 項目 |
内容 |
| 状況 |
WHERE句を書き忘れて全行が更新されてしまった |
| 原因 |
UPDATE employees SET status = 'inactive'; のようにWHERE句がない |
| 対処法 |
トランザクション内で実行していればROLLBACK。そうでなければバックアップから復旧 |
予防策:MySQLの場合、--safe-updates オプション(SET sql_safe_updates = 1;)を有効にすると、WHERE句なしのUPDATE/DELETEが拒否されます。
セーフモードの有効化
-- セーフモードを有効化
SET sql_safe_updates = 1;
-- これはエラーになる(WHERE句なし)
UPDATE employees SET status = 'active';
-- ERROR 1175: You are using safe update mode...
エラー2:データ型の不一致
| 項目 |
内容 |
| エラー |
Incorrect integer value / Data truncated |
| 原因 |
INT型のカラムに文字列を入れようとした、VARCHAR型の桁数を超えた等 |
| 対処法 |
カラムの型と桁数を確認し、適切な値を指定する |
エラー例と修正
-- NG: INT型のカラムに文字列
UPDATE employees SET salary = '高い' WHERE id = 1;
-- OK: INT型には数値を指定
UPDATE employees SET salary = 500000 WHERE id = 1;
エラー3:外部キー制約違反
| 項目 |
内容 |
| エラー |
Cannot add or update a child row: a foreign key constraint fails |
| 原因 |
外部キーで参照されている値を、参照先に存在しない値に変更しようとした |
| 対処法 |
参照先テーブルに対象の値が存在するか確認してから更新する |
エラー4:サブクエリが複数行を返す
| 項目 |
内容 |
| エラー |
Subquery returns more than 1 row |
| 原因 |
SET句のサブクエリが複数行を返している |
| 対処法 |
結合条件を見直すか、LIMIT 1やMAX()/MIN()で1行に絞る |
修正例
-- NG: サブクエリが複数行を返す可能性
UPDATE employees
SET salary = (SELECT base_salary FROM departments);
-- OK: 結合条件で1行に絞る
UPDATE employees e
SET salary = (
SELECT d.base_salary
FROM departments d
WHERE d.dept_name = e.department
);
エラー5:ロック競合(デッドロック)
| 項目 |
内容 |
| エラー |
Deadlock found when trying to get lock / Lock wait timeout exceeded |
| 原因 |
複数のトランザクションが互いにロックを待っている状態 |
| 対処法 |
更新順序を統一する、トランザクションを短くする、バッチサイズを小さくする |
RDBMS別のUPDATE構文の違い
UPDATE文の基本構文はどのRDBMSでも共通ですが、拡張機能には違いがあります。主要なRDBMSの違いをまとめます。
| 機能 |
MySQL |
PostgreSQL |
SQL Server |
Oracle |
| 基本 UPDATE SET WHERE |
対応 |
対応 |
対応 |
対応 |
| JOIN UPDATE |
UPDATE JOIN SET |
UPDATE SET FROM WHERE |
UPDATE FROM JOIN |
MERGE INTO |
| ORDER BY + LIMIT |
対応 |
非対応 |
TOP N |
ROWNUM |
| RETURNING句 |
非対応 |
対応 |
OUTPUT句 |
RETURNING INTO |
| 自テーブル参照サブクエリ |
派生テーブル必須 |
そのまま可 |
そのまま可 |
そのまま可 |
| 文字列結合 |
CONCAT() |
|| |
+ |
|| |
PostgreSQLのRETURNING句
PostgreSQLでは、UPDATE文に RETURNING を付けると、更新後の値をSELECT結果のように返せます。
PostgreSQL – RETURNING句
UPDATE employees
SET salary = salary * 1.10
WHERE department = '営業部'
RETURNING id, name, salary;
実行結果
id | name | salary
----+----------+--------
1 | 田中太郎 | 385000
3 | 鈴木一郎 | 330000
7 | 山本翔太 | 308000
(3 rows)
RETURNING句を使うと、UPDATE完了と同時に更新後の値を確認できるため、手順が1ステップ減ります。
SQL ServerのOUTPUT句
SQL Server – OUTPUT句
UPDATE employees
SET salary = salary * 1.10
OUTPUT
inserted.id,
inserted.name,
deleted.salary AS old_salary,
inserted.salary AS new_salary
WHERE department = '営業部';
SQL ServerのOUTPUT句では、inserted(更新後)と deleted(更新前)の両方を参照できるため、変更前後の値を同時に確認できます。
UPDATEのパフォーマンス最適化
大量のデータを更新する場合、パフォーマンスに注意が必要です。
インデックスの活用
WHERE句で使用するカラムにインデックスが張られていると、更新対象の行を高速に見つけることができます。
インデックスの確認と作成
-- WHERE句のカラムにインデックスがあるか確認
SHOW INDEX FROM employees;
-- インデックスを作成
CREATE INDEX idx_department ON employees(department);
-- 実行計画で確認
EXPLAIN UPDATE employees
SET salary = salary * 1.1
WHERE department = '営業部';
大量更新のバッチ処理
100万件以上のデータを一度に更新すると、ロック時間が長くなり他のクエリに影響します。バッチに分割して更新するのがベストプラクティスです。
MySQL – バッチ処理で大量更新
-- 1,000件ずつバッチ更新
UPDATE employees
SET salary = salary * 1.05
WHERE status = 'active'
AND id BETWEEN 1 AND 1000;
UPDATE employees
SET salary = salary * 1.05
WHERE status = 'active'
AND id BETWEEN 1001 AND 2000;
-- ...以下繰り返し
| 方法 |
メリット |
デメリット |
| 一括UPDATE |
シンプル、トランザクション1回 |
ロック時間が長い、メモリ大量使用 |
| バッチ更新 |
ロック時間が短い、他のクエリに影響しにくい |
処理が複雑、途中でエラーが起きた場合の考慮が必要 |
| pt-online-schema-change等 |
無停止で大量更新可能 |
ツールの導入が必要 |
UPDATE文のベストプラクティス(まとめ)
最後に、UPDATE文を安全かつ効率的に使うためのベストプラクティスをまとめます。
UPDATE文 10のルール
- WHERE句を必ず付ける — 全行更新を防ぐ
- 事前にSELECTで確認する — 同じWHERE条件で対象行を確認
- トランザクションを使う — 問題があればROLLBACKで元に戻せる
- バックアップを取る — 大量更新前には必ずバックアップ
- CASE式のELSEを忘れない — 意図しないNULL更新を防ぐ
- NULLの比較には IS NULL を使う — = NULL は常に偽になる
- NULL計算にはCOALESCEを使う — NULL + 数値 = NULL を防ぐ
- 大量更新はバッチに分割する — ロック時間を短くする
- 本番はセーフモードを検討する — sql_safe_updates = 1
- RDBMS別の構文差に注意する — 特にJOIN UPDATEは構文が異なる
UPDATE文 チートシート
この記事で紹介したUPDATE文のパターンを一覧にまとめます。コピー&ペーストしてお使いください。
UPDATE文チートシート
-- ■ 基本の更新
UPDATE テーブル SET カラム = 値 WHERE 条件;
-- ■ 複数カラム更新
UPDATE テーブル SET col1 = val1, col2 = val2 WHERE 条件;
-- ■ 計算式で更新
UPDATE テーブル SET salary = salary * 1.1 WHERE 条件;
-- ■ CASE式で条件分岐
UPDATE テーブル SET col = CASE WHEN 条件1 THEN 値1 ELSE col END WHERE ...;
-- ■ サブクエリで更新(SET句)
UPDATE t1 SET col = (SELECT val FROM t2 WHERE t2.id = t1.id);
-- ■ JOIN更新(MySQL)
UPDATE t1 JOIN t2 ON t1.id = t2.id SET t1.col = t2.col;
-- ■ JOIN更新(PostgreSQL)
UPDATE t1 SET col = t2.col FROM t2 WHERE t1.id = t2.id;
-- ■ NULLをセット
UPDATE テーブル SET col = NULL WHERE 条件;
-- ■ NULLを安全に更新
UPDATE テーブル SET col = COALESCE(col, 0) + 100 WHERE 条件;
-- ■ トランザクション
BEGIN;
UPDATE テーブル SET col = val WHERE 条件;
-- 確認後 → COMMIT; または ROLLBACK;
よくある質問(FAQ)
Q. WHERE句を書き忘れた場合、全レコードが更新されてしまいます。防ぐ方法はありますか?
A. まずSELECTで更新対象を確認してからUPDATEを実行する習慣が重要です。また、DBによってはSET SQL_SAFE_UPDATES=1(MySQL)でインデックスなしのUPDATEを防げます。本番環境ではトランザクション(BEGIN/ROLLBACK)の中でUPDATEを実行し、件数を確認してからCOMMITするのが安全です。
Q. 他のテーブルの値を参照してUPDATEする(JOIN UPDATE)にはどうすればよいですか?
A. MySQLではUPDATE table1 t1 JOIN table2 t2 ON t1.id=t2.id SET t1.col=t2.col WHERE ...、SQL ServerとPostgreSQLではUPDATE t1 SET col=t2.col FROM table2 t2 WHERE t1.id=t2.id、OracleではUPDATE (SELECT ... FROM t1 JOIN t2 ...) SET col=...またはサブクエリを使います。
Q. 大量のレコード(100万件以上)を一括UPDATEする場合の注意点は?
A. 大量UPDATEはロック・ログ膨張・パフォーマンス低下の原因になります。バッチサイズ(例:1万件)に分割してコミットすること、UPDATEするカラムにインデックスが不要でないか確認すること、メンテナンス時間帯に実行することを推奨します。
まとめ
この記事では、SQLのUPDATE文について基本構文から応用テクニックまで解説しました。
| セクション |
ポイント |
| 基本構文 |
UPDATE テーブル SET カラム = 値 WHERE 条件 |
| 複数カラム |
SET句でカンマ区切り、末尾カンマに注意 |
| 計算式 |
SET salary = salary * 1.1 で現在値ベースの更新可能 |
| WHERE句 |
=, LIKE, IN, BETWEEN, IS NULL, AND/ORの組み合わせ |
| CASE式 |
条件ごとに異なる値を設定、ELSEで元の値を保持 |
| サブクエリ |
SET句は1行1列を返す必要あり、MySQLは派生テーブルが必要 |
| JOIN更新 |
RDBMS別に構文が異なるので注意 |
| NULL |
IS NULLで判定、COALESCE()で安全に計算 |
| 安全な運用 |
SELECT確認 → BEGIN → UPDATE → COMMIT/ROLLBACK |
| パフォーマンス |
インデックス活用、大量更新はバッチ分割 |
UPDATE文は非常に強力な反面、WHERE句の付け忘れやデータ型の不一致で意図しない結果になることがあります。「事前のSELECT確認」「トランザクションの活用」「バックアップの取得」の3つを習慣にすることで、安全にデータ更新を行えるようになります。
ぜひ、この記事のサンプルを実際に手を動かして試してみてください。実践を通じてUPDATE文の挙動を体感することが、スキル向上への近道です。