Step 13:高度なINSERT/UPDATE/DELETE

🚀 Step 13: 高度なINSERT/UPDATE/DELETE

データ操作をマスターして実務レベルへ!

📋 このステップで学ぶこと
  • INSERT SELECT で大量データを効率的に挿入
  • サブクエリを使った高度なUPDATE
  • UPSERT(INSERT ON CONFLICT)の使い方
  • トランザクションでデータの一貫性を保つ
  • 複数テーブルの連携処理

🎯 1. これまでのINSERTとの違い

1-1. 今まで学んだINSERT(1行ずつ挿入)

これまでは、1行のデータを挿入するために INSERT INTO ... VALUES を使っていました。

※横にスクロールできます

— 1行ずつ挿入する方法(今まで学んだ方法) INSERT INTO customers (customer_id, customer_name, email) VALUES (1, ‘山田太郎’, ‘yamada@example.com’); INSERT INTO customers (customer_id, customer_name, email) VALUES (2, ‘鈴木花子’, ‘suzuki@example.com’); INSERT INTO customers (customer_id, customer_name, email) VALUES (3, ‘田中次郎’, ‘tanaka@example.com’);

この方法でも動きますが、100件、1000件、10000件のデータを挿入したい場合は大変です。

1-2. INSERT SELECTとは

INSERT SELECT は、SELECTの結果をそのままINSERTする方法です。他のテーブルからデータをコピーしたり、集計結果を保存したりできます。

💡 INSERT SELECTが便利な場面
  • バックアップテーブルの作成:既存データを別テーブルにコピー
  • 集計結果の保存:月次売上などをサマリーテーブルに保存
  • データ移行:古いテーブルから新しいテーブルへ
  • 履歴テーブルへの記録:変更前のデータを履歴として保存
  • 条件付きコピー:特定の条件に合うデータだけをコピー

1-3. INSERT SELECTの基本構文

📌 INSERT SELECTの書き方

※横にスクロールできます

INSERT INTO コピー先テーブル (列1, 列2, 列3, …) SELECT 列1, 列2, 列3, … FROM コピー元テーブル WHERE 条件;

ポイント:

  • VALUES の代わりに SELECT を使う
  • SELECTの列数・データ型とINSERTの列数・データ型を合わせる
  • SELECTで使えるすべての機能(WHERE、JOIN、GROUP BY等)が使える

📚 2. INSERT SELECTの基本例

2-1. 例1:全データをバックアップする

やりたいこと:customersテーブルの全データをバックアップテーブルにコピーする

ステップ1:バックアップテーブルを作成

※横にスクロールできます

— 元テーブルと同じ構造のテーブルを作成(データは空) CREATE TABLE customers_backup AS SELECT * FROM customers WHERE 1=0;
解説:
  • CREATE TABLE ... AS SELECT → SELECTの結果でテーブルを作成
  • WHERE 1=0 → 常にFALSEなので、データは0件(構造だけコピー)

ステップ2:データをコピー

※横にスクロールできます

— 全データを一括コピー INSERT INTO customers_backup SELECT * FROM customers;
解説:
  • SELECT * → 全列を選択
  • 列名を省略 → SELECT * と同じ順序で挿入される
  • 1万件のデータも1つのSQLで一瞬でコピーできる

2-2. 例2:条件付きでコピーする

やりたいこと:東京都の顧客だけを別テーブルにコピーする

※横にスクロールできます

— 東京都の顧客だけをコピー INSERT INTO tokyo_customers (customer_id, customer_name, email, prefecture) SELECT customer_id, customer_name, email, prefecture FROM customers WHERE prefecture = ‘東京都’;
解説:
  • WHERE prefecture = '東京都' → 東京都のデータだけが対象
  • 列名を明示 → どの列に何を入れるか明確になる

2-3. 例3:集計結果を保存する

やりたいこと:月ごとの売上合計をサマリーテーブルに保存する

コードを段階的に見ていきましょう。

ステップ1:月次売上を集計するSELECT文を作成

※横にスクロールできます

SELECT strftime(‘%Y-%m’, order_date) AS year_month, SUM(total_amount) AS total_amount, COUNT(*) AS order_count FROM orders WHERE order_date >= ‘2024-01-01’ GROUP BY strftime(‘%Y-%m’, order_date)

ステップ2:INSERT文と組み合わせる

※横にスクロールできます

