C言語+MySQL入門|商品を変更・削除する

C言語+MySQL入門|商品を変更・削除する
前の記事では、productsに3つの商品を登録し、SELECTで一覧表示や絞り込みを行いました。
今回は、登録した商品の情報を変更するUPDATEと、商品を削除するDELETEを学びます。
どちらも保存済みのデータに直接作用する命令です。特に大切なのが、操作対象を決めるWHERE条件です。まずSELECTで対象を確認し、同じ条件で変更・削除を行う流れを身に付けましょう。
この記事では、次の内容を学びます。
- UPDATEで商品の単価や商品名を変更する。
- SETとWHEREの役割を理解する。
- 実行結果から、対象件数と変更件数を読み取る。
- DELETEで指定した商品を削除する。
- 外部キーによって削除が制限される場合を理解する。
今回操作する商品を確認しよう
MySQLクライアントへ管理作業用のアカウントで接続し、inventory_dbを選択します。
USE inventory_db;実行結果の例
Database changed続けて、現在の商品一覧を表示します。
SELECT product_id, product_name, price, stock
FROM products
ORDER BY product_id;実行結果の例
+------------+----------------+--------+-------+
| product_id | product_name | price | stock |
+------------+----------------+--------+-------+
| 1 | ノート | 180.00 | 0 |
| 2 | ボールペン | 120.00 | 0 |
| 3 | クリアファイル | 90.00 | 0 |
+------------+----------------+--------+-------+
3 rows in set (0.00 sec)この記事では、前の記事までの手順を各1回実行した状態から、次の操作を順に行います。
| 対象 | 操作する内容 |
|---|---|
| 商品ID1のノート | 単価を180.00から200.00へ変更する |
| 商品ID2のボールペン | 商品名を油性ボールペン、単価を150.00へ変更する |
| 商品ID3のクリアファイル | 在庫が0で、履歴がないことを確認して削除する |
実際の商品IDが例と異なる場合は、一覧で確認したIDに置き換えてください。商品名だけで判断せず、商品IDと内容を対応させてから進めましょう。
この記事も、前の記事と同様に、自動コミットが有効で、明示的なトランザクションを開始していない状態を前提にします。この状態では、成功した変更や削除はその都度確定し、後からROLLBACKを実行しても取り消せません。
変更・削除は確認とセットで進めよう
今回の操作では、次の順番を基本にします。
- SELECTで操作対象を確認する。
- 同じWHERE条件でUPDATEまたはDELETEを実行する。
- 実行結果の件数を確認する。
- SELECTで操作後の状態を確認する。
たとえば、商品ID1の単価を変更する場合は、最初のSELECTでも、UPDATEでも、WHERE product_id = 1を使います。
確認用の条件と実際の操作条件が違っていると、確認した商品とは別の商品を操作してしまう可能性があります。
図1:対象の確認から操作後の確認までをつなげる

