〜テーブル間コピー・履歴テーブル作成・集計データ反映などの実務ユースケース〜
はじめに
SQLで新しいデータを登録する際、INSERT INTO ... VALUES(...) を使うのが一般的ですが、
既存のテーブルデータをもとに新しいレコードを作りたいケースでは、
INSERT INTO SELECT構文 が非常に便利です。
この記事では、構文の基本から実務でのユースケースまで、分かりやすく解説します。
対応DB: PostgreSQL, MySQL, SQL Server, Oracle, SQLite 等、主要なRDBMS全般 ※本記事のサンプルコードは標準SQLおよびPostgreSQL(DATE_TRUNCなど)をベースに記載しています。
基本構文
INSERT INTO 追加先テーブル (列1, 列2, 列3, ...)
SELECT 列1, 列2, 列3, ...
FROM 参照元テーブル
WHERE 条件;
ポイント
- INSERT INTO の後に 追加先テーブルと列名 を指定する
- SELECT 部分では 参照元テーブルのデータを抽出 する
- WHERE 句で抽出条件を絞れる
ユースケース1:マスタからテンポラリテーブルを作る
開発やバッチ処理で「本番マスタを壊さないように一時テーブルを作りたい」ときに使えます。
INSERT INTO TEMP_CUSTOMER (CUSTOMER_ID, NAME, EMAIL)
SELECT CUSTOMER_ID, NAME, EMAIL
FROM CUSTOMER
WHERE IS_ACTIVE = TRUE;
有効な顧客だけを一時テーブルにコピーする例です。
ユースケース2:履歴テーブルへコピーする
「更新前の状態を履歴テーブルに保存する」ケースでもよく使われます。
INSERT INTO CUSTOMER_HISTORY (CUSTOMER_ID, NAME, UPDATED_AT)
SELECT CUSTOMER_ID, NAME, CURRENT_TIMESTAMP
FROM CUSTOMER
WHERE CUSTOMER_ID = 1001;
更新前に履歴を残すことで、変更追跡が可能になります。
ユースケース3:集計結果を別テーブルに格納する
バッチ処理やレポート生成などで、「集計結果を物理テーブルに保存」したいケースにも。
INSERT INTO MONTHLY_SALES (MONTH, TOTAL_AMOUNT)
SELECT DATE_TRUNC('month', ORDER_DATE), SUM(AMOUNT)
FROM ORDERS
GROUP BY DATE_TRUNC('month', ORDER_DATE);
月ごとの売上集計を MONTHLY_SALES テーブルに一括登録する例です。
補足 日付操作の関数はDB製品ごとに異なります。お使いのDB(MySQLなら DATE_FORMAT など)に合わせて書き換えてください。
ユースケース4:JOINを使って複数テーブルから挿入する
複数テーブルの結合結果を挿入することもできます。
INSERT INTO ORDER_DETAIL_SUMMARY (ORDER_ID, CUSTOMER_NAME, TOTAL_PRICE)
SELECT o.ORDER_ID, c.NAME, SUM(od.PRICE * od.QUANTITY)
FROM ORDERS o
JOIN ORDER_DETAIL od ON o.ORDER_ID = od.ORDER_ID
JOIN CUSTOMER c ON o.CUSTOMER_ID = c.CUSTOMER_ID
GROUP BY o.ORDER_ID, c.NAME;
JOINで結合して、複数テーブルの情報をまとめて登録する例です。
パフォーマンス・注意点
-
INSERT INTO SELECTは大量データを一括で処理できるため、ループ処理より高速 - 列数・型が一致していることを確認すること
- 大量データを扱う場合、単に「遅い」だけでなく 「サービスが止まる」リスク があること。以下の3点は必ず意識しましょう。
- 「インデックス・トリガー・制約」が重い
- インデックス : 1行挿入するたびに、裏側で「索引」の並び替えが発生します。100万件あれば100万回の書き換えが発生するため、非常に時間がかかります。
- 制約 (PRIMARY KEY/NOT NULLなど) : 挿入するデータが正しいか、他のテーブルを毎回見に行くため負荷がかかります。
- 対策: 管理者や経験者に相談のうえ、一度インデックスを削除(DROP)し、挿入後に再作成(CREATE)する方が早い場合があります。
- 「トランザクション・ログ」の肥大化
- 大量の INSERT を一度に行うと、DBは「失敗した時に元に戻せるよう」に処理内容をメモリやログファイルに溜め込みます。
- リスク : DBサーバーのディスク容量がいっぱいになり、他の処理も含めて停止する可能性があります。
- 対策 : WHERE 句でIDの範囲を区切るなどして、数回に分けて実行することを検討してください。
- テーブルロック(排他制御)
- 処理中は、対象のテーブルが「編集中」としてロックされます。
- リスク : 処理が終わるまで、他のユーザーがそのテーブルを検索したり更新したりできなくなる(待ちが発生する)ことがあります。
- 「インデックス・トリガー・制約」が重い
まとめ
| やりたいこと | 書き方 | 補足 |
|---|---|---|
| テーブルコピー | INSERT INTO A SELECT * FROM B |
条件で絞ることも可 |
| 履歴保存 | INSERT INTO history SELECT ... FROM ... |
更新前に実行 |
| 集計結果保存 | INSERT INTO report SELECT SUM(...) FROM ... |
GROUP BY活用 |
| JOINで統合 | INSERT INTO summary SELECT ... FROM A JOIN B |
複数テーブル対応 |
最後に
INSERT INTO SELECTは、データ加工・履歴管理・集計処理など
実務で最も出番の多いSQL構文の1つです。
「一括登録処理をループで書いていた」人は、
ぜひこの構文を使ってよりシンプルで高速なSQLに書き換えてみてください!