Skip to content
PostgreSQL

PostgreSQL Error 21000

more than one row returned by a subquery used as an expression

Applies to: PostgreSQL 13 to 18; MERGE from PostgreSQL 15 (examples run on PostgreSQL 18.6 in PGlite)
Last technically reviewed 24 October 2026 on PostgreSQL 18.6 (PGlite 0.5.11)
The full message
ERROR:  more than one row returned by a subquery used as an expression
Other forms of this message
INSERT ... ON CONFLICT DO UPDATE with the same key twice in one statement
ERROR:  ON CONFLICT DO UPDATE command cannot affect row a second time
HINT:  Ensure that no rows proposed for insertion within the same command have duplicate constrained values.
MERGE where several source rows match one target row
ERROR:  MERGE command cannot affect row a second time
HINT:  Ensure that not more than one source row matches any one target row.
SQLSTATE
21000
Condition name
cardinality_violation
Product
PostgreSQL
Versions
PostgreSQL 13 to 18; MERGE from PostgreSQL 15 (examples run on PostgreSQL 18.6 in PGlite)
Last reviewed
24 October 2026

What it means

SQLSTATE 21000 means a statement needed at most one row but got several: a scalar subquery returned more than one row, or an upsert or MERGE tried to change the same target row twice.

A subquery used as a value, in a SELECT list, after =, < and similar operators, or in UPDATE ... SET, must return a single value. If it returns no rows the value is NULL, but if it returns two or more PostgreSQL cannot pick one and raises 21000 (cardinality_violation). The error is raised while the query runs, so it often appears only for some data: the query works until a customer places a second order or a lookup table gains a duplicate.

The same SQLSTATE protects upserts. INSERT ... ON CONFLICT DO UPDATE refuses to update one existing row twice in a single statement, which happens when the inserted rows contain the same key more than once; MERGE (PostgreSQL 15 and later) refuses when several source rows match the same target row. In both cases the result would depend on row order, so PostgreSQL stops instead.

Common causes

  • A correlated subquery in the SELECT list, such as the order total per customer, returns several rows once a customer has more than one order.
  • A filter uses = (SELECT ...) where the subquery can match several rows; IN or EXISTS was meant.
  • UPDATE ... SET column = (SELECT ...) is missing the condition that ties the subquery to the row being updated.
  • A batch upsert (INSERT ... VALUES list or INSERT ... SELECT with ON CONFLICT DO UPDATE) contains the same key twice.
  • MERGE uses a source with duplicate join keys.

Diagnose it

Read-only These queries only read the system catalogs (pg_catalog, information_schema) and statistics views such as pg_stat_activity and pg_locks. They do not change data, roles or settings, so they are safe to run first.

Which keys make the subquery return several rows?

Group the subquery's table by the column that links it to the outer query.

SELECT customer_id, count(*) AS matching_rows
FROM orders
GROUP BY customer_id
HAVING count(*) > 1
ORDER BY matching_rows DESC;
What to look for

Each outer row that makes the subquery return more than one row. Decide whether you want one of them (latest, largest), an aggregate, or all of them through a join.

Find duplicate keys in an upsert or MERGE source

SELECT sku, count(*) AS copies
FROM staging_stock
GROUP BY sku
HAVING count(*) > 1;
What to look for

Keys that appear more than once in the rows being upserted or merged. Each must be reduced to one row before the statement can run.

How to fix it

Each fix is labelled with what it changes. Try read-only checks and the least invasive fix first, and test changes outside production.

Use IN or EXISTS when several rows are valid

Read-only

For a filter that should match any of the subquery's rows, use IN or EXISTS instead of =.

SELECT *
FROM customers
WHERE id IN (SELECT customer_id FROM orders WHERE total > 10);

Return exactly one row: ORDER BY ... LIMIT 1 or an aggregate

Read-only

When you want one value per outer row, say which one: the latest with ORDER BY and LIMIT 1, or a summary with max or sum. Without ORDER BY, LIMIT 1 picks an arbitrary row.

SELECT c.name,
       (SELECT o.total
        FROM orders AS o
        WHERE o.customer_id = c.id
        ORDER BY o.id DESC
        LIMIT 1) AS last_total
FROM customers AS c;

Deduplicate the rows before an upsert

Changes data

Combine or choose one row per key in the source, then run ON CONFLICT DO UPDATE. Here duplicate quantities are added up; use DISTINCT ON to keep only the latest row instead.

INSERT INTO stock (sku, qty)
SELECT sku, sum(qty)
FROM staging_stock
GROUP BY sku
ON CONFLICT (sku) DO UPDATE SET qty = EXCLUDED.qty;

Enforce the single row with a unique constraint

