【SQL】INSERT INTO…SELECT完全ガイド|条件絞り込み・JOIN・CASE WHEN加工・異テーブル列マッピング・アーカイブ・パフォーマンスまで解説

INSERT INTO ... SELECT は、SELECT で取り出したデータをそのまま別のテーブルに挿入できる構文です。

「条件に合致したデータをアーカイブテーブルに移す」「集計結果をサマリーテーブルに保存する」「本番データを加工してステージングに反映する」——データの複製・移行・集約が 1 つの SQL 文で完結します。

この記事では基本構文から、WHERE 条件・JOIN・CASE WHEN を使った加工、異なる列構造のテーブルへのマッピング、大量データ時のパフォーマンス対策、アーカイブ・バックアップ・履歴管理などの実務パターンまで体系的に解説します。

サンプルデータ(以降の例で使用)
-- orders テーブル(注文)
-- id | customer_id | product_id | amount | status    | region | ordered_at
--  1 |           1 |        101 |  12000 | shipped   | east   | 2024-01-15 09:30:00
--  2 |           2 |        102 |   3500 | pending   | west   | 2024-02-01 15:45:00
--  3 |           3 |        101 |  85000 | shipped   | east   | 2024-02-20 11:00:00
--  4 |           1 |        103 |   2800 | canceled  | north  | 2024-03-05 08:00:00
--  5 |           4 |        102 |  45000 | pending   | west   | 2024-03-10 17:30:00
--  6 |           2 |        101 |   9500 | shipped   | south  | 2024-04-01 10:00:00

-- customers テーブル(顧客)
-- id | name   | email                | rank
--  1 | 田中   | tanaka@example.com   | gold
--  2 | 鈴木   | suzuki@example.com   | silver
--  3 | 高橋   | takahashi@example.com| gold
--  4 | 伊藤   | ito@example.com      | bronze
スポンサーリンク

INSERT INTO … SELECT の基本構文

INSERT INTO ... SELECT の基本構文は以下のとおりです。VALUES を使わず SELECT 結果をそのまま挿入します。

基本構文
-- 基本構文
INSERT INTO 挿入先テーブル (列1, 列2, ...)
SELECT 列A, 列B, ...
FROM   取得元テーブル
WHERE  条件;

-- ポイント:
-- ・INSERT の列数と SELECT の列数を一致させる
-- ・データ型も一致させる(自動変換されることもあるが明示が安全)
-- ・WHERE を省略すると全行が挿入される

-- 例: shipped 注文を shipped_orders テーブルにコピー
CREATE TABLE shipped_orders LIKE orders;  -- MySQL: 同じ構造のテーブルを作成

INSERT INTO shipped_orders (id, customer_id, product_id, amount, status, region, ordered_at)
SELECT                      id, customer_id, product_id, amount, status, region, ordered_at
FROM orders
WHERE status = 'shipped';
-- → id: 1, 3, 6 の 3 行が挿入される
項目 INSERT INTO … VALUES INSERT INTO … SELECT
用途 固定値を 1〜数行挿入 既存データを条件で絞り込んで挿入
挿入行数 1 行〜複数行(VALUES リスト) SELECT 結果件数(0 行〜大量)
値の変換 固定値のみ CASE WHEN・関数・計算式で加工可能
テーブル複製 不可 可(全件 SELECT で全行コピー)
典型的な用途 新規データの追加 データ移行・アーカイブ・集計結果保存

WHERE 条件で絞り込んで挿入する

WHERE 句を使って挿入対象を絞り込むのが最も一般的なパターンです。複数条件の組み合わせも同様に使えます。

WHERE で条件絞り込み
-- 単一条件: キャンセル注文をアーカイブテーブルに移す
INSERT INTO orders_archive (id, customer_id, amount, status, ordered_at, archived_at)
SELECT                      id, customer_id, amount, status, ordered_at, NOW()
FROM orders
WHERE status = 'canceled';
-- archived_at 列に NOW() を挿入(SELECT 元にない値を付加できる)

-- 複数条件(AND / OR)
INSERT INTO high_value_shipped
SELECT *
FROM orders
WHERE status = 'shipped'
  AND amount >= 10000;
-- shipped かつ 10000 以上の注文だけを挿入

-- 日付範囲で絞り込み
INSERT INTO orders_2024q1
SELECT *
FROM orders
WHERE ordered_at >= '2024-01-01'
  AND ordered_at <  '2024-04-01';

