1. 導入
データベース運用において、「あるテーブルのデータを別のテーブルへコピーしたい」「特定の条件を満たすデータを別テーブルへアーカイブしたい」といった要件は頻繁に発生します。アプリケーション側で一度全データを取得して再挿入する方法もありますが、ネットワーク負荷やメモリ消費の観点から非効率です。INSERT INTO … SELECT文を活用することで、データベース内部で直接データを移動・複製でき、パフォーマンスと保守性を大幅に向上させることが可能です。
2. 基礎知識
通常、INSERT文は「値を直接指定」して行を追加しますが、この構文を用いると「SELECTの結果セット」をそのまま挿入対象にできます。重要なルールは、「挿入先テーブルの列数(または指定した列の数)」と「SELECT文で取得する列の数」が一致していることです。データ型がある程度互換性を持っていれば、列名が異なっていても問題なく挿入できます。
3. 実装/解決策
基本書式は「INSERT INTO ターゲットテーブル (列リスト) SELECT ソース列 FROM ソーステーブル WHERE 条件;」です。この手法は、ログデータの退避や、特定のステータスを持つレコードの抽出・集約に最適です。特に、大量データを扱う場合、アプリケーションを介さないことでトランザクションの制御が容易になり、一貫性を保ちやすくなります。
4. サンプルプログラム
以下は、SQLite環境での実行例です。古いデータを別テーブルに退避させるシナリオを想定しています。
— 1. 準備: 元データ用テーブルを作成
CREATE TABLE users (id INTEGER, name TEXT, age INTEGER);
INSERT INTO users VALUES (1, ‘Yamada’, 24), (2, ‘Oota’, 31), (3, ‘Suzuki’, 18);
— 2. 退避先テーブルの作成
CREATE TABLE archived_users (userid INTEGER, name TEXT, archived_at TIMESTAMP);
— 3. 条件に合致するデータ(20歳以上)を別テーブルへ移行
— ターゲットの列数とSELECTの列数を合わせるのがポイントです
INSERT INTO archived_users (userid, name, archived_at)
SELECT id, name, CURRENT_TIMESTAMP
FROM users
WHERE age >= 20;
— 4. 結果の確認
SELECT FROM archived_users;
5. 応用・注意点
現場で活用する際の注意点は以下の通りです。
・トランザクション管理: 大量データを移行する場合、途中で失敗すると中途半端なデータが残ります。必ず「BEGIN TRANSACTION」と「COMMIT」で囲み、原子性を確保してください。
・主キーの重複: 移行先テーブルに主キー制約がある場合、重複が発生するとエラーになります。「INSERT OR IGNORE」や「INSERT OR REPLACE」といった修飾子を検討してください。
・NULLの扱い: SELECT文で指定しなかった列には、NULLが格納されます。もし移行先テーブルでNOT NULL制約がある場合は、SELECT文側でCOALESCE関数などを使ってデフォルト値を補完するようにしてください。
・パフォーマンス: 移行対象が数百万件を超える場合、一度に実行するとログ領域が枯渇したり、テーブルロック時間が長引いたりします。その場合は、WHERE句でID範囲を指定し、バッチ処理のように分割実行することをお勧めします。

コメント