How to fix Too many rows in PostgreSQL
The "too many rows" error (P0003) occurs in PL/pgSQL when a SELECT INTO STRICT statement returns multiple rows instead of exactly one. Fix it by adding LIMIT 1, tightening WHERE clauses, or removing STRICT.
Quick fix
Fixes P0003
BEGIN
SELECT * INTO STRICT myrec FROM users WHERE email = user_email LIMIT 1;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
RAISE EXCEPTION 'Multiple users found with email %', user_email;
END;- Add LIMIT 1 to return only the first matching row
- Tighten your WHERE clause to guarantee uniqueness
- Remove STRICT if you only need the first result
- Add exception handling to catch and log the error
BEGINSELECT * INTO STRICT myrec FROM users WHERE email = user_email LIMIT 1;EXCEPTIONWHEN TOO_MANY_ROWS THENRAISE EXCEPTION 'Multiple users found with email %', user_email;END;Too many rows
What this error means
PostgreSQL throws the "too many rows" error (SQLSTATE P0003) when using SELECT INTO with the STRICT option and the query returns more than one row. The STRICT keyword enforces that a query must return exactly one row—if it returns zero rows, you get NO_DATA_FOUND (P0002); if it returns multiple rows, you get TOO_MANY_ROWS (P0003). This is a critical safety check in PL/pgSQL functions to prevent silent data inconsistencies.
Typical symptoms
- PL/pgSQL function execution fails with error code P0003
- SELECT INTO STRICT statement throws exception when query matches multiple records
- Application receives "ERROR: query returned more than one row" message
- Function intended to find unique records instead finds duplicates
- RETURNING clause used with STRICT also triggers this error with multiple results
Common causes
- Using SELECT INTO STRICT without a WHERE clause that guarantees uniqueness
- WHERE clause matches multiple rows when it should match exactly one
- Missing LIMIT 1 clause on a query that can return multiple rows
- Business logic assumes uniqueness but data contains duplicates
- Using SELECT INTO STRICT on a non-unique column (e.g., category, status)
- Forgetting to add UNIQUE constraints or indexes on columns used in WHERE clause
How to fix "Too many rows"
If you only need one result and multiple rows may exist, add LIMIT 1 to your query:
BEGIN
SELECT * INTO STRICT myrec FROM users WHERE email = user_email LIMIT 1;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
RAISE EXCEPTION 'Multiple users found with email %', user_email;
END;This ensures only the first row is fetched and assigned to the variable.
Refine the WHERE condition to match exactly one record. For example, use a primary key or unique identifier instead of a non-unique column:
-- WRONG: matches multiple users with same email domain
SELECT * INTO STRICT user_rec FROM users WHERE email LIKE '%@example.com';
-- CORRECT: matches exactly one user by primary key
SELECT * INTO STRICT user_rec FROM users WHERE id = user_id;Always use unique columns (id, uuid, email if unique constraint exists) in your WHERE clause when using STRICT.
If your function logic doesn't require exactly one row and you just want the first match, remove the STRICT keyword:
BEGIN
-- Without STRICT: assigns first row, ignores others
SELECT id, name INTO user_id, user_name FROM users WHERE status = 'active';
-- If no rows found, variables are set to NULL
IF NOT FOUND THEN
RAISE EXCEPTION 'No active users found';
END IF;
END;Without STRICT, the first row is assigned and remaining rows are ignored. Use this only when your logic accepts that behavior.
Use BEGIN...EXCEPTION blocks to catch TOO_MANY_ROWS and handle it gracefully:
DECLARE
myrec RECORD;
BEGIN
SELECT * INTO STRICT myrec FROM products WHERE sku = product_sku;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE EXCEPTION 'Product with SKU % not found', product_sku;
WHEN TOO_MANY_ROWS THEN
RAISE EXCEPTION 'Data integrity error: product SKU % is not unique', product_sku;
END;This provides clear error messages and allows graceful error handling in your application.
Check if duplicate data exists in your table and add UNIQUE constraints to prevent future duplicates:
-- Find duplicate values causing the error
SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1;
-- Add unique constraint to prevent future duplicates
ALTER TABLE users ADD CONSTRAINT uq_users_email UNIQUE (email);
-- Or create a unique index if some values can be NULL
CREATE UNIQUE INDEX idx_users_email ON users(email) WHERE email IS NOT NULL;Database constraints ensure data integrity at the source and prevent this error from recurring.
Advanced notes
The print_strict_params setting can be enabled to include query parameters in error messages for debugging. By default, PostgreSQL does not include these details. You can enable it per-function using SET plpgsql.print_strict_params = on, or globally via postgresql.conf. This is useful for understanding which parameter value caused multiple matches. Additionally, if you need to process multiple rows intentionally, use cursors with FETCH instead of SELECT INTO: DECLARE cur CURSOR FOR SELECT ... ; FETCH ALL FROM cur;. For large result sets, cursors allow memory-efficient row-by-row processing without loading everything into memory.
Frequently asked questions
- What does "P0003" mean?
- PostgreSQL throws the "too many rows" error (SQLSTATE P0003) when using SELECT INTO with the STRICT option and the query returns more than one row. The STRICT keyword enforces that a query must return exactly one row—if it returns zero rows, you get NO_DATA_FOUND (P0002); if it returns multiple rows, you get TOO_MANY_ROWS (P0003). This is a critical safety check in PL/pgSQL functions to prevent silent data inconsistencies.
- How do I fix "P0003"?
- The "too many rows" error (P0003) occurs in PL/pgSQL when a SELECT INTO STRICT statement returns multiple rows instead of exactly one. Fix it by adding LIMIT 1, tightening WHERE clauses, or removing STRICT.
- What causes "P0003"?
- Using SELECT INTO STRICT without a WHERE clause that guarantees uniqueness WHERE clause matches multiple rows when it should match exactly one Missing LIMIT 1 clause on a query that can return multiple rows
- Is the "P0003" error serious?
- It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.
Official resources & further reading
- 📚PostgreSQL Official Documentation
- 📖PostgreSQL PL/pgSQL Basic Statements (SELECT INTO)(Official Docs)
- 📖PostgreSQL Error Codes Reference(Official Docs)