- Error code
- ORA-01422
- Product
- Oracle Database
- Versions
- Oracle Database 19c, 21c, 23ai and 26ai (wording differs: see below)
- Category
- PL/SQL & Triggers
- Last reviewed
- 24 October 2026
On this page
What it means
ORA-01422 means a SELECT INTO (or a DML statement with RETURNING INTO) in PL/SQL returned more than one row, but its target variables can hold only one; PL/SQL raised the predefined exception TOO_MANY_ROWS.
A SELECT ... INTO into scalar variables or a record must return exactly one row. Oracle's current reference lists three causes: a SELECT in exact fetch mode returned more rows than requested (seen from client interfaces), a PL/SQL SELECT INTO returned more than one row, and a PL/SQL UPDATE, DELETE or INSERT ... RETURNING INTO returned more than one row. In PL/SQL this is the predefined exception TOO_MANY_ROWS.
The opposite case, no rows, is ORA-01403 no data found. The current reference's template ends with a placeholder for the number of rows requested; Oracle's documentation does not show how that number is printed, so this page leaves it out. Oracle Database 19c prints the shorter text "exact fetch returns more than requested number of rows".
Common causes
- The WHERE clause is not on a unique key: a customer can have several addresses, orders or emails, and the code assumed one.
- Duplicate rows exist where the design assumed uniqueness but no unique constraint enforces it.
- A join multiplies rows, for example joining to a child table, so one parent row becomes several.
- An UPDATE or DELETE with RETURNING INTO matches several rows, because its WHERE clause is wider than intended.
- A filter that used to narrow the result to one row, such as an "active" flag or a date range, now matches more than one.
Diagnose it
Read-only These queries only read the data dictionary (USER_, ALL_ and DBA_ views) and dynamic performance views (V$). They do not change data, privileges or settings, so they are safe to run first. DBA_ and V$ views need the matching privileges.
Run the query and count the rows
SELECT COUNT(*) AS matches
FROM shop.addresses
WHERE customer_id = 4711
AND is_active = 'Y'; Any number above 1 explains the error. Then list the rows to see what makes them different.
Find every key that has more than one row
SELECT customer_id, COUNT(*) AS copies
FROM shop.addresses
WHERE is_active = 'Y'
GROUP BY customer_id
HAVING COUNT(*) > 1
ORDER BY copies DESC; All the keys for which the SELECT INTO would fail, not just the one in the current call.
Is the uniqueness enforced?
SELECT c.constraint_name, c.constraint_type, cc.column_name, cc.position
FROM all_constraints c
JOIN all_cons_columns cc
ON cc.owner = c.owner AND cc.constraint_name = c.constraint_name
WHERE c.owner = 'SHOP'
AND c.table_name = 'ADDRESSES'
AND c.constraint_type IN ('P', 'U')
ORDER BY c.constraint_name, cc.position; The primary and unique keys. If none covers the columns your WHERE clause uses, nothing stops several rows from matching.
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.
Narrow the query to the one row you mean
Changes schemaAdd the condition that makes the row unique, such as the primary key or a clear rule for which row wins. FETCH FIRST 1 ROW ONLY with an ORDER BY states that rule explicitly; without the ORDER BY the row chosen is arbitrary.
CREATE OR REPLACE FUNCTION shop.get_active_address (p_customer_id IN NUMBER)
RETURN shop.addresses.id%TYPE
IS
v_id shop.addresses.id%TYPE;
BEGIN
SELECT id INTO v_id
FROM shop.addresses
WHERE customer_id = p_customer_id AND is_active = 'Y'
ORDER BY created_at DESC
FETCH FIRST 1 ROW ONLY; -- the newest active address wins
RETURN v_id;
END;
/
Process all rows with a loop or BULK COLLECT
Changes schemaIf several rows are valid, Oracle's action text says to use a FOR loop to process them, or BULK COLLECT to return them into a collection.
CREATE OR REPLACE PROCEDURE shop.verify_active_addresses (p_customer_id IN NUMBER)
IS
TYPE id_list IS TABLE OF shop.addresses.id%TYPE;
v_ids id_list;
BEGIN
SELECT id BULK COLLECT INTO v_ids
FROM shop.addresses
WHERE customer_id = p_customer_id AND is_active = 'Y';
FOR i IN 1 .. v_ids.COUNT LOOP
shop.verify_address(v_ids(i));
END LOOP;
END;
/
Enforce the rule with a unique constraint or index
Changes schemaWhen there really must be only one active row per key, enforce it. A function-based unique index can enforce "one active row per customer" while allowing any number of inactive rows, because rows where the expression is NULL are not indexed. Clean up the existing duplicates first, or creating the index fails.
CREATE UNIQUE INDEX shop.addresses_one_active_ux
ON shop.addresses (CASE WHEN is_active = 'Y' THEN customer_id END);
Handle TOO_MANY_ROWS only as a clear error
Changes schemaCatching TOO_MANY_ROWS and picking a row silently hides a data problem. If you catch it, raise an application error that names the key, so the duplicates get fixed.
CREATE OR REPLACE FUNCTION shop.get_only_active_address (p_customer_id IN NUMBER)
RETURN shop.addresses.id%TYPE
IS
v_id shop.addresses.id%TYPE;
BEGIN
SELECT id INTO v_id FROM shop.addresses
WHERE customer_id = p_customer_id AND is_active = 'Y';
RETURN v_id;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
RAISE_APPLICATION_ERROR(-20020,
'Customer ' || p_customer_id || ' has more than one active address');
END;
/
Examples
A customer with two active addresses
Not reproduced here: no Oracle Database is installed for this site. The message is the current wording from Oracle's error reference, without the row count its template ends with; Oracle Database 19c prints "exact fetch returns more than requested number of rows". Names and the line number are illustrative.
DECLARE
v_id addresses.id%TYPE;
BEGIN
SELECT id INTO v_id
FROM addresses
WHERE customer_id = 4711 AND is_active = 'Y'; -- two rows match
END;
/
ORA-01422: exact fetch returned more than the requested number of rows ORA-06512: at line 4
RETURNING INTO from an UPDATE that matches several rows
Not reproduced here: no Oracle Database is installed for this site. Oracle's current reference names DML RETURNING INTO as one of the causes; the message format is from that reference and the names are illustrative. Use RETURNING BULK COLLECT INTO a collection when several rows are expected.
DECLARE
v_total orders.total%TYPE;
BEGIN
UPDATE orders SET total = total * 0.9
WHERE customer_id = 4711
RETURNING total INTO v_total; -- the customer has three orders
END;
/
ORA-01422: exact fetch returned more than the requested number of rows ORA-06512: at line 4
How to prevent it
Use SELECT INTO only on a primary or unique key, or with a rule that picks exactly one row (an aggregate, or ORDER BY with FETCH FIRST 1 ROW ONLY). Back every "only one" assumption with a unique constraint or index.
When a query can legitimately return several rows, write it as a cursor loop or BULK COLLECT from the start.
Common questions
How do I fix ORA-01422 exact fetch returns more than requested number of rows?
Run the SELECT INTO's query and count the rows. Then either add the condition that makes it return one row, process all the rows with a loop or BULK COLLECT, or remove the duplicates and add a unique constraint so they cannot come back.
Is ROWNUM = 1 a good fix?
Only if any of the matching rows is acceptable. Without an ORDER BY the row you get is arbitrary and can change, so state which row should win with ORDER BY ... FETCH FIRST 1 ROW ONLY, or fix the duplicates.
Which exception name do I catch for ORA-01422?
TOO_MANY_ROWS, the predefined PL/SQL exception with error code -1422. The no-rows case is NO_DATA_FOUND (ORA-01403).
Learn more on CodeWithSQL
Official documentation
- ORA-01422 (Oracle Database Error Messages)
- SELECT INTO statement (Oracle PL/SQL Language Reference 19c)
- Bulk SQL and bulk binding (Oracle PL/SQL Language Reference 19c)
Last technically reviewed 24 October 2026; examples and diagnostics run on Oracle Database Error Messages reference (26ai, 21c and 19c); not run locally. Messages and explanations follow Oracle's Database Error Messages reference; no Oracle Database was available, so the examples were not run locally.