INSERT INTO monthly_sales_summary (year_month, total_amount, order_count) SELECT strftime(‘%Y-%m’, order_date) AS year_month, SUM(total_amount) AS total_amount, COUNT(*) AS order_count FROM orders WHERE order_date >= ‘2024-01-01’ GROUP BY strftime(‘%Y-%m’, order_date);
結果イメージ(monthly_sales_summaryテーブル):
year_month total_amount order_count
2024-01 1,500,000 120
2024-02 1,800,000 145
2024-03 2,100,000 168

2-4. 例4:JOINを使った複雑なコピー

やりたいこと:顧客情報と注文情報を結合して履歴テーブルに保存する

※横にスクロールできます

INSERT INTO customer_order_history (customer_name, email, order_date, total_amount, product_name) SELECT c.customer_name, c.email, o.order_date, o.total_amount, p.product_name FROM orders o JOIN customers c ON o.customer_id = c.customer_id JOIN products p ON o.product_id = p.product_id WHERE o.order_date >= ‘2024-01-01’;
ポイント:
  • JOINで複数テーブルを結合した結果を挿入できる
  • 必要な列だけを選んで、別の形式で保存できる
  • 分析用のデータマートを作成するのに便利

⚡ 3. INSERT SELECTのメリット

💡 INSERT SELECTの3つのメリット
メリット 説明
高速 1行ずつ挿入するより圧倒的に速い。1万件でも数秒で完了。
簡潔 数万件のデータも1つのSQLで処理。コードがシンプル。
柔軟 SELECT文の機能(WHERE、JOIN、GROUP BY等)をすべて使える。
📌 INSERT SELECTを使う前の確認
  • 先にSELECTだけで実行して、結果を確認しよう
  • 列の順序・データ型が合っているか確認
  • 重複データが発生しないか確認(主キー制約など)
  • 大量データの場合は、まず少量でテスト
活用シーン:
  • 日次バッチ処理:毎日の集計結果をサマリーテーブルに保存
  • データウェアハウス:分析用のテーブルにデータを転送
  • アーカイブ:古いデータを履歴テーブルに移動
  • テストデータ作成:本番データをテスト環境にコピー

🔄 4. サブクエリを使ったUPDATE

4-1. 今まで学んだUPDATEとの違い

これまでは、固定の値でUPDATEしていました。

※横にスクロールできます

— 今まで学んだUPDATE(固定値で更新) UPDATE customers SET customer_rank = ‘ゴールド’ WHERE customer_id = 101;

しかし実務では、他のテーブルから計算した値で更新したいことがよくあります。例えば:

  • 顧客の総購入額に応じて、顧客ランクを自動更新したい
  • 各顧客の注文回数を、ordersテーブルから計算して更新したい
  • 商品の平均評価を、レビューテーブルから計算して更新したい

このような場合にサブクエリを使ったUPDATEが活躍します!

4-2. 基本構文

📌 サブクエリを使ったUPDATEの書き方

※横にスクロールできます

UPDATE テーブル名 SET 列名 = ( SELECT 計算結果 FROM 他のテーブル WHERE 条件(外側のテーブルと紐づけ) ) WHERE 更新対象の条件;

