【SQL】INSERT文の使い方|複数行挿入・SELECT挿入・UPSERT・高速化まで完全解説

【SQL】INSERT文の使い方|複数行挿入・SELECT挿入・UPSERT・高速化まで完全解説 SQL

SQLのINSERT文は、テーブルにデータを登録するための基本構文です。単純な1行挿入から、複数行の一括挿入、別テーブルからのデータコピー、存在チェック付きのUPSERT、大量データの高速投入まで、INSERT文には多くのバリエーションがあります。

この記事では、INSERT文の基本構文からRDBMS別の違いパフォーマンス最適化よくあるミスまで、実務で必要な知識を体系的に解説します。

この記事で学べること

  • INSERT文の基本構文(カラム指定あり/なし、値の型)
  • 複数行を一度に挿入する方法(MySQL / PostgreSQL / SQL Server / Oracle)
  • INSERT INTO … SELECT で別テーブルからデータをコピーする方法
  • UPSERT(存在すれば更新、なければ挿入)のRDBMS別構文
  • デフォルト値・AUTO INCREMENT・RETURNING句の活用
  • バルクINSERTの高速化テクニック
  • よくあるミスと注意点(制約違反・型不一致・エスケープ)
スポンサーリンク

INSERT文の基本構文

INSERT文は、テーブルに新しい行を追加するSQL文です。基本形は以下のとおりです。

カラム指定ありの構文

挿入するカラムを明示的に指定するパターンです。実務ではこの書き方が推奨されます。

カラム指定ありのINSERT
INSERT INTO テーブル名 (カラム1, カラム2, カラム3)
VALUES (値1, 値2, 値3);

カラムの順序は、テーブル定義の順序と一致する必要はありません。VALUES句の値がカラムリストの順序に対応していれば正しく動作します。

カラム指定なしの構文

カラムを省略する場合は、テーブルの全カラムに対して定義順に値を指定する必要があります。

カラム省略のINSERT
INSERT INTO テーブル名
VALUES (値1, 値2, 値3, ...);

注意

カラム省略型は、テーブルにカラムが追加された場合にエラーになるリスクがあります。保守性を考慮して、カラムを明示する書き方を推奨します。

値の型と書き方

INSERT文で指定する値は、カラムのデータ型に合わせて正しく記述する必要があります。

データ型 書き方
文字列 シングルクォートで囲む '田中太郎'
数値 そのまま記述 1003.14
日付 シングルクォートで囲む '2024-01-15'
NULL クォートなしで NULL と記述 NULL
真偽値 TRUE / FALSE(RDBMSによる) TRUE1

具体例:社員テーブルへの挿入

以下のような employees テーブルに1行挿入してみます。

テーブル定義
CREATE TABLE employees (
    id         INT PRIMARY KEY,
    name       VARCHAR(50) NOT NULL,
    department VARCHAR(30),
    salary     INT,
    hire_date  DATE
);
カラム指定ありのINSERT
INSERT INTO employees (id, name, department, salary, hire_date)
VALUES (1, '田中太郎', '営業', 350000, '2020-04-01');

実行結果: 1 row inserted.

カラム指定なしの場合は、テーブル定義順(id, name, department, salary, hire_date)に値を指定します。

カラム省略のINSERT(同じ結果)
INSERT INTO employees
VALUES (1, '田中太郎', '営業', 350000, '2020-04-01');

NULLを挿入する

カラムにNULLを設定したい場合は、値にNULLとそのまま記述します。ただし、NOT NULL制約が設定されたカラムにはNULLを挿入できません。

NULLの挿入
INSERT INTO employees (id, name, department, salary, hire_date)
VALUES (2, '佐藤花子', '開発', NULL, '2021-10-01');

この例では salary がNULLになります。カラムリストから省略してもNULLが設定されます(DEFAULT値がなければ)。

複数行を一度に挿入する

1回のINSERT文で複数行を挿入する方法は、RDBMSによって構文が異なります。

VALUES句の複数指定(MySQL / PostgreSQL / SQL Server)

MySQL・PostgreSQL・SQL Server(2008以降)では、VALUES句にカンマ区切りで複数行を指定できます。

