[PR] 本記事にはプロモーションが含まれています。

【データベース編 第6回】書き換える・消す ― UPDATE と DELETE を、トランザクションで安全に試す

書き換えたカードを、確定する前に戻せるように、下書きのトレイに置いて確かめている様子を表したイラスト MySQL

【2026年10月24日時点の情報です】

第5回までは、表を作り、データを入れ、取り出し、数えるところまで進みました。どれも、データを読むだけの操作です。

第6回は、初めてデータを書き換えたり、消したりします。書き換えは、間違えると元に戻せないことがあります。そこで、取り消しのできる「トランザクション」という仕組みの中で、わざと危ない書き換えも試してみました。

ブログの管理で、連載のタイトルをまとめて変えたときに使った UPDATE も、最後に読み解きます。

場面0:この回の前に知っておくこと

練習用のデータ

第5回と同じ、6行の練習用のデータ(値はすべて架空のもの)を使います。この回で id 4 の値を書き換えるので、終わったあとに最初の状態に戻したいときは、第1回の付録の手順を使ってください。

Bash
mysql -u practice -p sql_practice

以下の SQL は、すべて MariaDB の中(行頭が MariaDB [sql_practice]>)で打ちます。

書き換える・消す命令の形

No.命令形何をするか
1UPDATEUPDATE 表 SET 列 = 値 WHERE 条件条件に合う行の、指定した列を書き換える
2DELETEDELETE FROM 表 WHERE 条件条件に合う行を、まるごと消す

どちらも、WHERE を書き忘れると、すべての行が対象になります。そのため、書き換える前に、同じ条件の SELECT で対象の行を確かめるのが基本です。

トランザクション

START TRANSACTION から始めると、その後の書き換えは「仮の書き換え」になります。ROLLBACK で取り消すか、COMMIT で確定するかを、あとから選べます。

START TRANSACTION で仮の書き換えを始め、UPDATE や DELETE のあと SELECT で確かめて、ROLLBACK で取り消すか COMMIT で確定するかを選ぶ流れを示した図
  • START TRANSACTION:
    ここから先の書き換えを、仮の状態にする。組み込みでいえば、フラッシュに書く前に、RAM 上のシャドウで編集を始める
  • ROLLBACK:
    仮の書き換えを捨てて、元に戻す。シャドウを捨てる
  • COMMIT:
    仮の書き換えを確定する。シャドウをフラッシュに書き込む

START TRANSACTION を使わずに打った UPDATE や DELETE は、その場で確定します。取り消しはできません。

場面1:1行だけ書き換える(UPDATE)

id 4 の血糖(104)を、105 に書き換えました。まず SELECT で対象の行を確かめてから、同じ条件のまま UPDATE に置き換えます。

SQL
SELECT * FROM measurements WHERE id = 4;
UPDATE measurements SET value_text = '105', value_num = 105 WHERE id = 4;
SELECT * FROM measurements WHERE id = 4;
  • UPDATE measurements:
    measurements 表の行を書き換える
  • SET value_text = ‘105’, value_num = 105:
    どの列を、どの値にするか。カンマで区切って、複数の列を一度に書き換えられる
  • WHERE id = 4:
    どの行を書き換えるか。直前の SELECT と同じ条件にする

UPDATE の結果には、次のような行が出ます。

  • Rows matched:
    条件に合った行の数
  • Changed:
    実際に値が変わった行の数。同じ値で書き換えると、matched は 1 でも changed は 0 になる

id 4 が 105 になったことは、次の場面2 で表示した表で確かめられます。この書き換えはトランザクションの外で行ったので、その場で確定しています。

場面2:取り消せる状態で書き換える(トランザクション)

次に、血糖の項目名を「血糖(空腹時)」に書き換えてから、取り消してみました。

SQL
START TRANSACTION;
UPDATE measurements SET item_name = REPLACE(item_name, '血糖', '血糖(空腹時)') WHERE item_name = '血糖';
SELECT * FROM measurements;
  • REPLACE(item_name, ‘血糖’, ‘血糖(空腹時)’):
    item_name の文字列の中の「血糖」を「血糖(空腹時)」に置き換えた文字列を作る関数

続けて表示した表です。

結果の例:

Plaintext
+----+-------------+-----------------------+------------+-----------+-------+
| id | measured_on | item_name             | value_text | value_num | unit  |
+----+-------------+-----------------------+------------+-----------+-------+
|  1 | 2026-09-01  | 血糖(空腹時)        | 98         |    98.000 | mg/dL |
|  2 | 2026-09-01  | CRP                   | 0.05       |     0.050 | mg/dL |
|  3 | 2026-09-01  | 尿蛋白                | (-)        |      NULL | NULL  |
|  4 | 2026-10-01  | 血糖(空腹時)        | 105        |   105.000 | mg/dL |
|  5 | 2026-10-01  | CRP                   | 0.03未満   |     0.030 | mg/dL |
|  6 | 2026-10-01  | 尿蛋白                | (+-)       |      NULL | NULL  |
+----+-------------+-----------------------+------------+-----------+-------+
6 rows in set (0.000 sec)