この図から分かること
変更前の確認、変更処理、変更後の確認で、同じ商品を追いかけます。
操作が成功したというメッセージだけで終わらず、保存された値まで確認することで、意図した変更になっているかを確かめられます。
UPDATEで商品の単価を変更しよう
最初に、ノートの単価を180.00から200.00へ変更します。
変更対象の商品を表示する
SELECT product_id, product_name, price, stock
FROM products
WHERE product_id = 1;実行結果の例
+------------+--------------+--------+-------+
| product_id | product_name | price | stock |
+------------+--------------+--------+-------+
| 1 | ノート | 180.00 | 0 |
+------------+--------------+--------+-------+
1 row in set (0.00 sec)商品ID1がノートで、現在の単価が180.00であることを確認します。
ノートの単価を200.00に変更するSQL
ファイル名: update_product_price.sql
USE inventory_db;
UPDATE products
SET price = 200.00
WHERE product_id = 1;実行結果の例
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0今回のSQLは、次のように読み取れます。
| SQLの部分 | 意味 |
|---|---|
| UPDATE products | productsの行を更新する |
| SET price = 200.00 | priceに200.00を設定する |
| WHERE product_id = 1 | 商品IDが1の行を対象にする |
SETは変更する内容、WHEREは変更する対象を指定します。
また、同じ=でも、使われる場所によって役割が異なります。
- SETのprice = 200.00は、列へ設定する値を指定する。
- WHEREのproduct_id = 1は、値が等しいかを比較する。
C言語の代入と比較を思い浮かべながら、SQLでの書き方も確認しておきましょう。
変更後の商品を確認する
SELECT product_id, product_name, price, stock
FROM products
WHERE product_id = 1;実行結果の例
+------------+--------------+--------+-------+
| product_id | product_name | price | stock |
+------------+--------------+--------+-------+
| 1 | ノート | 200.00 | 0 |
+------------+--------------+--------+-------+
1 row in set (0.00 sec)単価が200.00になりました。
今回のSETで指定したのはpriceだけなので、商品名や在庫数は変わりません。今回のテーブル定義では、created_atも自動更新する設定にはしていないため、登録日時は保持されます。
対象件数と変更件数を区別しよう
UPDATEの結果には、Rows matchedとChangedが表示されます。
| 表示 | 意味 |
|---|---|
| Rows matched | WHERE条件に一致した行数 |
| Changed | 実際に値が変わった行数 |
| Warnings | 警告の件数 |
通常のMySQLクライアントで表示されるUPDATEのaffected rowsは、実際に変更された行数です。条件に一致した行数と、値が変更された行数は、必ずしも同じではありません。
たとえば、すでに単価が200.00になっている商品に、もう一度同じ値を設定した場合、次のような結果になります。
同じ値を設定した場合の実行結果の例
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1 Changed: 0 Warnings: 0対象の商品は見つかっていますが、値は変わっていません。
一方、WHERE条件に一致する商品が存在しない場合は、次のようになります。
対象の商品が存在しない場合の実行結果の例
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 Changed: 0 Warnings: 00 rows affectedだけを見て、商品が存在しないと判断しないことが大切です。
CプログラムからSQLを実行するときにも、SQLの実行失敗と、変更件数が0件だった場合は区別して扱います。
商品名と単価をまとめて変更しよう
UPDATEでは、複数の列を一度に変更できます。
次は、商品ID2の商品名を油性ボールペン、単価を150.00に変更します。
まず、変更前の状態を確認しましょう。
SELECT product_id, product_name, price, stock
FROM products
WHERE product_id = 2;実行結果の例
+------------+--------------+--------+-------+
| product_id | product_name | price | stock |
+------------+--------------+--------+-------+
| 2 | ボールペン | 120.00 | 0 |
+------------+--------------+--------+-------+
1 row in set (0.00 sec)ボールペンの商品名と単価を変更するSQL
ファイル名: update_product_details.sql
USE inventory_db;
UPDATE products
SET
product_name = '油性ボールペン',
price = 150.00
WHERE product_id = 2;実行結果の例
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0SETで複数の列を変更する場合は、それぞれの指定をカンマで区切ります。最後の指定とWHEREの間には、カンマを付けません。
商品名は文字列データなので、SQL内ではシングルクォートで囲みます。単価は数値として、そのまま指定しています。
今回は2つの列を変更していますが、変更した行は1行です。そのため、Changedは1になります。
変更後の内容を確認しましょう。
SELECT product_id, product_name, price, stock
FROM products
WHERE product_id = 2;実行結果の例
+------------+----------------+--------+-------+
| product_id | product_name | price | stock |
+------------+----------------+--------+-------+
| 2 | 油性ボールペン | 150.00 | 0 |
+------------+----------------+--------+-------+
1 row in set (0.00 sec)商品名と単価が変わっても、product_idは2のままです。同じ商品として、情報を更新できています。
WHEREを省略すると操作対象が広がる
WHEREは、UPDATEやDELETEの対象を絞り込むために使います。
WHEREを省略すると、UPDATEは全行を更新対象にし、DELETEは全行を削除対象にします。
| 命令 | WHEREがある場合 | WHEREを省略した場合 |
|---|---|---|
| UPDATE | 条件に一致する行を更新する | 全行が更新対象になる |
| DELETE | 条件に一致する行を削除する | 全行が削除対象になる |
たとえば、単価を200.00に設定するUPDATEでWHEREを省略すると、1つの商品だけでなく、すべての商品の単価を200.00に設定しようとします。
今回は、WHEREを省略した変更・削除は実行しません。対象を1商品に絞るため、主キーであるproduct_idを使います。
WHEREがあれば必ず1件になるわけではない
WHEREを付けていても、条件によっては複数の商品が対象になります。
| 条件の例 | 対象の考え方 |
|---|---|
| product_id = 1 | 主キーなので最大1件 |
| price >= 100.00 | 条件を満たす商品が複数ある可能性がある |
| stock = 0 | 在庫が0の商品がすべて対象になる |
今回の3商品は、すべて在庫が0です。stock = 0だけを条件にすると、3商品すべてが対象になります。
商品名も、今回の設計では重複を許可しています。1商品を操作する場合は、商品IDで対象を決めると分かりやすくなります。
図2:WHERE条件によって変更範囲が変わる