Changes schema

If the subquery looks up something that should be unique (one rate per currency, one setting per key), add a unique constraint so a duplicate is rejected when it is written, with 23505, instead of breaking reads later. Remove existing duplicates first.

ALTER TABLE exchange_rates
    ADD CONSTRAINT exchange_rates_currency_key UNIQUE (currency);

Examples

A correlated subquery that returns several orders

CREATE TABLE customers (id int PRIMARY KEY, name text);
CREATE TABLE orders (id int PRIMARY KEY, customer_id int, total numeric(10,2));
INSERT INTO customers VALUES (1, 'Sara'), (2, 'Ali');
INSERT INTO orders VALUES (10, 1, 20), (11, 1, 35), (12, 2, 15);

SELECT c.name,
       (SELECT o.total FROM orders o WHERE o.customer_id = c.id) AS last_total
FROM customers c;
Result
ERROR:  more than one row returned by a subquery used as an expression

Reproduced on PGlite 0.5.11 (PostgreSQL 18.6 compiled to WebAssembly), 24 October 2026.

= (SELECT ...) where IN was meant

CREATE TABLE customers (id int PRIMARY KEY, name text);
CREATE TABLE orders (id int PRIMARY KEY, customer_id int, total numeric(10,2));
INSERT INTO customers VALUES (1, 'Sara'), (2, 'Ali');
INSERT INTO orders VALUES (10, 1, 20), (11, 1, 35), (12, 2, 15);

SELECT * FROM customers
WHERE id = (SELECT customer_id FROM orders WHERE total > 10);
Result
ERROR:  more than one row returned by a subquery used as an expression

Reproduced on PGlite 0.5.11 (PostgreSQL 18.6 compiled to WebAssembly), 24 October 2026.

An upsert batch with the same key twice

The table can be empty: the first A1 row is inserted and the second would have to update it in the same statement, which PostgreSQL does not allow.

CREATE TABLE stock (sku text PRIMARY KEY, qty int);

INSERT INTO stock (sku, qty)
VALUES ('A1', 5), ('A1', 7)
ON CONFLICT (sku) DO UPDATE SET qty = EXCLUDED.qty;
Result
ERROR:  ON CONFLICT DO UPDATE command cannot affect row a second time
HINT:  Ensure that no rows proposed for insertion within the same command have duplicate constrained values.

Reproduced on PGlite 0.5.11 (PostgreSQL 18.6 compiled to WebAssembly), 24 October 2026.

MERGE with two source rows for one target row

CREATE TABLE stock (sku text PRIMARY KEY, qty int);
INSERT INTO stock VALUES ('A1', 0);

MERGE INTO stock s
USING (VALUES ('A1', 1), ('A1', 2)) AS v(sku, qty)
ON s.sku = v.sku
WHEN MATCHED THEN UPDATE SET qty = v.qty;
Result
ERROR:  MERGE command cannot affect row a second time
HINT:  Ensure that not more than one source row matches any one target row.

Reproduced on PGlite 0.5.11 (PostgreSQL 18.6 compiled to WebAssembly), 24 October 2026.

How to prevent it

Write a scalar subquery only when the data model guarantees one row (a primary key or unique constraint on the lookup), or make it explicit with an aggregate or ORDER BY ... LIMIT 1. Test queries with data that has several matching rows, not just one.

Deduplicate batches before an upsert or MERGE, in the application or with GROUP BY or DISTINCT ON in the source query, so each key appears once per statement.

Common questions

How do I fix "more than one row returned by a subquery used as an expression"?

Find which outer rows match several subquery rows (GROUP BY the linking column with HAVING count(*) > 1), then either use IN or EXISTS, return one row with ORDER BY ... LIMIT 1 or an aggregate, or rewrite the subquery as a join if you want all rows.

Why did the query work yesterday and fail today?

The check happens at run time, on the actual rows. As long as every subquery returned at most one row the query worked; a new order, a duplicate lookup row or an import changed that. A unique constraint on the looked-up column prevents it for good.

What does "ON CONFLICT DO UPDATE command cannot affect row a second time" mean?

The rows being inserted in one statement contain the same conflict key more than once, so PostgreSQL would have to update the same row twice. Combine or remove the duplicates in the source before the upsert.

Learn more on CodeWithSQL

Official documentation

Last technically reviewed 24 October 2026; examples and diagnostics run on PostgreSQL 18.6 (PGlite 0.5.11). SQL-level examples were run in PGlite (PostgreSQL compiled to WebAssembly); examples that need a running server are shown as documented in the PostgreSQL documentation.

Still stuck on a PostgreSQL error?

Browse the full PostgreSQL error library, or get hands-on help from an experienced SQL professional.