血糖の2行が「血糖(空腹時)」になり、id 4 は場面1 で書き換えた 105 になっています。ここで、取り消しました。

SQL
ROLLBACK;
SELECT * FROM measurements;

結果の例:

Plaintext
Query OK, 0 rows affected (0.000 sec)

+----+-------------+-----------+------------+-----------+-------+
| id | measured_on | item_name | value_text | value_num | unit  |
+----+-------------+-----------+------------+-----------+-------+
|  1 | 2026-09-01  | 血糖      | 98         |    98.000 | mg/dL |
|  2 | 2026-09-01  | CRP       | 0.05       |     0.050 | mg/dL |
|  3 | 2026-09-01  | 尿蛋白    | (-)        |      NULL | NULL  |
|  4 | 2026-10-01  | 血糖      | 105        |   105.000 | mg/dL |
|  5 | 2026-10-01  | CRP       | 0.03未満   |     0.030 | mg/dL |
|  6 | 2026-10-01  | 尿蛋白    | (+-)       |      NULL | NULL  |
+----+-------------+-----------+------------+-----------+-------+
6 rows in set (0.000 sec)

項目名は「血糖」に戻りました。一方、id 4 の 105 は戻りません。場面1 の書き換えは、START TRANSACTION より前に確定していたからです。ROLLBACK で戻るのは、START TRANSACTION から後の書き換えだけです。

場面3:WHERE を忘れると、どうなるか

UPDATE で WHERE を書き忘れたらどうなるかを、トランザクションの中でわざと試しました。

SQL
START TRANSACTION;
UPDATE measurements SET unit = 'X';
SELECT * FROM measurements;

結果の例:

Plaintext
Query OK, 0 rows affected (0.000 sec)

Query OK, 6 rows affected (0.000 sec)
Rows matched: 6  Changed: 6  Warnings: 0

+----+-------------+-----------+------------+-----------+------+
| id | measured_on | item_name | value_text | value_num | unit |
+----+-------------+-----------+------------+-----------+------+
|  1 | 2026-09-01  | 血糖      | 98         |    98.000 | X    |
|  2 | 2026-09-01  | CRP       | 0.05       |     0.050 | X    |
|  3 | 2026-09-01  | 尿蛋白    | (-)        |      NULL | X    |
|  4 | 2026-10-01  | 血糖      | 105        |   105.000 | X    |
|  5 | 2026-10-01  | CRP       | 0.03未満   |     0.030 | X    |
|  6 | 2026-10-01  | 尿蛋白    | (+-)       |      NULL | X    |
+----+-------------+-----------+------------+-----------+------+
6 rows in set (0.000 sec)
  • Rows matched: 6 Changed: 6:
    WHERE がないので、条件に合う行は「すべての行」になる。6行すべての単位が X に書き換わった

C でいえば、配列の特定の要素だけに書くつもりが、添字を書き忘れて全要素に書いてしまうのと同じです。トランザクションの中なので、取り消しました。

SQL
ROLLBACK;
SELECT * FROM measurements;

結果の例:

Plaintext
Query OK, 0 rows affected (0.000 sec)

+----+-------------+-----------+------------+-----------+-------+
| id | measured_on | item_name | value_text | value_num | unit  |
+----+-------------+-----------+------------+-----------+-------+
|  1 | 2026-09-01  | 血糖      | 98         |    98.000 | mg/dL |
|  2 | 2026-09-01  | CRP       | 0.05       |     0.050 | mg/dL |
|  3 | 2026-09-01  | 尿蛋白    | (-)        |      NULL | NULL  |
|  4 | 2026-10-01  | 血糖      | 105        |   105.000 | mg/dL |
|  5 | 2026-10-01  | CRP       | 0.03未満   |     0.030 | mg/dL |
|  6 | 2026-10-01  | 尿蛋白    | (+-)       |      NULL | NULL  |
+----+-------------+-----------+------------+-----------+-------+
6 rows in set (0.000 sec)

単位が元に戻りました。これが、書き換える前に必ず同じ条件の SELECT で確かめる理由です。不安なときは、トランザクションの中で試してから COMMIT します。

場面4:行を消す(DELETE)

消す練習のために、「テスト」という行を1つ足してから、その行を消しました。

SQL
INSERT INTO measurements (measured_on, item_name, value_text, value_num, unit)
VALUES ('2026-10-01', 'テスト', '1', 1, NULL);
SELECT * FROM measurements WHERE item_name = 'テスト';

結果の例:

Plaintext
Query OK, 1 row affected (0.004 sec)

+----+-------------+-----------+------------+-----------+------+
| id | measured_on | item_name | value_text | value_num | unit |
+----+-------------+-----------+------------+-----------+------+
|  7 | 2026-10-01  | テスト    | 1          |     1.000 | NULL |
+----+-------------+-----------+------------+-----------+------+
1 row in set (0.000 sec)