この図から分かること
変更する値が同じでも、WHERE条件によって操作対象は変わります。
大切なのは、WHEREが書かれているかだけでなく、その条件がどの商品に一致するかを確認することです。
DELETEで指定した商品を削除しよう
次は、商品ID3のクリアファイルを削除します。
今回は、在庫が0で、まだ入出庫の履歴がない商品を、削除の練習対象にします。
削除する商品と在庫数を確認する
SELECT product_id, product_name, price, stock
FROM products
WHERE product_id = 3
AND stock = 0;実行結果の例
+------------+----------------+-------+-------+
| product_id | product_name | price | stock |
+------------+----------------+-------+-------+
| 3 | クリアファイル | 90.00 | 0 |
+------------+----------------+-------+-------+
1 row in set (0.00 sec)ANDは、複数の条件をすべて満たす行を選ぶために使います。
今回の条件は、次の両方を満たすことを表しています。
- 商品IDが3である。
- 在庫数が0である。
この条件を、実際のDELETEにも使います。在庫がある場合は、この削除の対象にはなりません。
対応する在庫履歴を確認する
続けて、商品ID3の履歴件数を確認します。
SELECT COUNT(*) AS history_count
FROM stock_history
WHERE product_id = 3;実行結果の例
+---------------+
| history_count |
+---------------+
| 0 |
+---------------+
1 row in set (0.00 sec)前の記事までの手順では、在庫履歴はまだ登録していないため、0件になります。
独自に履歴を追加していて0件でない場合は、今回の削除対象にはせず、商品と履歴を残してください。
在庫が0の商品を商品IDで指定して削除するSQL
ファイル名: delete_product.sql
USE inventory_db;
DELETE FROM products
WHERE product_id = 3
AND stock = 0;実行結果の例
Query OK, 1 row affected (0.01 sec)DELETE FROMの後ろには、削除対象のテーブル名を書きます。WHEREには、削除する行の条件を書きます。
| SQLの部分 | 意味 |
|---|---|
| DELETE FROM products | productsから行を削除する |
| product_id = 3 | 商品ID3を指定する |
| AND stock = 0 | 在庫数が0であることも条件にする |
1 row affectedは、1行が削除されたことを示します。
DELETEは、商品名だけを消したり、在庫数を0にしたりする命令ではありません。条件に一致した行全体を削除します。productsというテーブルや、列の定義は残ります。
削除後の状態を確認しよう
同じ商品IDを検索してみます。
SELECT product_id, product_name, price, stock
FROM products
WHERE product_id = 3;実行結果の例
Empty set (0.00 sec)商品ID3の行が存在しなくなったことを確認できます。
続けて、全商品を表示しましょう。
SELECT product_id, product_name, price, stock
FROM products
ORDER BY product_id;実行結果の例
+------------+----------------+--------+-------+
| product_id | product_name | price | stock |
+------------+----------------+--------+-------+
| 1 | ノート | 200.00 | 0 |
| 2 | 油性ボールペン | 150.00 | 0 |
+------------+----------------+--------+-------+
2 rows in set (0.00 sec)変更した2商品が残り、クリアファイルが削除されています。
件数も確認します。
SELECT COUNT(*) AS product_count
FROM products;実行結果の例
+---------------+
| product_count |
+---------------+
| 2 |
+---------------+
1 row in set (0.00 sec)同じDELETEをもう一度実行しても、すでに対象の行がないため、通常は0 rows affectedになります。対象がなかったことと、SQLの実行エラーは区別しましょう。
また、商品を削除しても、ほかの商品IDが自動的に振り直されることはありません。商品IDは表示位置や件数を表す番号ではなく、商品を識別するための値です。
履歴がある商品は削除できない
今回のstock_historyには、productsを参照する外部キーがあり、ON DELETE RESTRICTを設定しています。
そのため、対象商品の履歴が残っている場合、productsからその商品を削除しようとすると拒否されます。通常はERROR 1451が返され、商品は残ります。
在庫数と履歴の有無は、別々に考える必要があります。
| 商品の状態 | 今回の削除条件と外部キーによる扱い |
|---|---|
| 在庫0、履歴なし | 商品IDが一致すれば削除できる |
| 在庫あり、履歴なし | stock = 0に一致しないため削除しない |
| 在庫0、履歴あり | 外部キー制約によって削除が拒否される |
| 在庫あり、履歴あり | 今回のWHERE条件には一致しない。商品IDだけで削除しようとしても外部キーが制限する |
たとえば、入庫した商品をすべて出庫すれば、在庫数は0になります。しかし、入出庫の履歴は残っています。
在庫が0であることと、履歴がないことは同じではありません。
なお、在庫がある商品の削除を防いでいるのは、今回追加したWHEREのstock = 0です。外部キーそのものが、在庫数を検査しているわけではありません。
また、外部キーがあっても、履歴のない商品まで誤削除から守ってくれるわけではありません。対象を正しく指定するWHERE条件は、引き続き必要です。
図3:在庫数と履歴の有無を分けて削除を判断する