-- IN での複数値絞り込み
INSERT INTO priority_orders
SELECT *
FROM orders
WHERE region IN ('east', 'west')
  AND status IN ('pending', 'shipped');

-- NULL チェック
INSERT INTO orders_no_customer
SELECT *
FROM orders
WHERE customer_id IS NULL;

JOIN で複数テーブルからデータを取り出して挿入する

SELECT 部分で JOIN を使うことで、複数テーブルの情報を結合した結果を挿入できます。正規化されたデータを非正規化して挿入する(データウェアハウス的な使い方)に有効です。

JOIN で複数テーブルを結合して挿入
-- 注文情報と顧客情報を結合して注文サマリーテーブルに挿入
-- 挿入先テーブル構造:
-- order_summary (order_id, customer_name, customer_email, customer_rank, amount, region, ordered_at)
INSERT INTO order_summary (order_id, customer_name, customer_email, customer_rank, amount, region, ordered_at)
SELECT
    o.id,
    c.name,
    c.email,
    c.rank,
    o.amount,
    o.region,
    o.ordered_at
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.status = 'shipped';
-- JOIN で顧客情報を付加して挿入

-- LEFT JOIN: 顧客情報がない注文も挿入(customer_name は NULL)
INSERT INTO order_summary (order_id, customer_name, amount, region)
SELECT
    o.id,
    c.name,        -- 顧客が見つからない場合は NULL
    o.amount,
    o.region
FROM orders o
LEFT JOIN customers c ON o.customer_id = c.id;

-- サブクエリで集計した結果を挿入
INSERT INTO monthly_summary (year_month, order_count, total_amount)
SELECT
    DATE_FORMAT(ordered_at, '%Y-%m') AS year_month,
    COUNT(*) AS order_count,
    SUM(amount) AS total_amount
FROM orders
WHERE status = 'shipped'
GROUP BY DATE_FORMAT(ordered_at, '%Y-%m');

CASE WHEN・関数で加工・変換しながら挿入する

SELECT 部分で CASE WHEN・文字列関数・数値計算を使ってデータを変換・加工してから挿入できます。ETL(Extract-Transform-Load)処理に有効です。

CASE WHEN で値を変換しながら挿入
-- status を日本語ラベルに変換して挿入
INSERT INTO orders_ja (id, customer_id, amount, status_ja, ordered_at)
SELECT
    id,
    customer_id,
    amount,
    CASE status
        WHEN 'shipped'  THEN '発送済み'
        WHEN 'pending'  THEN '処理中'
        WHEN 'canceled' THEN 'キャンセル'
        ELSE '不明'
    END AS status_ja,
    ordered_at
FROM orders;

-- 金額を区分ラベルに変換して分類テーブルに挿入
INSERT INTO order_categories (order_id, amount, category, tax_amount, tax_rate)
SELECT
    id,
    amount,
    CASE
        WHEN amount >= 50000 THEN 'large'
        WHEN amount >= 10000 THEN 'medium'
        ELSE 'small'
    END AS category,
    ROUND(amount * 0.10) AS tax_amount,   -- 10% 消費税を計算して挿入
    0.10                  AS tax_rate
FROM orders
WHERE status != 'canceled';

-- 文字列・日付関数で加工
INSERT INTO orders_normalized (order_id, region_upper, order_date, order_year, order_month)
SELECT
    id,
    UPPER(region) AS region_upper,      -- 大文字に変換
    DATE(ordered_at) AS order_date,     -- 日付部分のみ
    YEAR(ordered_at) AS order_year,
    MONTH(ordered_at) AS order_month
FROM orders;

異なる列構造のテーブルへのマッピング

INSERT 先と SELECT 元で列名・列数・順序が違う場合でも、INSERT の列リストと SELECT の列を対応させることで柔軟にマッピングできます。

列名・順序・数が異なるテーブルへの挿入
-- orders(7列)→ orders_lite(4列)に絞り込んで挿入
-- orders_lite (order_id, total, region, created_at)
INSERT INTO orders_lite (order_id, total, region, created_at)
SELECT        id,       amount, region, ordered_at  -- 列名も順序も違うが対応させる
FROM orders
WHERE status = 'shipped';

-- 不要な列は SELECT から省くだけでよい
-- 余分な列は INSERT の列リストに含めなければ DEFAULT 値 or NULL が入る

