データを変える
INSERT、UPDATE、DELETE の基本。WHERE 条件を書き忘れるリスク、実行前に影響行数を見積もる習慣、制約によるデータ保護を体感します。
- 行を足す・変える・消すにはどう書くか
- WHERE を忘れると何が起きるか
- 間違った変更から、DB は何を守ってくれるか
この章はデータを変える。壊したら npm run seed:basics で元に戻る。
更新クエリを実行: 「影響を受けた行数」を確認する
SELECT とは異なり結果の表は返らず、実行結果パネルに「影響した行数: 1」と表示されます。UPDATE テーブル名 SET 列名 = 新しい値, … WHERE 条件 という構文で、条件に合致した行のデータを書き換えます。 この「影響した行数(Affected rows)」は、更新処理が意図通りに行われたかを確認する極めて重要な指標となります。
quantity = quantity - 1 のように、現在の列の値を参照して計算結果をセットすることも可能です。「在庫数を 1 減らす」といった処理は実務でもこのように記述します。
最大の落とし穴: WHERE 句のない UPDATE
もし WHERE 句を書き忘れたまま UPDATE を実行してしまうと何が起きるでしょうか。構文エラーで止まってくれるでしょうか。 何行が影響を受けるか、実行して確かめてみてください。 この教材のデータベースは 50 行の検証用環境ですので、安全に失敗を体験できます。
影響した行数は 50、全行が「delivered(配達済み)」に書き換わってしまいました。エラーにはなりません。 SQL の仕様上、WHERE 句を指定しない UPDATE や DELETE は「全行を対象とする」とみなされます。 データベースは「本当に全行を更新して良いですか?」と確認ダイアログを出してくれるわけではありません。 実務において「全注文のステータスが変わってしまった」「全ユーザーが誤って退会処理されてしまった」という大事故は、まさにこの一行で引き起こされます。
このような致命的な人為ミスを防ぐための 3 つの習慣を紹介します。
- UPDATE や DELETE を実行する前に、全く同じ WHERE 句を使って
SELECT COUNT(*)を実行し、対象行数を必ず事前に確認する。 - 本番環境での手作業メンテナンスでは必ずトランザクション(第 7 章)を開始し、影響行数を確認した上でコミットする。
- 手作業による直接更新を極力減らし、データ修正はスクリプト化してコードレビューを通す運用にする。
データを破損させてしまったので初期状態に戻しましょう。ターミナルで npm run seed:basics を実行し、画面上の「更新」ボタンを押してから次へ進んでください。
安全な更新手順(事前確認)の実践
事前にカウントした件数と、UPDATE で影響を受けた行数が完全に一致していれば、想定通りの行だけが更新されたと確信できます。WHERE 句による対象行の抽出ロジックは SELECT でも UPDATE でも完全に共通です。
INSERT: データの追加
LAST_INSERT_ID() は、直前の INSERT 文で自動採番された AUTO_INCREMENT の主キー値を取得する関数です。Web アプリケーションでは「今登録した注文の ID」を取得し、続けて注文明細を登録する際によく利用されます。
INSERT INTO … SELECT … 構文を使うと、SELECT クエリで取得した結果をそのまま別のテーブルに一括登録できます(ここでは在庫 30 個未満の商品を「セール対象」カテゴリ(ID: 6)に一括登録しています)。 ただし、すでに対象カテゴリに登録済みの商品が含まれていた場合、主キー制約(重複禁止)違反でエラーになります。 このようなケースをスマートに解決するのが次の UPSERT です。
UPSERT: 既存データがあれば更新し、なければ新規挿入する
categories.name 列にはユニーク(一意性)制約が設定されているため、既存の「文具」と同じ名前は登録できずエラーになります。 この制約を活用することで、「存在しなければ新規登録し、すでに存在していれば更新またはスキップする」という処理を 1 つの SQL 文で安全に表現できます。
INSERT IGNORE は、重複キー違反が発生した行だけをスキップして残りの行を挿入します。 既存データを上書き更新したい場合は ON DUPLICATE KEY UPDATE、重複を単に無視したい場合は INSERT IGNORE を使い分けます。 「先に SELECT で存在チェックをして、なければ INSERT する」というように処理を 2 回に分けると、同時にリクエストが来た際に両方が「存在しない」と判断して同時に INSERT を試み、衝突エラーを引き起こします。 ユニーク制約と UPSERT 構文を組み合わせることで、データベース側で 1 つの不可分な処理として安全に完結させることができます。
DELETE: データの削除と参照制約
商品 ID 1 の商品は、すでに注文明細(order_items)から参照されています。 もしこの商品を直接削除してしまうと、注文明細が「存在しない架空の商品」を指すことになり、整合性が破綻してしまいます。 第 4 章で学んだ外部キー制約が働き、データベースが DELETE を拒否してデータを保護しています。
実務では、物理的にレコードを削除する代わりに deleted_at 列に削除日時を記録して「削除されたものとみなす」論理削除という手法も広く用いられます。 履歴が残り、誤操作時に復元が容易である一方、すべてのクエリに WHERE deleted_at IS NULL を記述する必要があり、書き忘れると削除済みデータが表示されてしまうという別のリスクを抱えることになります。
データ変更文は「影響した行数」を返します。WHERE 句を省略すると全行に適用されてしまうため、事前の SELECT 確認やトランザクションの活用が不可欠です。
確認
WHERE を忘れた UPDATE が全行を変えてしまうのはなぜか。
変更の文を打つ前に、同じ WHERE で数えておくのはなぜか。
先に存在を確かめてから INSERT する 2 文より、ユニーク制約と UPSERT のほうが安全なのはなぜか。
次の章へ
ここまでは 1 つのクエリで完結するデータ操作を学んできました。 次の章では、クエリの実行結果を別のクエリの中で活用する「サブクエリ」の仕組みを学びます。 「平均単価より高い商品」や「特定条件の会員による注文」など、動的な条件判定を行う強力な記法を身につけましょう。