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 更新対象の条件;
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;
UPDATE products
SET price_level = CASE
WHEN price > (
SELECT AVG(price)
FROM products p2
WHERE p2.category = products.category
) THEN ‘高’
ELSE ‘低’
END;
— 効率的な方法:先に集計してから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」の略で、「データが存在すれば更新、なければ挿入」という処理です。
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
);
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;
— バックアップテーブルを作成(構造のみ)
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;
— 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;
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;
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;
-- テーブル作成(初回のみ)
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;
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;