複数行INSERT(MySQL / PostgreSQL / SQL Server)
INSERT INTO employees (id, name, department, salary, hire_date)
VALUES
    (1, '田中太郎', '営業',   350000, '2020-04-01'),
    (2, '佐藤花子', '開発',   420000, '2021-10-01'),
    (3, '鈴木一郎', '総務',   300000, '2022-01-15'),
    (4, '高橋美咲', '開発',   450000, '2019-07-01'),
    (5, '伊藤健太', '営業',   380000, '2023-04-01');

実行結果: 5 rows inserted.

1行ずつINSERT文を書くよりもパフォーマンスが良く、コードも簡潔になります。

INSERT ALL(Oracle)

Oracleでは複数行VALUES句が使えないため、INSERT ALL を使用します。

複数行INSERT(Oracle)
INSERT ALL
    INTO employees (id, name, department, salary, hire_date)
         VALUES (1, '田中太郎', '営業', 350000, DATE '2020-04-01')
    INTO employees (id, name, department, salary, hire_date)
         VALUES (2, '佐藤花子', '開発', 420000, DATE '2021-10-01')
    INTO employees (id, name, department, salary, hire_date)
         VALUES (3, '鈴木一郎', '総務', 300000, DATE '2022-01-15')
SELECT * FROM DUAL;

ポイント

INSERT ALL の末尾には SELECT * FROM DUAL が必要です。これはOracleの仕様で、INSERT ALL 構文が SELECT文を要求するためです。DUALはOracleのダミーテーブルです。

RDBMS別 複数行INSERT比較表

RDBMS 複数行INSERTの構文 1回の最大行数
MySQL VALUES句 カンマ区切り max_allowed_packet に依存
PostgreSQL VALUES句 カンマ区切り 制限なし(メモリ依存)
SQL Server VALUES句 カンマ区切り 1,000行(1文あたり)
Oracle INSERT ALL … SELECT * FROM DUAL 制限なし(メモリ依存)

SELECT結果をINSERTする(INSERT INTO … SELECT)

別のテーブルやクエリの結果を使ってデータを挿入する場合は、INSERT INTO … SELECT 構文を使います。すべての主要RDBMSで利用可能です。

基本構文

INSERT INTO … SELECT
INSERT INTO 挿入先テーブル (カラム1, カラム2, カラム3)
SELECT カラムA, カラムB, カラムC
FROM 参照元テーブル;

INSERT先のカラム数・データ型と、SELECTの列数・データ型が一致している必要があります。

別テーブルからのデータコピー

たとえば、employees テーブルから営業部のデータだけを sales_staff テーブルにコピーするケースです。

営業部のデータを別テーブルにコピー
INSERT INTO sales_staff (id, name, salary)
SELECT id, name, salary
FROM employees
WHERE department = '営業';

実行結果: 2 rows inserted.(田中太郎、伊藤健太)

条件付きコピー(WHERE句)

WHERE句に複雑な条件を指定して、必要なデータだけを挿入できます。

条件付きコピー
INSERT INTO high_salary_employees (id, name, department, salary)
SELECT id, name, department, salary
FROM employees
WHERE salary >= 400000
  AND hire_date >= '2020-01-01';

集計結果をINSERTする

SELECT文にGROUP BYや集計関数を使い、集計結果をテーブルに保存することもできます。

部署別の平均給与をサマリテーブルに保存
INSERT INTO dept_salary_summary (department, avg_salary, emp_count)
SELECT department, AVG(salary), COUNT(*)
FROM employees
GROUP BY department;

テーブル構造ごとコピー(CREATE TABLE AS SELECT)

テーブル自体が存在しない場合、テーブルの作成とデータの挿入を同時に行えます。

RDBMS 構文
MySQL / PostgreSQL CREATE TABLE 新テーブル AS SELECT ... FROM 元テーブル
Oracle CREATE TABLE 新テーブル AS SELECT ... FROM 元テーブル
SQL Server SELECT ... INTO 新テーブル FROM 元テーブル
MySQL / PostgreSQL / Oracle
CREATE TABLE employees_backup AS
SELECT * FROM employees;
SQL Server
SELECT *
INTO employees_backup
FROM employees;

注意

CREATE TABLE AS SELECT では、テーブル構造(カラム名・データ型)はコピーされますが、PRIMARY KEY・INDEX・制約などはコピーされません。必要に応じて後からALTER TABLEで追加してください。