ポイント:

  • SET の値にサブクエリを使う
  • サブクエリは1つの値を返す必要がある(スカラーサブクエリ)
  • 外側のテーブルの列を参照できる(相関サブクエリ

4-3. 例1:顧客の注文回数を更新する

やりたいこと:各顧客の注文回数を、ordersテーブルから計算してcustomersテーブルに保存する

コードを段階的に見ていきましょう。

ステップ1:特定の顧客の注文回数を計算するSELECT文

※横にスクロールできます

— 顧客ID=101の注文回数を計算 SELECT COUNT(*) FROM orders WHERE customer_id = 101;

ステップ2:これをサブクエリにしてUPDATEに組み込む

※横にスクロールできます

UPDATE customers SET order_count = ( SELECT COUNT(*) FROM orders WHERE orders.customer_id = customers.customer_id — 外側と紐づけ );
クエリの解説:
  • orders.customer_id = customers.customer_id → 外側のテーブル(customers)と紐づけ
  • これを相関サブクエリと呼ぶ(外側の各行ごとにサブクエリが実行される)
  • WHEREを省略 → 全顧客が更新対象
実行前:
customer_id customer_name order_count
101 山田太郎 NULL
102 鈴木花子 NULL
実行後:
customer_id customer_name order_count
101 山田太郎 15
102 鈴木花子 8

4-4. 例2:顧客ランクを総購入額で更新する

やりたいこと:顧客の総購入額に応じてランクを自動判定して更新する

コードを段階的に見ていきましょう。

ステップ1:総購入額を計算するサブクエリ

※横にスクロールできます

SELECT COALESCE(SUM(total_amount), 0) FROM orders WHERE orders.customer_id = customers.customer_id

ステップ2:CASE式でランクを判定

※横にスクロールできます

CASE WHEN 総購入額 >= 100000 THEN ‘プラチナ’ WHEN 総購入額 >= 50000 THEN ‘ゴールド’ WHEN 総購入額 >= 10000 THEN ‘シルバー’ ELSE ‘ブロンズ’ END

完成コード:

※横にスクロールできます

UPDATE customers SET customer_rank = CASE WHEN ( SELECT COALESCE(SUM(total_amount), 0) FROM orders WHERE orders.customer_id = customers.customer_id ) >= 100000 THEN ‘プラチナ’ WHEN ( SELECT COALESCE(SUM(total_amount), 0) FROM orders WHERE orders.customer_id = customers.customer_id ) >= 50000 THEN ‘ゴールド’ WHEN ( SELECT COALESCE(SUM(total_amount), 0) FROM orders WHERE orders.customer_id = customers.customer_id ) >= 10000 THEN ‘シルバー’ ELSE ‘ブロンズ’ END;
クエリの解説:
  • COALESCE(SUM(...), 0) → 注文がない顧客は0円として扱う
  • CASE式の各WHENでサブクエリを使用
  • 全顧客のランクが一度に更新される
📌 注意:同じサブクエリが複数回実行される

上記のクエリでは、同じサブクエリが3回書かれています。これはパフォーマンス上の問題になることがあります。後のセクションで効率的な書き方を学びます。

4-5. 例3:カテゴリ平均との比較で更新する

やりたいこと:各商品の価格が、同じカテゴリの平均価格より高いか低いかを判定する

※横にスクロールできます

UPDATE products SET price_level = CASE WHEN price > ( SELECT AVG(price) FROM products p2 WHERE p2.category = products.category ) THEN ‘高’ ELSE ‘低’ END;
クエリの解説:
  • p2.category = products.category → 同じカテゴリの商品だけで平均を計算
  • 各商品ごとに、そのカテゴリの平均価格と比較
  • 自分自身も平均計算に含まれる

🔍 5. 相関サブクエリの仕組み

5-1. 相関サブクエリとは

相関サブクエリとは、外側のクエリの値を参照するサブクエリのことです。外側の各行に対して、サブクエリが繰り返し実行されます。

💡 実行イメージ(顧客ごとの注文回数を計算する場合):
処理順 外側の行 サブクエリの条件 結果
1回目 customer_id = 101 WHERE customer_id = 101 15件
2回目 customer_id = 102 WHERE customer_id = 102 8件
3回目 customer_id = 103 WHERE customer_id = 103 3件

顧客が1000人いれば、サブクエリも1000回実行されます。

5-2. パフォーマンスの注意点

⚠️ 相関サブクエリは遅くなる可能性がある
  • 外側の行数 × サブクエリの実行回数 で処理量が増える
  • 1万行のテーブルなら、サブクエリが1万回実行される
  • 大量データの場合は、別のアプローチを検討

5-3. 効率的な代替案(CTEを使う)

サブクエリを何度も実行する代わりに、先に集計結果を計算しておく方法があります。

※横にスクロールできます

— 効率的な方法:先に集計してからUPDATE WITH customer_totals AS ( SELECT customer_id, COALESCE(SUM(total_amount), 0) AS total_purchase FROM orders GROUP BY customer_id ) UPDATE customers SET customer_rank = CASE WHEN (SELECT total_purchase FROM customer_totals WHERE customer_totals.customer_id = customers.customer_id) >= 100000 THEN ‘プラチナ’ WHEN (SELECT total_purchase FROM customer_totals WHERE customer_totals.customer_id = customers.customer_id) >= 50000 THEN ‘ゴールド’ WHEN (SELECT total_purchase FROM customer_totals WHERE customer_totals.customer_id = customers.customer_id) >= 10000 THEN ‘シルバー’ ELSE ‘ブロンズ’ END;
ポイント:
  • CTEで先に集計結果を計算(1回だけ実行)
  • UPDATEではCTEの結果を参照するだけ
  • 大量データでは大幅に高速化される可能性がある
活用シーン:
  • 顧客管理:購入実績に基づくランク更新
  • 在庫管理:売上数に基づく在庫ステータス更新
  • 商品管理:レビュー平均に基づく評価更新
  • 人事管理:実績に基づく評価更新

🔀 6. UPSERT(INSERT ON CONFLICT)

6-1. UPSERTとは

UPSERT(アップサート)は、「UPDATE or INSERT」の略で、「データが存在すれば更新、なければ挿入」という処理です。

💡 UPSERTが必要な場面

例えば、日次売上サマリーを保存するテーブルを考えてみましょう:

  • 1回目の実行:2024-01-15のデータがない → INSERTする
  • 2回目の実行:2024-01-15のデータがある → UPDATEする

通常は「データがあるか確認 → あればUPDATE、なければINSERT」と2回のSQLが必要ですが、UPSERTなら1回のSQLで済みます!

6-2. UPSERTの基本構文

📌 SQLiteでのUPSERT構文(SQLite 3.24.0以降)

※横にスクロールできます

INSERT INTO テーブル名 (列1, 列2, 列3, …) VALUES (値1, 値2, 値3, …) ON CONFLICT(主キーまたはユニーク列) DO UPDATE SET 列2 = 新しい値, 列3 = 新しい値;

各部分の意味:

  • INSERT INTO ... VALUES ... → 通常のINSERT文
  • ON CONFLICT(列名) → この列で重複が発生した場合
  • DO UPDATE SET ... → 重複時はUPDATEを実行

6-3. 例1:顧客情報の更新または挿入

やりたいこと:customer_id=101の顧客を挿入。既に存在すれば情報を更新する

※横にスクロールできます

INSERT INTO customers (customer_id, customer_name, email, prefecture) VALUES (101, ‘山田太郎’, ‘yamada_new@example.com’, ‘東京都’) ON CONFLICT(customer_id) DO UPDATE SET customer_name = excluded.customer_name, email = excluded.email, prefecture = excluded.prefecture, updated_at = CURRENT_TIMESTAMP;
クエリの解説:
  • ON CONFLICT(customer_id) → customer_idで重複が発生した場合
  • excluded.customer_name → INSERTしようとした値を参照
  • updated_at = CURRENT_TIMESTAMP → 更新日時も記録
📌 excludedキーワードについて

excluded は、INSERTしようとした値(競合した行)を参照するための特別なキーワードです。「INSERT文で指定した値」という意味です。

6-4. 例2:日次売上サマリーの更新

やりたいこと:日付ごとの売上を保存。既にある場合は値を加算する

※横にスクロールできます

INSERT INTO daily_sales (sale_date, total_amount, order_count) VALUES (‘2024-11-10’, 50000, 10) ON CONFLICT(sale_date) DO UPDATE SET total_amount = total_amount + excluded.total_amount, order_count = order_count + excluded.order_count;
クエリの解説:
  • ON CONFLICT(sale_date) → 同じ日付で重複した場合
  • total_amount + excluded.total_amount → 既存の値に新しい値を加算
  • バッチ処理で何度実行しても、正しく累積される

6-5. 例3:INSERT SELECTとUPSERTの組み合わせ

やりたいこと:月次売上サマリーを集計して保存(既存データは上書き)

※横にスクロールできます

INSERT INTO monthly_sales (year_month, total_amount, order_count, avg_amount) SELECT strftime(‘%Y-%m’, order_date) AS year_month, SUM(total_amount) AS total_amount, COUNT(*) AS order_count, AVG(total_amount) AS avg_amount FROM orders GROUP BY strftime(‘%Y-%m’, order_date) ON CONFLICT(year_month) DO UPDATE SET total_amount = excluded.total_amount, order_count = excluded.order_count, avg_amount = excluded.avg_amount, updated_at = CURRENT_TIMESTAMP;
ポイント:
  • INSERT SELECTとON CONFLICTを組み合わせられる
  • 定期的に実行しても、データが重複しない
  • レポート用のサマリーテーブルを常に最新に保てる

6-6. UPSERTが使えない場合の代替方法

古いバージョンのSQLiteや他のDBMSでは、以下の方法が使えます。

方法1:INSERT OR REPLACE

※横にスクロールできます

— 既存データを削除して再挿入 INSERT OR REPLACE INTO customers (customer_id, customer_name, email) VALUES (101, ‘山田太郎’, ‘yamada@example.com’);
⚠️ INSERT OR REPLACEの注意点
  • 既存行をDELETE→INSERTするため、AUTOINCREMENTの値が変わる
  • 外部キー制約がある場合、関連データに影響する可能性
  • できればON CONFLICTを使う方が安全

方法2:UPDATE→INSERTの2段階処理

※横にスクロールできます

— まず更新を試みる UPDATE customers SET customer_name = ‘山田太郎’, email = ‘yamada@example.com’ WHERE customer_id = 101; — 更新されなかったら(該当行がなければ)挿入 INSERT INTO customers (customer_id, customer_name, email) SELECT 101, ‘山田太郎’, ‘yamada@example.com’ WHERE NOT EXISTS ( SELECT 1 FROM customers WHERE customer_id = 101 );

🛡️ 7. トランザクション

7-1. トランザクションとは

トランザクションとは、複数のSQL文を1つの処理単位としてまとめる機能です。「全部成功」か「全部失敗」のどちらかになります。

💡 トランザクションが必要な例:銀行の送金

AさんからBさんに10,000円を送金する場合:

  1. Aさんの口座から10,000円を引く
  2. Bさんの口座に10,000円を足す

もし1だけ成功して2が失敗したら、10,000円が消えてしまいます!
トランザクションを使えば、2が失敗した場合は1も取り消されます。

7-2. トランザクションの基本構文

📌 トランザクションの書き方

※横にスクロールできます

— トランザクション開始 BEGIN TRANSACTION; — 複数のSQL文を実行 UPDATE …; INSERT …; DELETE …; — 成功したら確定 COMMIT; — 失敗したら取り消し — ROLLBACK;

各部分の意味:

  • BEGIN TRANSACTION → トランザクション開始
  • COMMIT → 全ての変更を確定(データベースに反映)
  • ROLLBACK → 全ての変更を取り消し(なかったことに)

7-3. 例1:注文確定処理

やりたいこと:注文を確定する際に、在庫減少・ステータス更新・ポイント付与を同時に行う

コードを段階的に見ていきましょう。

ステップ1:トランザクション開始

※横にスクロールできます

BEGIN TRANSACTION;

ステップ2:注文ステータスを更新

※横にスクロールできます

UPDATE orders SET status = ‘確定’, confirmed_at = CURRENT_TIMESTAMP WHERE order_id = 12345;

ステップ3:在庫を減らす

※横にスクロールできます

UPDATE products SET stock = stock – ( SELECT quantity FROM order_items WHERE order_items.product_id = products.product_id AND order_items.order_id = 12345 ) WHERE product_id IN ( SELECT product_id FROM order_items WHERE order_id = 12345 );

ステップ4:顧客にポイントを付与

※横にスクロールできます

UPDATE customers SET points = points + ( SELECT total_amount * 0.1 — 購入額の10% FROM orders WHERE order_id = 12345 ) WHERE customer_id = ( SELECT customer_id FROM orders WHERE order_id = 12345 );

ステップ5:トランザクション確定

※横にスクロールできます

COMMIT;

完成コード:

※横にスクロールできます

BEGIN TRANSACTION; — 1. 注文ステータスを更新 UPDATE orders SET status = ‘確定’, confirmed_at = CURRENT_TIMESTAMP WHERE order_id = 12345; — 2. 在庫を減らす UPDATE products SET stock = stock – ( SELECT quantity FROM order_items WHERE order_items.product_id = products.product_id AND order_items.order_id = 12345 ) WHERE product_id IN ( SELECT product_id FROM order_items WHERE order_id = 12345 ); — 3. 顧客にポイントを付与(購入額の10%) UPDATE customers SET points = points + ( SELECT total_amount * 0.1 FROM orders WHERE order_id = 12345 ) WHERE customer_id = ( SELECT customer_id FROM orders WHERE order_id = 12345 ); — 全て成功したら確定 COMMIT;
トランザクションの効果:
  • 途中でエラーが発生 → ROLLBACKで全て取り消し
  • 全て成功 → COMMITで全て確定
  • データの整合性が保たれる

7-4. ACID特性

トランザクションには、ACIDと呼ばれる4つの重要な特性があります。

トランザクションのACID特性:
特性 英語 意味
Atomicity 原子性 全部成功か全部失敗か(中途半端な状態にならない)
Consistency 一貫性 データの整合性が常に保たれる
Isolation 独立性 他のトランザクションの影響を受けない
Durability 永続性 COMMITしたデータは消えない(障害があっても)

7-5. トランザクションのベストプラクティス

📌 トランザクションを使うときの注意点
  • 短くまとめる:長いトランザクションは他の処理をブロックする
  • エラー処理を必ず実装:エラー時はROLLBACKする
  • テスト環境で十分に確認:本番環境で実行する前に
  • デッドロックに注意:複数のロックの順序を統一する
活用シーン:
  • ECサイト:注文確定(在庫減少、ステータス更新、ポイント付与)
  • 銀行システム:送金(出金と入金を同時に)
  • 在庫管理:入出庫処理(在庫数と履歴を同時に更新)
  • 予約システム:予約確定(空き枠減少と予約情報登録)

📝 練習問題

高度なデータ操作をマスターしましょう!

問題 1基本

INSERT SELECTでバックアップ

customersテーブルの全データを、customers_backupテーブルにコピーしてください。

※横にスクロールできます

— バックアップテーブルを作成(構造のみ) CREATE TABLE customers_backup AS SELECT * FROM customers WHERE 1=0; — データを一括コピー INSERT INTO customers_backup SELECT * FROM customers; — 確認 SELECT COUNT(*) AS backup_count FROM customers_backup;

解説:

  • WHERE 1=0 で構造だけコピー(データは0件)
  • SELECT * で全データを挿入

問題 2基本

条件付きコピー

ordersテーブルから、2024年のデータだけをorders_2024テーブルにコピーしてください。

※横にスクロールできます

— 2024年テーブルを作成 CREATE TABLE orders_2024 AS SELECT * FROM orders WHERE 1=0; — 2024年のデータをコピー INSERT INTO orders_2024 SELECT * FROM orders WHERE order_date >= ‘2024-01-01’ AND order_date < '2025-01-01'; -- 確認 SELECT COUNT(*) FROM orders_2024;

解説:

  • WHERE句で期間を指定してコピー
  • < '2025-01-01' で2024年末までを含む

問題 3基本

集計結果の保存

各顧客の注文回数と総購入金額を計算して、customer_summaryテーブルに保存してください。

※横にスクロールできます

-- サマリーテーブルを作成 CREATE TABLE customer_summary ( customer_id INTEGER PRIMARY KEY, order_count INTEGER, total_amount INTEGER ); -- 集計結果を挿入 INSERT INTO customer_summary (customer_id, order_count, total_amount) SELECT customer_id, COUNT(*) AS order_count, SUM(total_amount) AS total_amount FROM orders GROUP BY customer_id; -- 確認 SELECT * FROM customer_summary LIMIT 5;

解説:

  • GROUP BYの結果を直接INSERTできる
  • 集計関数(COUNT、SUM)もそのまま使える

問題 4応用

サブクエリを使った更新

customersテーブルのorder_count列を、実際の注文回数で更新してください。

※横にスクロールできます

-- 注文回数を計算して更新 UPDATE customers SET order_count = ( SELECT COUNT(*) FROM orders WHERE orders.customer_id = customers.customer_id ); -- 確認 SELECT customer_id, customer_name, order_count FROM customers LIMIT 5;

解説:

  • 相関サブクエリで各顧客の注文数を計算
  • 外側のcustomersテーブルの各行に対してサブクエリが実行される

問題 5応用

CASE式を使った更新

productsテーブルのstock_status列を以下のルールで更新してください:
- stock >= 100: '十分'
- stock >= 50: '普通'
- stock < 50: '少ない'

※横にスクロールできます

UPDATE products SET stock_status = CASE WHEN stock >= 100 THEN '十分' WHEN stock >= 50 THEN '普通' ELSE '少ない' END; -- 確認 SELECT product_name, stock, stock_status FROM products ORDER BY stock DESC LIMIT 10;

解説:

  • CASE式で複雑な条件分岐を実装
  • 厳しい条件(>= 100)を先に書く

問題 6チャレンジ

UPSERTの実装

customer_id=999の顧客を挿入し、既に存在する場合はメールアドレスと都道府県を更新してください。

※横にスクロールできます

INSERT INTO customers (customer_id, customer_name, email, prefecture) VALUES (999, 'テスト太郎', 'test999@example.com', '大阪府') ON CONFLICT(customer_id) DO UPDATE SET email = excluded.email, prefecture = excluded.prefecture, updated_at = CURRENT_TIMESTAMP; -- 確認 SELECT * FROM customers WHERE customer_id = 999;

解説:

  • ON CONFLICTで重複時の動作を指定
  • excludedで挿入しようとした値を参照

問題 7チャレンジ

トランザクションでの処理

以下の処理をトランザクションで実行してください:
1. 新しい注文を追加(customer_id=101, total_amount=10000)
2. 顧客のorder_countを1増やす
3. 顧客のpointsを1000増やす

※横にスクロールできます

BEGIN TRANSACTION; -- 1. 注文を追加 INSERT INTO orders (customer_id, total_amount, order_date) VALUES (101, 10000, CURRENT_TIMESTAMP); -- 2. 注文回数を増やす UPDATE customers SET order_count = order_count + 1 WHERE customer_id = 101; -- 3. ポイントを増やす UPDATE customers SET points = points + 1000 WHERE customer_id = 101; -- 確認 SELECT * FROM customers WHERE customer_id = 101; -- 問題なければ確定 COMMIT;

解説:

  • BEGIN TRANSACTIONで開始
  • 複数のSQLを順番に実行
  • COMMITで全て確定(エラー時はROLLBACK)

問題 8チャレンジ

顧客ランクの計算と更新

各顧客の総購入金額を計算し、以下のルールでcustomer_rankを更新してください:
- 100,000円以上: 'プラチナ'
- 50,000円以上: 'ゴールド'
- 10,000円以上: 'シルバー'
- それ以外: 'ブロンズ'

※横にスクロールできます

UPDATE customers SET customer_rank = CASE WHEN ( SELECT COALESCE(SUM(total_amount), 0) FROM orders WHERE orders.customer_id = customers.customer_id ) >= 100000 THEN 'プラチナ' WHEN ( SELECT COALESCE(SUM(total_amount), 0) FROM orders WHERE orders.customer_id = customers.customer_id ) >= 50000 THEN 'ゴールド' WHEN ( SELECT COALESCE(SUM(total_amount), 0) FROM orders WHERE orders.customer_id = customers.customer_id ) >= 10000 THEN 'シルバー' ELSE 'ブロンズ' END; -- 確認 SELECT customer_name, (SELECT COALESCE(SUM(total_amount), 0) FROM orders WHERE orders.customer_id = customers.customer_id) AS total_amount, customer_rank FROM customers ORDER BY total_amount DESC LIMIT 10;

解説:

  • 相関サブクエリで各顧客の総購入額を計算
  • COALESCE関数で注文がない顧客を0円として扱う
  • CASE式でランクを判定

問題 9チャレンジ

月次サマリーのUPSERT

月ごとの売上サマリーを計算し、monthly_salesテーブルにUPSERTしてください。
既存データがあれば上書き更新してください。

※横にスクロールできます

-- テーブル作成(初回のみ) CREATE TABLE IF NOT EXISTS monthly_sales ( year_month TEXT PRIMARY KEY, total_amount INTEGER, order_count INTEGER, avg_amount REAL, updated_at TEXT ); -- 月次サマリーをUPSERT INSERT INTO monthly_sales ( year_month, total_amount, order_count, avg_amount, updated_at ) SELECT strftime('%Y-%m', order_date) AS year_month, SUM(total_amount) AS total_amount, COUNT(*) AS order_count, AVG(total_amount) AS avg_amount, CURRENT_TIMESTAMP AS updated_at FROM orders GROUP BY strftime('%Y-%m', order_date) ON CONFLICT(year_month) DO UPDATE SET total_amount = excluded.total_amount, order_count = excluded.order_count, avg_amount = excluded.avg_amount, updated_at = CURRENT_TIMESTAMP; -- 確認 SELECT * FROM monthly_sales ORDER BY year_month DESC;

解説:

  • INSERT SELECTで集計結果を挿入
  • ON CONFLICTで既存データを上書き
  • 定期実行で常に最新データを維持できる

問題 10チャレンジ

注文確定の総合処理

注文ID=12345を「完了」にする総合処理を、トランザクションで実装してください:
1. ordersのstatusを'完了'に更新
2. 顧客のポイントを注文金額の10%付与
3. ポイント履歴テーブルに記録を追加

※横にスクロールできます

BEGIN TRANSACTION; -- 1. 注文ステータスを更新 UPDATE orders SET status = '完了', completed_at = CURRENT_TIMESTAMP WHERE order_id = 12345; -- 2. ポイントを付与(注文金額の10%) UPDATE customers SET points = points + ( SELECT total_amount * 0.1 FROM orders WHERE order_id = 12345 ) WHERE customer_id = ( SELECT customer_id FROM orders WHERE order_id = 12345 ); -- 3. ポイント履歴を記録 INSERT INTO point_history (customer_id, points, reason, created_at) SELECT customer_id, total_amount * 0.1, '購入ポイント付与(注文ID:12345)', CURRENT_TIMESTAMP FROM orders WHERE order_id = 12345; -- 全て成功したら確定 COMMIT;

解説:

  • トランザクションで複数の処理を一度に確定
  • 途中でエラーが発生したらROLLBACKで全て取り消し
  • データの整合性が保たれる

📝 Step 13 のまとめ

✅ 学んだこと
  • INSERT SELECT:他のテーブルからデータを一括コピー
  • サブクエリを使ったUPDATE:他のテーブルの計算結果で更新
  • 相関サブクエリ:外側のテーブルを参照するサブクエリ
  • UPSERT:存在すればUPDATE、なければINSERT
  • トランザクション:複数の処理を一度に確定/取り消し
  • ACID特性:データの整合性を保つための4つの特性
📌 重要な構文

※横にスクロールできます

-- INSERT SELECT INSERT INTO テーブル名 SELECT 列1, 列2, ... FROM 元テーブル WHERE 条件; -- サブクエリでのUPDATE UPDATE テーブル名 SET 列名 = (SELECT ... FROM ... WHERE ...) WHERE 条件; -- UPSERT INSERT INTO テーブル名 (列1, 列2, ...) VALUES (値1, 値2, ...) ON CONFLICT(主キー) DO UPDATE SET 列1 = excluded.列1; -- トランザクション BEGIN TRANSACTION; -- 複数のSQL文 COMMIT; -- または ROLLBACK;
📌 注意点
  • INSERT SELECTの前にSELECTだけでテストする
  • 相関サブクエリは遅い可能性:大量データではCTEを検討
  • トランザクションは短く:長時間ロックを避ける
  • 本番前にテスト環境で確認:データ操作は慎重に

❓ よくある質問

Q1: INSERT SELECTとINSERT VALUESの使い分けは?

データ量で判断します。1〜数行ならVALUES、大量(数十行以上)ならSELECTを使うと効率的です。また、他のテーブルからコピーする場合は必然的にSELECTになります。

Q2: UPDATEでサブクエリを使うと遅くなりませんか?

相関サブクエリは各行ごとに実行されるため、大量データでは遅くなる可能性があります。その場合は、CTEで先に集計してから参照する方法を検討してください。

Q3: UPSERTが使えない古いSQLiteではどうすれば?

INSERT OR REPLACEを使うか、UPDATE→INSERTの2段階処理を実装します。ただし、INSERT OR REPLACEは既存行を削除してから挿入するため、AUTO INCREMENTの値が変わる点に注意してください。

Q4: トランザクション中にエラーが起きたら?

エラーが起きた時点でROLLBACKして、全ての変更を取り消します。プログラムで実装する場合は、try-catchで例外をキャッチしてROLLBACKを実行するのが一般的です。

Q5: トランザクションはどのくらい短くすべき?

できるだけ短くが原則です。長いトランザクションは他の処理をブロックしてしまいます。目安として、数秒以内に終わる処理にまとめるのが理想的です。

Q6: excludedキーワードは何を参照していますか?

excludedは、INSERT文で挿入しようとした値(競合した行)を参照するための特別なキーワードです。「INSERT文のVALUESで指定した値」または「INSERT SELECTの結果」を指します。

🎓 次のステップでは

Step 14: インデックスの基礎理論では、SQLのパフォーマンスを劇的に向上させるインデックスの仕組みと使い方を学びます。大量データを高速に検索するための必須知識です!

📝

学習メモ

SQL応用・パフォーマンス最適化 - Step 13

📋 過去のメモ一覧
#artnasekai #学習メモ
LINE