記事の目的
SQLの INSERT 文を使って、テーブルに新しい行を追加できるようになります。
1行だけ入れる基本形に加えて、複数行をまとめて入れる書き方、ほかのテーブルから検索した結果をそのまま入れるINSERT INTO … SELECT 、自動採番(AUTO_INCREMENT・SERIAL)の扱い、そして「同じキーが既にあるときどうするか」までを、PostgreSQL・MySQL・SQL Server の製品差に注意しながら整理します。
記事のサンプルテーブルの前提
本記事では、説明用に次のような users テーブルがある前提で進めます。
- id :自動採番(PostgreSQLなら
SERIAL、MySQLならAUTO_INCREMENT、SQL ServerならIDENTITY) - name :氏名(文字列)
- email :メールアドレス。重複を防ぐ
UNIQUE制約あり - created_at :作成日時。
DEFAULTに現在日時が設定されている実際のCREATE TABLE文は製品ごとに書き方が変わるため、別記事で扱います。
最短の答え
users テーブルに1行追加する基本形は次のとおりです。
- 主要なRDBMS(PostgreSQL / MySQL / SQL Server)で共通して使える基本形
INSERT INTO users (name, email)
VALUES ('佐藤', 'sato@example.com');実行後、テーブルには新しい行が1件追加されます。
SELECT id, name, email FROM users;
id | name | email
---+------+------------------
1 | 佐藤 | sato@example.com
- 複数行をまとめて入れたい場合は、VALUES の後ろにカンマ区切りで並べます。
INSERT INTO users (name, email) VALUES
('佐藤', 'sato@example.com'),
('鈴木', 'suzuki@example.com'),
('田中', 'tanaka@example.com');- 別のテーブルの検索結果をそのまま入れたい場合は SELECT を続けます。
INSERT INTO users_archive (name, email)
SELECT name, email FROM users WHERE created_at < '2025-01-01';ここから先は、それぞれの書き方の意味と、製品ごとに引っかかりやすい点を順に解説します。
基本構文 ── INSERT INTO … VALUES
INSERT 文の最も基本的な形は次のとおりです。
INSERT INTO テーブル名 (列1, 列2, 列3)
VALUES (値1, 値2, 値3);INSERT INTOの後ろに、データを入れたいテーブル名を書きます。- 続けてかっこ
()の中に、値を入れたい列の名前を順に並べます。 VALUESの後ろのかっこ()の中に、その列に対応する値を同じ順番で並べます。
列名を省略するとどうなるか
— 動きはする。ただし非推奨
INSERT INTO users
VALUES (1, ‘佐藤’, ‘sato@example.com’);
省略した場合は「テーブル定義の左から順に値を並べる」という意味になります。後でテーブルに列が追加されたり順序が変わったりすると、想定しない列に値が入ってエラーになります。業務コードでは必ず列名を書きます。
NULL と DEFAULT
明示的に NULL や DEFAULT (テーブル定義の既定値)を入れたい場合は、値の位置に書きます。
INSERT INTO users (name, email, memo)
VALUES ('佐藤', 'sato@example.com', NULL);
INSERT INTO users (name, email, created_at)
VALUES ('佐藤', 'sato@example.com', DEFAULT);NOT NULL 制約のある列に NULL を入れようとするとエラーになります。逆に、NOT NULL で DEFAULTも無い列を列リストから外すと、これも値が無くてエラーになります。
複数行INSERT ── 一度にまとめて入れる
同じテーブルに複数行を入れるなら、1行ずつ INSERT を繰り返すより、VALUES をカンマで並べて1回の INSERT にまとめるほうが高速です。ネットワーク往復が減り、トランザクションも1つにまとまります。
INSERT INTO users (name, email) VALUES
('佐藤', 'sato@example.com'),
('鈴木', 'suzuki@example.com'),
('田中', 'tanaka@example.com');実行後の中身は次のようになります。
SELECT id, name, email FROM users;
id | name | email
---+------+------------------
1 | 佐藤 | sato@example.com
2 | 鈴木 | suzuki@example.com
3 | 田中 | tanaka@example.com
複数行INSERTの製品差
3製品とも複数行 VALUES の構文をサポートしますが、まとめて入れられる行数の感覚が異なります。
| 製品 | 複数行 VALUES | 1文あたりの目安 |
|---|---|---|
| PostgreSQL | 対応 | 明確な行数上限は通常意識しない(SQL文サイズ・メモリ・実行時間の制約は受ける) |
| MySQL | 対応 | max_allowed_packet (既定64MB 程度)までのSQL文サイズ |
| SQL Server | 対応 | 1文あたり 1,000行まで(超えるとError 10738 ) |
SQL Server の1,000行制限は、INSERT … VALUES で複数行を直接並べた場合の制限です。これを超える場合は、INSERT を複数回に分けるか、BULK INSERT / bcp 、あるいは派生テーブルからのINSERT … SELECT を使います。
いずれの製品でも、数万行を超えるデータは INSERT の繰り返しではなく専用のバルクロード手段を使うのが定石です(詳しくは後述の「落とし穴」)。
上記の数値はバージョンや構成で変わります。各製品の現行ドキュメントで再確認してください
INSERT INTO … SELECT ── 検索結果をそのまま入れる
別のテーブル(または同じテーブル)の検索結果を、そのまま新しい行として入れる書き方です。データの複製・アーカイブ・集計結果の保存などで頻繁に使います。
INSERT INTO users_archive (name, email, created_at)
SELECT name, email, created_at
FROM users
WHERE created_at < '2025-01-01';ポイントは次の3つです。
- INSERT 側の列リストと SELECT 側の列の順序が一致していること。
- SELECT の列数と INSERT の列リストの数が一致していること。
- それぞれのデータ型が代入互換であること(数値→文字列のような暗黙変換は製品によって挙動が異なります)。
集計結果をそのまま保存する
SELECT 側は集計や JOIN を含む通常の SELECT 文がそのまま使えます。
-- 月次の購入件数をサマリーテーブルに記録する例
INSERT INTO purchase_monthly_summary (year_month, total_count, total_amount)
SELECT
TO_CHAR(purchased_at, 'YYYY-MM') AS year_month, -- PostgreSQL の例
COUNT(*),
SUM(amount)
FROM purchases
WHERE purchased_at >= '2026-01-01' AND purchased_at < '2026-02-01'
GROUP BY TO_CHAR(purchased_at, 'YYYY-MM');TO_CHAR は PostgreSQL の関数です。MySQL では DATE_FORMAT 、SQL Server では FORMAT またはCONVERT を使います。詳細は各製品の日付処理記事へ。
自動採番列(主キーの自動採番)
主キーを自動で振りたい場合、製品ごとに書き方が異なります。列名を省略するか、DEFAULT を入れるか、対応する関数を入れるのが基本です。
| 製品 | 列定義の書き方 | INSERT時の指定 |
|---|---|---|
| PostgreSQL | (SQL標準準拠)id BIGINT GENERATED ALWAYS AS IDENTITY(従来表現) id BIGSERIAL | 列リストから外す or DEFAULT |
| MySQL | id BIGINT AUTO_INCREMENT PRIMARY KEY | 列リストから外す orNULL |
| SQL Server | id BIGINT IDENTITY(1,1) PRIMARY KEY | 列リストから外す(直接指定は SET IDENTITY_INSERT が必要) |
PostgreSQL 10以降は SQL標準準拠の GENERATED AS IDENTITY が推奨され、SERIAL / BIGSERIAL は従来から広く使われている書き方として現在も有効です。
-- PostgreSQL / MySQL / SQL Server で共通の書き方(id列を省略する)
INSERT INTO users (name, email)
VALUES ('佐藤', 'sato@example.com');採番された値を取得する
INSERT 直後にその行の id を知りたい、というのは非常によくある要求ですが、書き方は完全に製品依存です。
-- PostgreSQL: RETURNING 句(標準SQL拡張)
INSERT INTO users (name, email)
VALUES ('佐藤', 'sato@example.com')
RETURNING id;-- MySQL: 直近の AUTO_INCREMENT 値を関数で取得
INSERT INTO users (name, email) VALUES ('佐藤', 'sato@example.com');
SELECT LAST_INSERT_ID();
-- 注:LAST_INSERT_ID() は接続単位で管理されます。
-- INSERTを実行したのと「同じ接続」で取得してください。
-- 別の接続から呼ぶと、その接続で最後に行われた採番の値(または0)が返ります。-- SQL Server: OUTPUT 句(推奨)
INSERT INTO users (name, email)
OUTPUT INSERTED.id
VALUES ('佐藤', 'sato@example.com');PostgreSQL の RETURNING 句については、別記事で詳しく扱います。
重複時の挙動(製品別UPSERT)
主キーやUNIQUE 制約のある列に同じ値を入れると、エラーになります。
-- email 列に UNIQUE 制約がある場合、同じ email の再INSERTは失敗する
INSERT INTO users (name, email) VALUES ('佐藤', 'sato@example.com');
INSERT INTO users (name, email) VALUES ('別人', 'sato@example.com');
-- → 制約違反エラー「既にあれば更新、無ければ追加」(UPSERT)にしたい場合、各製品が独自の構文を持ちます。ここでは入口だけ示します。それぞれの落とし穴・指定方法は別記事で扱います。
| 製品 | UPSERT の代表的な構文 | 詳細 |
|---|---|---|
| PostgreSQL | INSERT … ON CONFLICT (列) DO UPDATE SET … | PostgreSQL個別記事へ |
| MySQL | INSERT … ON DUPLICATE KEY UPDATE 列 = 値 | MySQL個別記事へ |
| SQL Server | (同時実行下の競合に注意)MERGE 文 | SQL Server個別記事へ |
-- PostgreSQL の例(PostgreSQL 9.5以降)
INSERT INTO users (email, name)
VALUES ('sato@example.com', '佐藤(更新)')
ON CONFLICT (email)
DO UPDATE SET name = EXCLUDED.name;詳しくは別記事で扱います。
UPSERTの具体的な書き方は版差が大きい領域です(例:MySQL 8.0.20以降の構文変更、SQL Server
MERGE の挙動)。各製品の現行マニュアルで版を確認してください。
落とし穴・注意点
INSERT は DELETE や DROP のような破壊的操作ではありませんが、運用で問題になりがちな点があります。
トランザクションを意識する
3製品とも、既定では文ごとの自動コミット( autocommit )が有効です。この状態で複数の INSERT文を順に実行していると、途中でエラーになっても、すでに成功した文は確定済みになります。複数のINSERT をまとめて「失敗時はすべて戻したい」場合は、明示的にトランザクションで囲みます。書き方は製品で少し異なります。
| 製品 | 明示的なトランザクションの書き方 |
|---|---|
| PostgreSQL | BEGIN; … COMMIT; |
| MySQL | START TRANSACTION; … COMMIT;またはBEGIN; … COMMIT; |
| SQL Server | BEGIN TRANSACTION; … COMMIT; |
途中で異常が起きたときは ROLLBACK; で巻き戻せます。
外部キー制約に注意
参照先のテーブルに対応する行が無いと、INSERT は失敗します。子テーブル → 親テーブル の順は逆で、親 → 子の順で入れるのが原則です。
トリガー・既定値で意図しない値が入ることがある
BEFORE INSERT トリガーや列の DEFAULT 式が、自分の知らない値を入れている場合があります。意図しない値が入っているときは、まずトリガー定義と列定義を確認します。
大量INSERTには専用の手段
数万行を超える挿入は、INSERT の繰り返しではなく COPY(PostgreSQL)/ LOAD DATA INFILE (MySQL)/ BULK INSERT ・ bcp (SQL Server)を検討します。
文字コード・タイムゾーンの取り扱い
クライアントとサーバの文字コードがズレていると、文字列がそのまま文字化けして保存されます。SET client_encoding (PostgreSQL)/ SET NAMES(MySQL)/接続文字列のエンコーディング指定(SQL Server)を最初に確認します。
まとめ
INSERT 文は SQL の最も基本的な操作ですが、列名は省略しない・複数行はまとめる・主キーの採番と取得は製品依存・UPSERT は子記事で個別に学ぶ、の4点を押さえれば、現場のほとんどのケースに対応できます。
