PostgreSQL lets you insert multiple rows in one INSERT statement by supplying a comma-separated list of value tuples. This is more efficient than separate single-row inserts because it reduces round-trips to the server and wraps all inserts in a single atomic operation.
Multi-row INSERT syntax
Extend the standard INSERT syntax with additional value lists, each wrapped in parentheses and separated by commas:
INSERT INTO contacts (first_name, last_name, email)
VALUES
('John', 'Doe', '[email protected]'),
('Jane', 'Smith', '[email protected]'),
('Bob', 'Johnson', '[email protected]');
Output:
INSERT 0 3
The count in the command tag confirms how many rows were inserted. If any value list violates a constraint (for example, a duplicate UNIQUE email), the entire statement fails and no rows are inserted.
Returning inserted rows and IDs
Use the RETURNING clause to get back the inserted rows — useful when you need the auto-generated primary key for each row:
INSERT INTO contacts (first_name, last_name, email)
VALUES
('Alice', 'Johnson', '[email protected]'),
('Charlie', 'Brown', '[email protected]')
RETURNING *;
Output:
id | first_name | last_name | email
----+------------+-----------+---------------------------
4 | Alice | Johnson | [email protected]
5 | Charlie | Brown | [email protected]
(2 rows)
To return only the generated IDs:
INSERT INTO contacts (first_name, last_name, email)
VALUES
('Eva', 'Williams', '[email protected]'),
('Michael', 'Miller', '[email protected]'),
('Sophie', 'Davis', '[email protected]')
RETURNING id;
Output:
id
----
6
7
8
(3 rows)
Practical tips
- Batch inserts are atomic — if one value list violates a constraint, the whole statement rolls back. Use
INSERT ... ON CONFLICT DO NOTHINGif you want to skip conflicting rows instead of failing. - For very large datasets,
COPY FROMis faster than even a single large multi-rowINSERTbecause it bypasses per-row overhead. - Keep multi-row inserts inside explicit transactions when they are part of a larger workflow so you can roll back cleanly on application errors.
- The order of rows returned by
RETURNINGmatches the order of theVALUESlists, which lets you correlate returned IDs with your input data.
Reference: PostgreSQL documentation — INSERT.