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を実行しても取り消せません。

変更・削除は確認とセットで進めよう

今回の操作では、次の順番を基本にします。

  1. SELECTで操作対象を確認する。
  2. 同じWHERE条件でUPDATEまたはDELETEを実行する。
  3. 実行結果の件数を確認する。
  4. 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 productsproductsの行を更新する
SET price = 200.00priceに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 matchedWHERE条件に一致した行数
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: 0

0 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: 0

SETで複数の列を変更する場合は、それぞれの指定をカンマで区切ります。最後の指定と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 productsproductsから行を削除する
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プログラムからの接続へ進む準備を整えましょう。

Follow me!