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.
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.
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.
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.
ERROR: syntax error at end of input
Syntax error at end of input in PostgreSQL
Bind message supplies N parameters but prepared statement requires M
Bind message supplies N parameters but prepared statement requires M in PostgreSQL
Multidimensional arrays must have sub-arrays with matching dimensions
Multidimensional arrays must have sub-arrays with matching dimensions
ERROR: value too long for type character varying
Value too long for type character varying
insufficient columns in unique constraint for partition key
How to fix "insufficient columns in unique constraint for partition key" in PostgreSQL