The PostgreSQL "Setval: value out of bounds" error occurs when you try to set a sequence to a value that falls outside its defined minimum and maximum boundaries. This typically happens when setting a sequence to 0 (which violates the default MINVALUE of 1) or to a value below the sequence's custom MINVALUE. Fix it by checking the sequence's bounds and using appropriate values.
PostgreSQL sequences generate incrementing numbers for primary keys and auto-increment columns. Every sequence has defined bounds: a minimum value (MINVALUE) and maximum value (MAXVALUE). The setval() function is used to set the sequence's current value, but it strictly enforces these boundaries. If you attempt to set the sequence to a value outside these bounds, PostgreSQL throws the "Setval: value out of bounds" error. By default, sequences have MINVALUE of 1 (or -9223372036854775808 for descending sequences) and MAXVALUE of 9223372036854775807 for bigint. A common mistake is trying to set a sequence to 0, which violates the default MINVALUE. This can happen when resetting sequences after deleting all rows, or when migrating data and trying to reset auto-increment counters. Another scenario occurs when a sequence was created with an explicit custom MINVALUE that is higher than the value you're trying to set.
Check your PostgreSQL error logs to find the exact sequence name and the bounds mentioned in the error.
-- Query the sequence metadata
SELECT sequencename, data_type, min_value, max_value, last_value, start_value
FROM pg_sequences
WHERE sequencename = 'your_sequence_name';Note the MINVALUE and MAXVALUE from the output. By default, MINVALUE is 1 for ascending sequences.
Check your application code or migration script to see what value is being passed to setval().
-- This will fail if trying to set to 0 or negative number
SELECT setval('user_id_seq', 0); -- ERROR: value 0 is out of bounds
-- Correct: set to 1 or higher (within MINVALUE..MAXVALUE)
SELECT setval('user_id_seq', 1); -- SUCCESSIf your code is setting the value to 0, this is the problem.
Ensure you're setting the sequence to a value within its defined range.
-- Set to 1 (the default minimum and typical starting value)
SELECT setval('user_id_seq', 1);
-- Or set based on the maximum existing ID in the table:
SELECT setval('user_id_seq', COALESCE((SELECT MAX(id) FROM users), 0) + 1);
-- If the table is empty and you want to restart from 1:
SELECT setval('user_id_seq', 1, false); -- false means the value hasn't been used yetThe third parameter to setval() (optional) controls whether the value should be marked as already used. false means the next nextval() will return this value; true means the next nextval() will return this value + 1.
If the sequence was created with a custom MINVALUE, respect that constraint:
-- Example: sequence with custom MINVALUE of 100
CREATE SEQUENCE product_id_seq MINVALUE 100 START 100;
-- This will fail:
SELECT setval('product_id_seq', 50); -- ERROR: 50 is below MINVALUE 100
-- This will succeed:
SELECT setval('product_id_seq', 100); -- SUCCESS
-- Check if you need to change the MINVALUE:
ALTER SEQUENCE product_id_seq MINVALUE 1; -- Then setval to 1 is allowedInstead of setval(), you can use ALTER SEQUENCE with RESTART to reset the sequence:
-- Restart sequence from 1
ALTER SEQUENCE user_id_seq RESTART WITH 1;
-- This is equivalent to setval() but with different semantics:
-- ALTER SEQUENCE doesn't increment after the restart value
-- It's useful when you want to reset the sequence completelyNote: ALTER SEQUENCE RESTART has different behavior than setval(). Choose based on your needs:
- setval(): Sets the current value; nextval() will return value+1
- ALTER SEQUENCE RESTART: Resets to start value; nextval() will return start value
When migrating data or initializing sequences, use COALESCE to handle empty tables:
-- Safe pattern: handles both empty tables and existing data
SELECT setval(
'user_id_seq',
COALESCE((SELECT MAX(id) FROM users), 0) + 1
);
-- In application code (e.g., Node.js with node-postgres):
await client.query(
`SELECT setval($1, COALESCE((SELECT MAX(id) FROM users), 0) + 1)`,
['user_id_seq']
);This pattern ensures:
- Empty tables: sequence starts at 1 (0 + 1)
- Tables with data: sequence starts at MAX(id) + 1
Update application code to respect sequence bounds:
// TypeScript/Node.js example
async function resetSequence(sequenceName: string, tableName: string, idColumn: string) {
// First, get the sequence bounds
const boundsResult = await client.query(
`SELECT min_value, max_value FROM pg_sequences WHERE sequencename = $1`,
[sequenceName]
);
if (boundsResult.rows.length === 0) {
throw new Error(`Sequence ${sequenceName} not found`);
}
const { min_value } = boundsResult.rows[0];
// Get the maximum ID in the table (or use min_value if empty)
const maxIdResult = await client.query(
`SELECT COALESCE(MAX(${idColumn}), $1 - 1) as next_value FROM ${tableName}`,
[min_value]
);
const nextValue = maxIdResult.rows[0].next_value + 1;
// Now setval with a value we know is valid
await client.query(
`SELECT setval($1, $2)`,
[sequenceName, nextValue]
);
}When designing sequences, consider:
1. Descending sequences: If you created a descending sequence (INCREMENT BY -1), its MINVALUE and MAXVALUE are reversed, and the starting value must be below the MINVALUE (which is now the upper bound).
CREATE SEQUENCE countdown MAXVALUE 1 MINVALUE -100 INCREMENT BY -1 START 1;
SELECT setval('countdown', -50); -- Valid2. CYCLE option: If a sequence reaches its MAXVALUE and has CYCLE enabled, it wraps back to MINVALUE rather than erroring:
CREATE SEQUENCE cycling_ids CYCLE MINVALUE 1 MAXVALUE 100;
-- When reaching 100, nextval() wraps to 1Be careful with CYCLE on sequences used for primary keys, as it can create duplicate IDs.
3. Bulk operations: When performing bulk inserts followed by sequence reset, use a single transaction:
BEGIN;
DELETE FROM users;
SELECT setval('user_id_seq', 1);
COMMIT;4. Monitoring sequence usage: Set up alerts for sequences approaching their MAXVALUE:
SELECT sequencename,
ROUND((last_value::float / max_value::float) * 100, 2) as percent_used
FROM pg_sequences
WHERE last_value > (max_value * 0.8)
ORDER BY percent_used DESC;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