-- NULL や定数を補完して挿入(SELECT 元にない列を持つテーブルへ)
-- orders_with_meta (id, amount, status, imported_at, import_source)
INSERT INTO orders_with_meta (id, amount, status, imported_at, import_source)
SELECT
    id,
    amount,
    status,
    NOW() AS imported_at,       -- 現在日時を定数として挿入
    'migration_2024' AS import_source  -- 文字列定数を挿入
FROM orders;

-- NULL を意図的に補完する列
INSERT INTO orders_extended (id, amount, note)
SELECT
    id,
    amount,
    NULL AS note    -- note は後から入力予定なので NULL で挿入
FROM orders
WHERE status = 'pending';
列マッピングのルール:

  • INSERT の列リストと SELECT の式は左から順に対応する
  • INSERT の列リストを省略すると全列をテーブル定義の順に一致させる必要がある(危険)
  • 列リストを明示することが強く推奨——テーブル定義が変わっても安全
  • SELECT 元にない列は NULL や定数・NOW() などを補完できる

同じテーブルへの挿入(自己テーブル・行のコピー)

INSERT 先と SELECT 元が同じテーブルでも機能します。「特定の行をコピーして別の値で追加する」用途に使います。

同一テーブル内での行コピー
-- 特定の注文をコピーして別の顧客向けに複製(id は AUTO_INCREMENT で自動採番)
INSERT INTO orders (customer_id, product_id, amount, status, region, ordered_at)
SELECT
    5 AS customer_id,      -- 新しい顧客 ID
    product_id,
    amount,
    'pending' AS status,   -- 新規注文としてステータスをリセット
    region,
    NOW() AS ordered_at    -- 現在日時を設定
FROM orders
WHERE id = 1;              -- id=1 の行を元に複製

-- 商品の期間限定キャンペーン価格をコピー
-- (割引セールの設定を次月にも引き継ぐ場合など)
INSERT INTO price_rules (product_id, discount_rate, valid_from, valid_to)
SELECT
    product_id,
    discount_rate,
    DATE_ADD(valid_to, INTERVAL 1 DAY) AS valid_from,   -- 終了翌日から開始
    DATE_ADD(valid_to, INTERVAL 31 DAY) AS valid_to     -- 1ヶ月延長
FROM price_rules
WHERE valid_to = CURDATE() - INTERVAL 1 DAY;  -- 昨日終了したルール
自己テーブルへの挿入で PRIMARY KEY 重複に注意:
INSERT 先と SELECT 元が同じテーブルで、id 列を SELECT に含めるとPRIMARY KEY の重複エラーになります。AUTO_INCREMENT の列は INSERT の列リストから除外するか、NULL を渡せば自動採番されます。

CREATE TABLE AS SELECT(CTAS)との違い

SELECT 結果を別テーブルにコピーする方法として、INSERT INTO ... SELECT のほかに CREATE TABLE AS SELECT(CTAS)があります。用途に応じて使い分けましょう。

項目 INSERT INTO … SELECT CREATE TABLE AS SELECT(CTAS)
前提 挿入先テーブルが事前に存在する必要あり テーブルを新規作成しながら挿入
列定義 既存テーブルの定義に従う SELECT の結果から自動的に列定義を生成
制約 既存テーブルの制約(PK・FK・NOT NULL)が有効 制約はコピーされない(PK・インデックスなし)
追加挿入 既存データに追記できる 新規テーブルへの初回作成のみ
用途 継続的なデータ蓄積・アーカイブ・移行 一時テーブル・バックアップの作成
CTAS(CREATE TABLE AS SELECT)の構文
-- ===== MySQL =====
CREATE TABLE orders_backup AS
SELECT * FROM orders
WHERE ordered_at >= '2024-01-01';
-- テーブルを新規作成してデータを挿入(構造はSELECT結果から自動生成)
-- ただし PRIMARY KEY・インデックスはコピーされない

-- MySQL: 空のテーブルを複製(データなし)
CREATE TABLE orders_empty LIKE orders;
-- LIKE は制約・インデックスもコピー(CTAS はコピーしない)

-- ===== PostgreSQL =====
CREATE TABLE orders_backup AS
SELECT * FROM orders WHERE ordered_at >= '2024-01-01';

-- 空テーブルの複製
CREATE TABLE orders_empty AS SELECT * FROM orders WHERE false;

-- ===== SQL Server =====
-- SQL Server: SELECT INTO で同等の操作
SELECT * INTO orders_backup
FROM orders
WHERE ordered_at >= '2024-01-01';

