2.

SQL INSERT文の書き方|複数行挿入・SELECT挿入・UPSERT

編集
この記事の要点
  • INSERT 文は DML (Data Manipulation Language) の代表で、テーブルに新しい行を追加する命令
  • 基本形は INSERT INTO テーブル (列…) VALUES (値…)。複数行同時挿入は VALUES (…), (…), (…)
  • SELECT 結果をそのまま挿入: INSERT INTO t (col) SELECT col FROM src
  • Upsert(既存なら更新、無ければ挿入): MySQL ON DUPLICATE KEY UPDATE / PostgreSQL ON CONFLICT DO UPDATE / SQL 標準 MERGE
  • 主キーを省略すると AUTO_INCREMENT / SEQUENCE が採番。RETURNING(PostgreSQL/Oracle/MariaDB)で採番値を即取得

INSERT 文の基本

INSERT 文は SQL の DML (Data Manipulation Language) のうち最も基本的な命令で、テーブルに新しい行を追加します。データの登録に使う中核 SQL であり、ほぼすべてのアプリケーションで使われます。

-- 列名を明示(推奨)
INSERT INTO users (id, name, email)
VALUES (1, 'Alice', 'alice@example.com');

-- 列名省略(全列に値を指定、テーブル定義順)
INSERT INTO users VALUES (1, 'Alice', 'alice@example.com', NOW());

-- DEFAULT で既定値を使う
INSERT INTO users (id, name, created_at)
VALUES (2, 'Bob', DEFAULT);

列名は明示するのが鉄則です。テーブルに列が追加されても既存 INSERT が壊れないため、保守性が大きく向上します。

複数行 INSERT(マルチバリュー)

1 つの INSERT で複数行を挿入できます。1 行ずつ INSERT するより桁違いに高速:

INSERT INTO users (id, name, email) VALUES
    (1, 'Alice', 'alice@example.com'),
    (2, 'Bob',   'bob@example.com'),
    (3, 'Carol', 'carol@example.com'),
    (4, 'Dave',  'dave@example.com');

-- 大量データの場合は 500〜1000 行ずつに分割
-- (MySQL の max_allowed_packet、PostgreSQL のパラメータ数制限を考慮)

1 万行を 1 行 INSERT × 1 万回するより、1000 行 × 10 回の方が 50〜100 倍高速になります。バルクロードの基本テクニックです。

INSERT ... SELECT

SELECT の結果を別テーブル(または同テーブル)に流し込めます。ETL やテーブル再構成で多用されます:

-- 別テーブルからコピー
INSERT INTO users_archive (id, name, email, created_at)
SELECT id, name, email, created_at
FROM users
WHERE created_at < '2024-01-01';

-- 集計結果を保存
INSERT INTO daily_sales (date, total)
SELECT DATE(created_at), SUM(amount)
FROM orders
WHERE created_at >= '2026-06-01'
GROUP BY DATE(created_at);

-- 同テーブルへの自己コピー(履歴生成など)
INSERT INTO logs (level, message)
SELECT 'ERROR', message FROM logs WHERE level = 'WARN';

Upsert(あれば更新、無ければ挿入)

「主キー or ユニーク列が既に存在するなら更新、無ければ挿入」を 1 文で行う Upsert は、リトライ可能なデータ取り込み処理で必須です。

MySQL: ON DUPLICATE KEY UPDATE

INSERT INTO user_points (user_id, point)
VALUES (1, 100)
ON DUPLICATE KEY UPDATE
    point = point + VALUES(point);   -- 既存にあれば加算

-- MySQL 8.0.20+ は VALUES() より new エイリアスが推奨
INSERT INTO user_points (user_id, point) VALUES (1, 100) AS new
ON DUPLICATE KEY UPDATE point = user_points.point + new.point;

PostgreSQL: ON CONFLICT

INSERT INTO user_points (user_id, point)
VALUES (1, 100)
ON CONFLICT (user_id) DO UPDATE
SET point = user_points.point + EXCLUDED.point;
-- EXCLUDED は INSERT しようとした行を指す疑似テーブル