UPSERT(存在すれば更新、なければ挿入)

UPSERT(アップサート)とは、「レコードが存在すればUPDATE、存在しなければINSERT」を1つの文で実現する操作です。データの同期やマスタ更新で頻繁に使われます。

UPSERTが必要な場面

  • 外部システムからのデータ取り込み(存在すれば最新値で更新)
  • 設定テーブルの初期化(なければデフォルト値で挿入)
  • 在庫テーブルの更新(あれば数量を加算、なければ新規登録)

MySQL: INSERT … ON DUPLICATE KEY UPDATE

MySQLでは、PRIMARY KEY または UNIQUE KEY が重複した場合に UPDATE を実行します。

MySQL – ON DUPLICATE KEY UPDATE
INSERT INTO employees (id, name, department, salary, hire_date)
VALUES (1, '田中太郎', '企画', 380000, '2020-04-01')
ON DUPLICATE KEY UPDATE
    name       = VALUES(name),
    department = VALUES(department),
    salary     = VALUES(salary);

id=1 が既に存在する場合は name, department, salary が更新されます。存在しない場合は通常のINSERTとして挿入されます。

MySQL 8.0.20以降の推奨構文

VALUES(col) は非推奨となり、エイリアス構文が推奨されています。

MySQL 8.0.20以降 – エイリアス構文
INSERT INTO employees (id, name, department, salary, hire_date)
VALUES (1, '田中太郎', '企画', 380000, '2020-04-01')
    AS new_val
ON DUPLICATE KEY UPDATE
    name       = new_val.name,
    department = new_val.department,
    salary     = new_val.salary;

PostgreSQL: INSERT … ON CONFLICT DO UPDATE

PostgreSQLでは ON CONFLICT 句を使います。競合対象のカラム(通常はPRIMARY KEYまたはUNIQUE制約)を明示的に指定します。

PostgreSQL – ON CONFLICT DO UPDATE
INSERT INTO employees (id, name, department, salary, hire_date)
VALUES (1, '田中太郎', '企画', 380000, '2020-04-01')
ON CONFLICT (id) DO UPDATE SET
    name       = EXCLUDED.name,
    department = EXCLUDED.department,
    salary     = EXCLUDED.salary;

EXCLUDED は挿入しようとした行を参照する特殊なキーワードです。競合時に何もしない場合は ON CONFLICT DO NOTHING と書きます。

競合時にスキップ(PostgreSQL)
INSERT INTO employees (id, name, department, salary, hire_date)
VALUES (1, '田中太郎', '企画', 380000, '2020-04-01')
ON CONFLICT (id) DO NOTHING;

Oracle: MERGE INTO

OracleではMERGE文を使います。JOIN条件でマッチした行はUPDATE、マッチしない行はINSERTを実行します。

Oracle – MERGE INTO
MERGE INTO employees tgt
USING (SELECT 1 AS id, '田中太郎' AS name,
              '企画' AS department, 380000 AS salary,
              DATE '2020-04-01' AS hire_date
       FROM DUAL) src
ON (tgt.id = src.id)
WHEN MATCHED THEN UPDATE SET
    tgt.name       = src.name,
    tgt.department = src.department,
    tgt.salary     = src.salary
WHEN NOT MATCHED THEN INSERT
    (id, name, department, salary, hire_date)
    VALUES (src.id, src.name, src.department, src.salary, src.hire_date);

SQL Server: MERGE

SQL ServerもMERGE構文を使用します。Oracleとほぼ同じ書き方ですが、末尾にセミコロンが必須です。

SQL Server – MERGE
MERGE INTO employees AS tgt
USING (VALUES
    (1, '田中太郎', '企画', 380000, '2020-04-01')
) AS src (id, name, department, salary, hire_date)
ON tgt.id = src.id
WHEN MATCHED THEN UPDATE SET
    tgt.name       = src.name,
    tgt.department = src.department,
    tgt.salary     = src.salary
WHEN NOT MATCHED THEN INSERT
    (id, name, department, salary, hire_date)
    VALUES (src.id, src.name, src.department, src.salary, src.hire_date);

UPSERT構文 RDBMS別比較表

