DEV Community

umzzil nng
umzzil nng

Posted on • Originally published at oraerror.com

PostgreSQL 2201W Error: Causes and Solutions Complete Guide

PostgreSQL Error 2201W: Invalid Row Count in LIMIT Clause

PostgreSQL error code 2201W (invalid_row_count_in_limit_clause) is thrown when the value provided to a LIMIT clause is not a valid non-negative integer. Specifically, passing a negative number, a non-integer expression that resolves to an invalid value, or an improperly typed variable will trigger this error at runtime. It commonly surfaces in applications that dynamically construct SQL queries or bind user-supplied pagination parameters directly to queries.


Top 3 Causes

1. Passing a Negative Value Directly to LIMIT

The most frequent cause is a miscalculated pagination value that results in a negative integer being bound to the LIMIT clause.

-- This will raise ERROR 2201W
SELECT * FROM orders LIMIT -10;

-- Also fails when a variable holds a negative value
DO $$
DECLARE
    v_limit INT := -5;
BEGIN
    -- Runtime error: invalid row count in limit clause
    EXECUTE 'SELECT * FROM orders LIMIT ' || v_limit;
END;
$$;
Enter fullscreen mode Exit fullscreen mode

2. Unvalidated Function or Subquery Results Used in LIMIT

When a subquery or function is used to compute the LIMIT value, unexpected data conditions can produce invalid results.

-- Dangerous pattern: subquery may return a negative or NULL value
SELECT *
FROM products
LIMIT (SELECT total_stock - reserved_stock FROM inventory WHERE id = 1);
-- If reserved_stock > total_stock, result is negative → 2201W!

-- Also risky: unguarded CASE expression
SELECT *
FROM events
LIMIT (
    CASE
        WHEN category = 'A' THEN 50
        -- Missing ELSE can cause NULL in some PostgreSQL contexts
    END
);
Enter fullscreen mode Exit fullscreen mode

3. Uninitialized or Miscalculated Variables in PL/pgSQL

Inside stored procedures and PL/pgSQL blocks, variables used for LIMIT can carry unexpected values due to logic errors or missing initialization.

-- Problematic function
CREATE OR REPLACE FUNCTION fetch_logs(p_multiplier INT)
RETURNS SETOF logs AS $$
DECLARE
    v_limit INT;
BEGIN
    -- Bug: if p_multiplier is negative, v_limit becomes negative
    v_limit := 100 * p_multiplier;
    RETURN QUERY SELECT * FROM logs LIMIT v_limit; -- 2201W if negative!
END;
$$ LANGUAGE plpgsql;
Enter fullscreen mode Exit fullscreen mode

Quick Fix Solutions

Use GREATEST() and COALESCE() as your first line of defense to sanitize LIMIT values before they reach the query engine.

-- Fix 1: Use GREATEST to guarantee a non-negative floor
SELECT * FROM orders
LIMIT GREATEST(0, :user_supplied_limit);

-- Fix 2: COALESCE + GREATEST for NULL safety
SELECT * FROM products
LIMIT COALESCE(GREATEST(0, :limit_param), 100);

-- Fix 3: Safe PL/pgSQL function with input validation
CREATE OR REPLACE FUNCTION fetch_logs_safe(p_multiplier INT)
RETURNS SETOF logs AS $$
DECLARE
    v_limit INT;
BEGIN
    v_limit := GREATEST(1, LEAST(100 * COALESCE(p_multiplier, 1), 10000));
    RETURN QUERY SELECT * FROM logs ORDER BY created_at DESC LIMIT v_limit;
END;
$$ LANGUAGE plpgsql;

-- Fix 4: Safe dynamic SQL execution
DO $$
DECLARE
    v_limit INT := GREATEST(0, -5);  -- Corrects to 0
BEGIN
    EXECUTE format('SELECT * FROM orders LIMIT %s', v_limit);
END;
$$;
Enter fullscreen mode Exit fullscreen mode

Prevention Tips

Validate at every layer. Never trust a raw user input or unchecked computed value as a LIMIT argument. Apply input validation in your application code first, then add a GREATEST(0, ...) guard in SQL as a second layer of defense. Set both a minimum (0) and a maximum (e.g., 10,000) to prevent runaway queries.

-- Use a domain type to enforce constraints at the DB level
CREATE DOMAIN safe_limit AS INT
    CHECK (VALUE >= 0 AND VALUE <= 10000);

-- Pair LIMIT with OFFSET defensively
SELECT * FROM users
ORDER BY id
LIMIT GREATEST(0, COALESCE(:page_size, 20))
OFFSET GREATEST(0, COALESCE(:offset_val, 0));
Enter fullscreen mode Exit fullscreen mode

Use CHECK constraints on config tables that store pagination settings, and lint your PL/pgSQL functions with thorough unit tests covering edge cases like p_limit = 0, p_limit = -1, and p_limit = NULL.


Related Errors

Error Code Name Description
2201X invalid_row_count_in_result_offset_clause Same class of error but for the OFFSET clause; always fix both together.
22003 numeric_value_out_of_range Fires when the LIMIT value exceeds the integer range.
22P02 invalid_text_representation Occurs when a non-numeric string is cast to integer for use in LIMIT.

📖 Want a more detailed guide?
Check out the full in-depth version (Korean) on oraerror.com — includes detailed analysis, additional SQL examples, and prevention tips.

Top comments (0)