足した行は id 7 になりました。この行を、同じ条件で消します。

SQL
DELETE FROM measurements WHERE item_name = 'テスト';
SELECT * FROM measurements;

結果の例:

Plaintext
Query OK, 1 row affected (0.005 sec)

+----+-------------+-----------+------------+-----------+-------+
| id | measured_on | item_name | value_text | value_num | unit  |
+----+-------------+-----------+------------+-----------+-------+
|  1 | 2026-09-01  | 血糖      | 98         |    98.000 | mg/dL |
|  2 | 2026-09-01  | CRP       | 0.05       |     0.050 | mg/dL |
|  3 | 2026-09-01  | 尿蛋白    | (-)        |      NULL | NULL  |
|  4 | 2026-10-01  | 血糖      | 105        |   105.000 | mg/dL |
|  5 | 2026-10-01  | CRP       | 0.03未満   |     0.030 | mg/dL |
|  6 | 2026-10-01  | 尿蛋白    | (+-)       |      NULL | NULL  |
+----+-------------+-----------+------------+-----------+-------+
6 rows in set (0.000 sec)
  • DELETE FROM measurements:
    measurements 表から行を消す。SELECT と違って、列の名前や * は書かない(行をまるごと消すため)
  • WHERE item_name = ‘テスト’:
    消す行の条件。忘れると、全行が消える
  • Query OK, 1 row affected:
    1行消えたという意味

消した id 7 の番号は、次に行を入れても再利用されず、次の行は 8 から振られます。AUTO_INCREMENT は、使った番号を戻さないカウンタだからです。

場面5:タイトルをまとめて変えたコマンドを読む

ブログの連載のタイトルを、「【第◯回】」から「【入門編 第◯回】」にまとめて変えたときは、次の2つを順に流していました。

まず、書き換えずに、変えたあとの形を表示して確かめました。

SQL
SELECT ID, post_status, post_title AS before_title,
       REPLACE(post_title, '【第', '【入門編 第') AS after_title
FROM wp_posts
WHERE post_type = 'post' AND post_name LIKE 'embedded-to-web-0%'
  AND post_status IN ('publish', 'future')
ORDER BY post_name;

確かめたあと、同じ条件で書き換えました。

SQL
UPDATE wp_posts
SET post_title = REPLACE(post_title, '【第', '【入門編 第')
WHERE post_type = 'post' AND post_name LIKE 'embedded-to-web-0%'
  AND post_status IN ('publish', 'future')
  AND post_title LIKE '【第%';
  • REPLACE(post_title, ‘【第’, ‘【入門編 第’) AS after_title:
    書き換えたあとのタイトルを、表示だけする。SELECT なので、データは変わらない
  • SET post_title = REPLACE(…):
    確かめたのと同じ置き換えで、本当に書き換える
  • post_name LIKE ‘embedded-to-web-0%’:
    連載の入門編(スラッグが embedded-to-web-0 で始まる記事)だけ。番外編(embedded-to-web-extra-…)は入らない
  • AND post_title LIKE ‘【第%’:
    まだ変えていないタイトルだけ。うっかり2回流しても、「【入門編 入門編 第」のように二重にはならない

場面1 の「SELECT で確かめてから、同じ条件で UPDATE」と、まったく同じ順番です。このときはトランザクションを使わず、代わりに、書き換える前の記事をファイルに控えておきました。控えの取り方と戻し方は、次回のテーマです。

まとめ

第6回では、初めてデータを書き換え、消しました。覚えたことは、次のとおりです。

No.覚えたことC・組み込みでいうと
1UPDATE 表 SET 列 = 値 WHERE 条件 で書き換える配列の要素のメンバへの代入
2DELETE FROM 表 WHERE 条件 で行を消す。列は書かない要素をまるごと取り除く
3WHERE を忘れると、すべての行が対象になる添字を書き忘れて全要素に書く
4書き換える前に、同じ条件の SELECT で確かめる書き込む前にアドレスを確かめる
5START TRANSACTION の中なら、ROLLBACK で取り消せる。前に確定したものは戻らないRAM 上のシャドウで編集して、捨てるか書き込むか選ぶ
6AUTO_INCREMENT は、消した番号を再利用しない使った番号を戻さないカウンタ

次回は、mysqldump でデータをファイルに控え、そこから元に戻す方法を試します。控え方によっては、戻したときにほかの行まで消えてしまう落とし穴もありました。

この記事は、シリーズ「組み込みエンジニアの Web 開発入門」の1本です。ほかの回は、シリーズの目次からどうぞ。

ほかの開発・プログラミングの記事は、開発・プログラミングメモの目次から、テーマ別に探せます。

このブログは、にほんブログ村と人気ブログランキングに参加しています。記事が役に立ったら、下のランキングのバナーのどれかを押して応援してもらえると励みになります。

ブログランキング・にほんブログ村へにほんブログ村

サーバー構築・運用ランキング プログラムランキング

MySQL
watasoftdevをフォローする

コメント