-- ===== 用途の使い分け =====
-- CTAS / SELECT INTO: テーブルが存在しない場合の初回作成・バックアップ
-- INSERT INTO ... SELECT: すでに存在するテーブルへの追記・蓄積

実務でよく使うINSERT INTO…SELECTパターン集

パターン①:古いデータのアーカイブ(DELETE と組み合わせ)
-- 3ヶ月以上前のキャンセル注文をアーカイブに移す
BEGIN;

-- ① アーカイブテーブルに挿入
INSERT INTO orders_archive (id, customer_id, amount, status, ordered_at, archived_at)
SELECT                      id, customer_id, amount, status, ordered_at, NOW()
FROM orders
WHERE status = 'canceled'
  AND ordered_at < DATE_SUB(NOW(), INTERVAL 3 MONTH);

-- ② 元テーブルから削除(同じ WHERE 条件を使う)
DELETE FROM orders
WHERE status = 'canceled'
  AND ordered_at < DATE_SUB(NOW(), INTERVAL 3 MONTH);

COMMIT;
-- トランザクションで一括処理: 両方成功するかゼロかを保証

-- 移行件数の確認
SELECT ROW_COUNT();  -- DELETE の影響行数
パターン②:集計結果をサマリーテーブルに保存
-- 月次サマリーテーブルに今月分の集計を保存
-- monthly_sales (year_month, shop_id, order_count, total_amount, avg_amount, updated_at)

-- 既存の今月分を削除してから最新データを挿入(upsert 的な操作)
DELETE FROM monthly_sales
WHERE year_month = DATE_FORMAT(NOW(), '%Y-%m');

INSERT INTO monthly_sales (year_month, region, order_count, total_amount, avg_amount, updated_at)
SELECT
    DATE_FORMAT(ordered_at, '%Y-%m') AS year_month,
    region,
    COUNT(*) AS order_count,
    SUM(amount) AS total_amount,
    ROUND(AVG(amount), 0) AS avg_amount,
    NOW() AS updated_at
FROM orders
WHERE status = 'shipped'
  AND ordered_at >= DATE_FORMAT(NOW(), '%Y-%m-01')
  AND ordered_at <  DATE_FORMAT(DATE_ADD(NOW(), INTERVAL 1 MONTH), '%Y-%m-01')
GROUP BY DATE_FORMAT(ordered_at, '%Y-%m'), region;
パターン③:テーブルのバックアップ(定期取得)
-- 実行前に今日付きのバックアップテーブルを作成(毎日実行するバッチなど)
-- MySQL: 日付付きのバックアップテーブルを作成してコピー

-- ① テーブルを複製(構造コピー)
CREATE TABLE orders_backup_20240408 LIKE orders;

-- ② 全データをコピー
INSERT INTO orders_backup_20240408
SELECT * FROM orders;

-- または CTAS で一発
CREATE TABLE orders_backup_20240408 AS SELECT * FROM orders;

-- ③ バックアップの確認
SELECT COUNT(*) FROM orders_backup_20240408;
SELECT COUNT(*) FROM orders;
-- 件数が一致すること
パターン④:本番データを加工してレポート用テーブルに挿入
-- 顧客ランク別・地域別のレポートテーブルに挿入
INSERT INTO report_order_summary (report_date, customer_rank, region, order_count, total_amount)
SELECT
    CURDATE() AS report_date,
    c.rank AS customer_rank,
    o.region,
    COUNT(*) AS order_count,
    SUM(o.amount) AS total_amount
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.status = 'shipped'
  AND o.ordered_at >= DATE_FORMAT(NOW() - INTERVAL 1 MONTH, '%Y-%m-01')
  AND o.ordered_at <  DATE_FORMAT(NOW(), '%Y-%m-01')
GROUP BY c.rank, o.region;
-- 前月のデータを集計してレポートテーブルに毎月1日に実行するバッチ
パターン⑤:重複を除外して挿入(INSERT IGNORE / NOT EXISTS)
-- ===== MySQL: INSERT IGNORE(重複はスキップ)=====
INSERT IGNORE INTO shipped_orders (id, customer_id, amount, status, ordered_at)
SELECT                             id, customer_id, amount, status, ordered_at
FROM orders
WHERE status = 'shipped';
-- id が PRIMARY KEY の場合、既に存在する id はスキップされる