-- 何もしないバージョン(重複なら無視)
INSERT INTO user_points (user_id, point)
VALUES (1, 100)
ON CONFLICT (user_id) DO NOTHING;

SQL 標準: MERGE 文

-- SQL Server / Oracle / DB2 / PostgreSQL 15+ で利用可能
MERGE INTO user_points AS tgt
USING (SELECT 1 AS user_id, 100 AS point) AS src
   ON tgt.user_id = src.user_id
WHEN MATCHED THEN
    UPDATE SET point = tgt.point + src.point
WHEN NOT MATCHED THEN
    INSERT (user_id, point) VALUES (src.user_id, src.point);

RETURNING で採番された ID を取得

-- PostgreSQL / Oracle / MariaDB 10.5+
INSERT INTO users (name, email)
VALUES ('Alice', 'alice@example.com')
RETURNING id, created_at;

-- MySQL は LAST_INSERT_ID() を別途呼ぶ
INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com');
SELECT LAST_INSERT_ID();

-- SQL Server は OUTPUT
INSERT INTO users (name, email)
OUTPUT inserted.id, inserted.created_at
VALUES ('Alice', 'alice@example.com');

パフォーマンスのコツ

シーン推奨
1 万行以下の単発投入マルチバリュー INSERT を 1000 行ずつ
10 万行以上のバルクロードMySQL LOAD DATA INFILE / PostgreSQL COPY
大量 INSERT 中に INDEX が遅いINDEX を一時 DROP → INSERT → 再 CREATE
外部キー制約が遅いセッション内で一時的に制約 DEFER(PostgreSQL)
同時 INSERT で競合Auto-Commit OFF + バッチでまとめて COMMIT

プレースホルダ(SQL インジェクション対策)

アプリから INSERT する際は必ずプレースホルダを使います。文字列連結は SQL インジェクションの温床:

// ❌ 危険: SQL インジェクション
$pdo->exec("INSERT INTO users (name) VALUES ('$name')");

// ✅ プレースホルダ
$stmt = $pdo->prepare('INSERT INTO users (name, email) VALUES (?, ?)');
$stmt->execute([$name, $email]);

// ✅ Laravel
DB::table('users')->insert(['name' => $name, 'email' => $email]);

// ✅ バルク INSERT
DB::table('users')->insert([
    ['name' => 'Alice', 'email' => 'alice@example.com'],
    ['name' => 'Bob',   'email' => 'bob@example.com'],
]);

よくあるトラブル

症状原因対処
Duplicate entry '...' for key 'PRIMARY'主キー重複ON DUPLICATE KEY / ON CONFLICT 活用
Column count doesn't match value count列数と値数の不一致列名を明示
Field 'xxx' doesn't have a default valueNOT NULL 列に値が無い列に値を入れるか DEFAULT 設定
Data too long for column 'xxx'VARCHAR 長超過列長を見直すか文字列短縮
大量 INSERT が遅い1 行ずつ実行 / インデックス過多マルチバリュー化 / インデックス見直し

FAQ

Q: INSERT IGNORE と INSERT ... ON DUPLICATE KEY UPDATE の違いは?
A: INSERT IGNORE は重複時に黙って何もしない(エラー無視)。ON DUPLICATE KEY UPDATE は重複時に更新を行います。意図に応じて使い分け。

Q: AUTO_INCREMENT の値を予測できる?
A: 予測すべきでありません。LAST_INSERT_ID() / RETURNING / OUTPUT挿入直後に取得するのが正解です。

Q: トランザクション内で INSERT してロールバックすると AUTO_INCREMENT は戻る?
A: 戻りません。MySQL InnoDB の場合、AUTO_INCREMENT カウンタは進んだまま。歯抜けの ID が発生するのは仕様です。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. SELECT文
  2. INSERT
  3. UPDATE文
  4. DELETE文
  5. WHERE句
  6. JOIN句
  7. 集合演算子
  8. インラインビュー
  9. 副問い合わせ (サブクエリ)

最近更新/作成されたページ