RDBMS UPSERT構文 競合キーの指定 挿入行の参照
MySQL ON DUPLICATE KEY UPDATE 自動(PK/UNIQUE) VALUES(col) / エイリアス
PostgreSQL ON CONFLICT DO UPDATE 明示指定(カラム名) EXCLUDED.col
Oracle MERGE INTO ... USING ON句で指定 src.col
SQL Server MERGE INTO ... USING ON句で指定 src.col

デフォルト値とAUTO INCREMENT

INSERT時にすべてのカラムに値を指定しなくても、DEFAULT値自動採番(AUTO INCREMENT)を活用することで、効率的にデータを登録できます。

DEFAULT値の活用

テーブル定義時に DEFAULT を指定しておくと、INSERTでそのカラムを省略した場合にデフォルト値が自動的に設定されます。

DEFAULT値付きテーブルの定義
CREATE TABLE products (
    id         INT PRIMARY KEY,
    name       VARCHAR(100) NOT NULL,
    price      INT DEFAULT 0,
    status     VARCHAR(20) DEFAULT 'active',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
price, status, created_at を省略してINSERT
INSERT INTO products (id, name)
VALUES (1, 'テスト商品');

この場合、price は 0、status は 'active'、created_at は現在日時が自動的に設定されます。明示的にDEFAULTキーワードを使うこともできます。

DEFAULTキーワードを明示的に使用
INSERT INTO products (id, name, price, status, created_at)
VALUES (2, '商品B', DEFAULT, DEFAULT, DEFAULT);

AUTO INCREMENT / SERIAL / IDENTITY / SEQUENCE

主キーを自動で連番にする仕組みは、RDBMSごとに異なります。

RDBMS 自動採番の仕組み テーブル定義例
MySQL AUTO_INCREMENT id INT AUTO_INCREMENT PRIMARY KEY
PostgreSQL SERIAL / GENERATED ALWAYS AS IDENTITY id SERIAL PRIMARY KEY
Oracle SEQUENCE + トリガー / IDENTITY(12c以降) id NUMBER GENERATED ALWAYS AS IDENTITY
SQL Server IDENTITY id INT IDENTITY(1,1) PRIMARY KEY

自動採番カラムはINSERT時に省略できます(省略すると自動で次の値が割り当てられます)。

MySQL – AUTO_INCREMENT
CREATE TABLE orders (
    id         INT AUTO_INCREMENT PRIMARY KEY,
    product    VARCHAR(100),
    quantity   INT,
    order_date DATE
);

-- id を省略して INSERT
INSERT INTO orders (product, quantity, order_date)
VALUES ('ノートPC', 2, '2024-03-15');
-- id = 1 が自動採番される

INSERT INTO orders (product, quantity, order_date)
VALUES ('マウス', 5, '2024-03-16');
-- id = 2 が自動採番される

RETURNING句(PostgreSQL / Oracle)

INSERT後に、挿入されたデータ(特に自動採番されたIDなど)を取得したい場合は RETURNING句 を使います。

PostgreSQL – RETURNING
INSERT INTO orders (product, quantity, order_date)
VALUES ('ノートPC', 2, '2024-03-15')
RETURNING id, product;

実行結果:

 id | product
----+---------
  1 | ノートPC
Oracle 12c以降 – RETURNING INTO
DECLARE
    v_id NUMBER;
BEGIN
    INSERT INTO orders (product, quantity, order_date)
    VALUES ('ノートPC', 2, DATE '2024-03-15')
    RETURNING id INTO v_id;

    DBMS_OUTPUT.PUT_LINE('挿入されたID: ' || v_id);
END;
RDBMS INSERT後のID取得方法
MySQL LAST_INSERT_ID()
PostgreSQL RETURNING id
Oracle RETURNING id INTO 変数
SQL Server SCOPE_IDENTITY() / OUTPUT INSERTED.id

パフォーマンスの考慮

大量データをINSERTする場合、何も考えずに1行ずつ挿入するとパフォーマンスが大幅に低下します。ここでは高速化のテクニックを紹介します。

バルクINSERTの高速化

1行ずつINSERTする場合と、複数行を一括でINSERTする場合のパフォーマンス差は大きいです。

遅い: 1行ずつINSERT
-- 1行ごとにSQLの解析・実行・コミットが発生
INSERT INTO orders (product, quantity) VALUES ('商品A', 1);
INSERT INTO orders (product, quantity) VALUES ('商品B', 2);
INSERT INTO orders (product, quantity) VALUES ('商品C', 3);
-- ... 10,000行繰り返し → 非常に遅い
速い: 複数行を一括INSERT
-- 1回のSQL実行で複数行を挿入
INSERT INTO orders (product, quantity) VALUES
    ('商品A', 1),
    ('商品B', 2),
    ('商品C', 3),
    ...
    ('商品N', N);
-- SQL解析が1回で済む → 高速

パフォーマンス目安

10,000行の挿入で、1行ずつINSERTの場合は約30秒かかるところ、一括INSERTなら約0.5秒で完了する場合があります(環境による)。数十倍の差が出ることは珍しくありません。

トランザクション制御(COMMIT頻度)

オートコミットが有効な環境では、INSERT文ごとにCOMMITが実行されます。大量挿入時はトランザクションでまとめてCOMMITすると高速になります。

MySQL – トランザクションでまとめてCOMMIT
SET autocommit = 0;
START TRANSACTION;

INSERT INTO orders (product, quantity) VALUES ('商品A', 1);
INSERT INTO orders (product, quantity) VALUES ('商品B', 2);
-- ... 大量のINSERT

COMMIT;
SET autocommit = 1;

注意

数百万行を1トランザクションにまとめると、UNDO領域やトランザクションログが肥大化して逆効果になる場合があります。1,000〜10,000行ごとにCOMMITするのが一般的なベストプラクティスです。

インデックスの一時無効化

大量データの初期ロード時は、インデックスを一時的に無効化してからINSERTし、完了後に再構築すると高速になります。

MySQL – インデックスの無効化と再構築
-- インデックスを無効化
ALTER TABLE orders DISABLE KEYS;

-- 大量INSERT実行
INSERT INTO orders (product, quantity) VALUES ...;

-- インデックスを再構築
ALTER TABLE orders ENABLE KEYS;

この方法は MyISAM テーブルで特に効果的です。InnoDB の場合はセカンダリインデックスのDROPと再作成で同様の効果が得られます。

LOAD DATA / COPY(CSVからの一括読み込み)

CSVファイルからの大量データ投入には、INSERT文よりも専用のバルクロード機能が圧倒的に高速です。

MySQL – LOAD DATA INFILE
LOAD DATA INFILE '/path/to/data.csv'
INTO TABLE orders
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS
(product, quantity, order_date);
PostgreSQL – COPY
COPY orders (product, quantity, order_date)
FROM '/path/to/data.csv'
WITH (FORMAT csv, HEADER true);
方法 100万行の目安速度 備考
1行ずつINSERT 数十分〜数時間 最も遅い
複数行INSERT(1000行ずつ) 数分〜十数分 手軽で実用的
LOAD DATA / COPY 数秒〜数十秒 最速(CSVファイル必要)
INSERT + インデックス無効化 + トランザクション 数十秒〜数分 組み合わせで高速化

よくあるミスと注意点

INSERT文の実行時に遭遇しやすいエラーとその原因・対処法をまとめます。

NOT NULL制約違反

NOT NULL制約が設定されたカラムにNULLを挿入(またはカラムを省略してDEFAULT値がない場合)するとエラーになります。

NOT NULL制約違反の例
-- name カラムは NOT NULL
INSERT INTO employees (id, name, department)
VALUES (10, NULL, '営業');

ERROR 1048 (23000): Column ‘name’ cannot be null

対処法:NULL以外の値を指定するか、テーブル定義にDEFAULT値を設定します。

型不一致

カラムのデータ型と値の型が一致しない場合、エラーまたは予期しない型変換が発生します。

型不一致の例
-- salary は INT 型
INSERT INTO employees (id, name, department, salary)
VALUES (10, '山田太郎', '営業', '高い');

ERROR 1366 (HY000): Incorrect integer value: ‘高い’ for column ‘salary’

MySQLの暗黙的型変換に注意

MySQLは厳密モード(STRICT_TRANS_TABLES)が無効だと、’123abc’ を数値カラムに挿入した際にエラーにならず 123 に変換されることがあります。予期しないデータが入る原因になるため、厳密モードの有効化を推奨します。

一意制約・主キー重複

PRIMARY KEY または UNIQUE制約のあるカラムに、既に存在する値を挿入しようとするとエラーになります。

主キー重複の例
-- id=1 は既に存在する
INSERT INTO employees (id, name, department, salary)
VALUES (1, '新しい人', '総務', 300000);

ERROR 1062 (23000): Duplicate entry ‘1’ for key ‘PRIMARY’

対処法

  • AUTO INCREMENTを使ってIDを自動採番する
  • INSERT前にSELECTで存在チェックする
  • UPSERTを使う(前述の ON DUPLICATE KEY UPDATE / ON CONFLICT)
  • INSERT IGNORE(MySQL)で重複時にスキップする
MySQL – INSERT IGNORE(重複時スキップ)
INSERT IGNORE INTO employees (id, name, department, salary)
VALUES (1, '新しい人', '総務', 300000);
-- エラーにならず、挿入がスキップされる

文字列中のシングルクォートのエスケープ

値にシングルクォート(')を含む文字列を挿入する場合、エスケープが必要です。

シングルクォートのエスケープ
-- シングルクォートを2つ重ねてエスケープ
INSERT INTO employees (id, name)
VALUES (10, 'O''Brien');
-- name に O'Brien が格納される
よくあるエラー 原因 対処法
Column cannot be null NOT NULL制約のカラムにNULL 値を指定 or DEFAULT設定
Incorrect integer value 数値カラムに文字列を挿入 正しいデータ型の値を指定
Duplicate entry for key PRIMARY KEY / UNIQUE重複 UPSERT or INSERT IGNORE
Column count doesn’t match カラム数と値の数が不一致 カラムリストと値の数を合わせる
Data too long for column VARCHAR長を超える文字列 値を短くする or カラム定義を変更
Unterminated string シングルクォートのエスケープ漏れ ''' に置換

SQLインジェクション対策

アプリケーションからINSERT文を実行する場合、ユーザー入力を直接SQL文に埋め込むとSQLインジェクションの脆弱性が生まれます。

危険: 文字列連結でSQL組み立て(絶対にやらない)
-- Python の例(危険なコード)
sql = f"INSERT INTO users (name) VALUES ('{user_input}')"
-- user_input に ' OR 1=1 -- のような値が入ると危険
安全: プリペアドステートメント(パラメータバインド)
-- Python の例(安全なコード)
sql = "INSERT INTO users (name) VALUES (%s)"
cursor.execute(sql, (user_input,))
-- パラメータバインドにより安全

鉄則

アプリケーションからのSQL実行は、必ずプリペアドステートメント(パラメータバインド)を使用してください。文字列連結でSQLを組み立てるのは絶対に避けましょう。

まとめ

やりたいこと 構文 対応RDBMS
1行挿入 INSERT INTO ... VALUES (...) 全RDBMS
複数行一括挿入 VALUES (...), (...), ... MySQL / PostgreSQL / SQL Server
複数行一括挿入(Oracle) INSERT ALL INTO ... SELECT * FROM DUAL Oracle
SELECT結果を挿入 INSERT INTO ... SELECT ... 全RDBMS
テーブルごとコピー CREATE TABLE ... AS SELECT MySQL / PostgreSQL / Oracle
UPSERT(MySQL) ON DUPLICATE KEY UPDATE MySQL
UPSERT(PostgreSQL) ON CONFLICT DO UPDATE / NOTHING PostgreSQL
UPSERT(Oracle / SQL Server) MERGE INTO ... USING Oracle / SQL Server
挿入後のID取得 RETURNING / LAST_INSERT_ID() RDBMS別
CSVからの高速読み込み LOAD DATA / COPY MySQL / PostgreSQL
重複時スキップ INSERT IGNORE / ON CONFLICT DO NOTHING MySQL / PostgreSQL

INSERT文は単純なようでいて、RDBMS間の構文差異が大きい分野です。基本の1行INSERTを理解したうえで、複数行INSERTINSERT INTO SELECTUPSERTパフォーマンス最適化を押さえれば、あらゆるデータ投入要件に対応できます。

関連記事:UPDATE文でデータを更新する方法DELETEで行を削除する方法SELECT文でデータを抽出する方法テーブル一覧を確認する方法