-- ===== NOT EXISTS を使った条件付き挿入 =====
-- アーカイブテーブルに未登録のものだけ挿入
INSERT INTO orders_archive (id, customer_id, amount, status, ordered_at, archived_at)
SELECT o.id, o.customer_id, o.amount, o.status, o.ordered_at, NOW()
FROM orders o
WHERE o.status = 'canceled'
  AND NOT EXISTS (
      SELECT 1 FROM orders_archive a WHERE a.id = o.id
  );

-- ===== INSERT INTO ... SELECT ON DUPLICATE KEY UPDATE(MySQL: UPSERT)=====
INSERT INTO monthly_sales (year_month, total_amount, updated_at)
SELECT
    DATE_FORMAT(ordered_at, '%Y-%m'),
    SUM(amount),
    NOW()
FROM orders
WHERE status = 'shipped'
GROUP BY DATE_FORMAT(ordered_at, '%Y-%m')
ON DUPLICATE KEY UPDATE
    total_amount = VALUES(total_amount),
    updated_at   = VALUES(updated_at);
-- year_month が PRIMARY KEY の場合、既存行は UPDATE、新規は INSERT

大量データ挿入時のパフォーマンス注意点

INSERT INTO ... SELECT で大量の行を挿入する場合、パフォーマンスとロックに注意が必要です。

大量データ挿入の最適化
-- ===== 1. バッチ分割で少量ずつ挿入(大量データ向け)=====
-- 一度に全件挿入するとトランザクションログが大量生成・テーブルロックが長時間
-- → N 件ずつ分割して繰り返す(アプリ側でループ処理)

-- 例: 1000件ずつ分割(MySQL)
INSERT INTO orders_archive (id, customer_id, amount, status, ordered_at, archived_at)
SELECT id, customer_id, amount, status, ordered_at, NOW()
FROM orders
WHERE status = 'canceled'
  AND ordered_at < DATE_SUB(NOW(), INTERVAL 3 MONTH)
ORDER BY id
LIMIT 1000;   -- 1000 件ずつ繰り返す

-- ===== 2. SELECT 部分のインデックス活用 =====
-- WHERE の列にインデックスがあること
CREATE INDEX idx_orders_status_ordered ON orders (status, ordered_at);

-- EXPLAIN で実行計画を確認
EXPLAIN
SELECT * FROM orders
WHERE status = 'canceled' AND ordered_at < '2024-01-01';
-- type: range でインデックスが使われているか確認

-- ===== 3. インデックスの一時無効化(大量一括挿入向け)=====
-- MySQL: 挿入前にインデックスを無効化(挿入後に再構築)
ALTER TABLE orders_archive DISABLE KEYS;

INSERT INTO orders_archive ...
SELECT ... FROM orders WHERE ...;

ALTER TABLE orders_archive ENABLE KEYS;
-- ※ MyISAM テーブル向け。InnoDB では innodb_autoinc_lock_mode の設定で制御

-- ===== 4. PostgreSQL: COPY コマンドが最速 =====
-- INSERT より COPY の方が大量データ挿入が速い
-- (外部ファイルや stdin から読み込む場合は COPY が有効)
-- INSERT INTO ... SELECT は COPY より遅いが、テーブル間コピーには有効
データ量 推奨対処
〜 1 万行 通常の INSERT INTO ... SELECT で問題なし
1 万〜 100 万行 トランザクション内で一括実行。EXPLAIN でインデックス確認
100 万行〜 N 件ずつバッチ分割・LIMIT で繰り返す。インデックス一時無効化を検討
テーブル全件コピー CTAS(CREATE TABLE AS SELECT)が一般的に速い

RDBMS 別の INSERT INTO … SELECT 構文差異

RDBMS 別の構文と注意点
-- ===== MySQL =====
-- 基本構文(標準的)
INSERT INTO 挿入先 (col1, col2) SELECT val1, val2 FROM 元 WHERE 条件;

-- AUTO_INCREMENT の扱い: 列リストから除外すると自動採番
INSERT INTO orders_copy (customer_id, amount, status)
SELECT customer_id, amount, status FROM orders;  -- id は自動採番

-- ===== PostgreSQL =====
-- 基本的に同じ構文
INSERT INTO 挿入先 (col1, col2) SELECT val1, val2 FROM 元 WHERE 条件;

-- RETURNING で挿入された行の値を取得
INSERT INTO orders_copy (customer_id, amount)
SELECT customer_id, amount FROM orders WHERE status = 'shipped'
RETURNING id, customer_id;  -- 挿入した id を返す