この図から分かること
在庫が0の商品でも、入出庫の履歴があれば、今回の設計では削除できません。
在庫数の条件はWHEREで確認し、商品と履歴の参照関係は外部キーで守ります。それぞれの仕組みが、異なる条件を担当しています。
商品情報の変更と入出庫の処理を区別しよう
今回変更したのは、商品名と単価です。在庫数は変更していないため、入出庫の履歴を追加する操作は行っていません。
今後、Cプログラムから在庫を増減させる場合は、次の2つをそろえて実行します。
- productsのstockを更新する。
- stock_historyに入出庫の記録を追加する。
UPDATEでstockだけを書き換えても、履歴は自動的には増えません。入出庫の処理では、後で学ぶトランザクションを使い、在庫数と履歴の食い違いを防ぎます。
また、今回のSELECTによる事前確認は、対象を見極めるための手順です。確認した行をロックするものではないため、複数の利用者が同時に操作する場合の対策は、今後のトランザクションやロックの学習で扱います。
変更・削除が想定どおりにならないときは
| 状況 | 確認すること |
|---|---|
| UPDATEで0 rows affectedになる | 対象が存在しないのか、すでに同じ値なのかを確認する |
| DELETEで0 rows affectedになる | 商品IDと在庫数の条件に一致する行があるか確認する |
| 単価の変更がCHECK制約に拒否される | 負の値を指定していないか確認する |
| 商品の削除が外部キー制約に拒否される | 対応するstock_historyが存在するか確認する |
| 想定より多くの商品が対象になる | WHERE条件が複数行に一致していないか確認する |
| 構文エラーになる | SETのカンマ、文字列の引用符、WHEREの位置などを確認する |
エラーや0件という結果を見たときは、条件を広げて再実行する前に、SELECTで現在の状態を確認しましょう。
履歴があるために削除できない場合も、削除を通すためだけに履歴や外部キーを取り除くのではなく、今回決めた商品と履歴を残す方針に沿って扱います。
次はCプログラム用の接続ユーザーを作ろう
次の記事では、Cプログラムからinventory_dbへ接続するための学習用ユーザーを作成します。
これまで使ってきた管理作業用のアカウントとは別に、アプリが利用する接続ユーザーを用意し、第4章のCプログラムからの接続へ進む準備を整えましょう。