-- ===== SQL Server =====
-- 基本構文は同じ
INSERT INTO 挿入先 (col1, col2) SELECT val1, val2 FROM 元 WHERE 条件;

-- SELECT INTO で新規テーブルにコピー(INSERT INTO の代替)
SELECT col1, col2 INTO 新テーブル FROM 元テーブル WHERE 条件;
-- → 新テーブルが存在しない場合に新規作成しながら挿入(CTAS 相当)

-- OUTPUT INSERTED で挿入した行を確認
INSERT INTO orders_copy (customer_id, amount, status)
OUTPUT INSERTED.id, INSERTED.customer_id
SELECT customer_id, amount, status FROM orders WHERE status = 'shipped';

-- ===== Oracle =====
-- 基本構文は同じ
INSERT INTO 挿入先 (col1, col2) SELECT val1, val2 FROM 元 WHERE 条件;

-- INSERT ALL: 複数テーブルへの同時挿入(Oracle 固有)
INSERT ALL
    INTO orders_east (id, amount) VALUES (id, amount)
    INTO orders_summary (region, amount) VALUES (region, amount)
SELECT id, amount, region
FROM orders
WHERE status = 'shipped';

よくある質問(FAQ)

QINSERT INTO … SELECT で挿入された件数を確認するにはどうすればよいですか?
AMySQL では ROW_COUNT() 関数で直前の INSERT の影響行数を確認できます。PostgreSQL では GET DIAGNOSTICS row_count = ROW_COUNT(PL/pgSQL内)またはRETURNING 句で挿入行を取得できます。SQL Server では @@ROWCOUNT を使います。また INSERT 前後で SELECT COUNT(*) FROM 挿入先 を実行して件数差を確認する方法も簡単です。
QINSERT INTO … SELECT に ORDER BY は必要ですか?
A挿入順序を保証したい場合は ORDER BY を使えますが、テーブルは集合であり「行の順序」を持たないため、多くの場合 ORDER BY は不要です。ただし LIMIT と組み合わせて「上位 N 件だけ挿入する」場合はORDER BY ... LIMIT N のように使います。
QINSERT と SELECT で同じテーブルを使えますか?
Aはい、同一テーブルでも動作します。MySQL では内部的に SELECT 結果を一時バッファに格納してから INSERT するため安全です。ただし PRIMARY KEY の重複に注意してください。AUTO_INCREMENT の列は INSERT の列リストから除外するか NULL を渡して自動採番させましょう。
QINSERT INTO … SELECT でトランザクションは必要ですか?
A小規模なコピーなら必ずしも必要ではありませんが、「アーカイブ挿入 → 元テーブルから DELETE」のように2つの操作をセットで行う場合はトランザクションが必須です。INSERT 後に DELETE が失敗すると同じデータが両テーブルに存在してしまいます。BEGIN; INSERT ...; DELETE ...; COMMIT; の形で両方の操作を一括でコミットしましょう。
QINSERT INTO … SELECT と CTAS(CREATE TABLE AS SELECT)はどちらを使うべきですか?
A挿入先テーブルがまだ存在しない場合(バックアップの初回作成・一時テーブル作成)は CTAS / SELECT INTO が便利です。挿入先テーブルがすでに存在し、データを追記したい場合(定期バッチ・アーカイブ蓄積・集計結果の更新)はINSERT INTO … SELECT を使います。CTAS はインデックスや制約がコピーされない点に注意してください。

まとめ

INSERT INTO ... SELECT の活用パターンをまとめます。

用途 キーポイント
WHERE で絞り込んで挿入 条件に合致した行だけを別テーブルへ。AND/OR/IN/IS NULL 等を組み合わせる
JOIN で複数テーブルを結合 正規化データを非正規化して挿入。LEFT JOIN で JOIN 失敗行も含められる
CASE WHEN で加工して挿入 ETL 処理。値の変換・ラベル付け・計算値の付加
列マッピング 列リストを明示して INSERT/SELECT を対応付ける。定数や NOW() も補完できる
同テーブルへの行コピー AUTO_INCREMENT 列は列リストから除外して自動採番
CTAS との使い分け 既存テーブルへの追記は INSERT SELECT / 新規テーブル作成は CTAS
アーカイブ・DELETE 必ずトランザクションで INSERT と DELETE をセットにする
大量データ LIMIT でバッチ分割・インデックス確認・トランザクションログ肥大化に注意

INSERT 文全般の基本構文・UPSERT・複数行挿入についてはINSERT文の使い方完全ガイドを参照してください。