Medium severitydata format
PostgreSQL Error:
22003
What does this error mean?
PostgreSQL gooit SQLSTATE 22003 wanneer een numerieke waarde niet past in het doeldatatype van een kolom. In een data-pipeline betekent dit dat een INSERT of UPDATE geblokkeerd wordt: de rij wordt niet weggeschreven en de transactie rolt terug. De engineer ziet doorgaans een fout als 'ERROR: integer out of range' of 'numeric field overflow', gevolgd door een gefaalde pipeline-run. Het probleem treedt typisch op bij tabelkolommen die ooit als INTEGER (max ~2,1 miljard) of SMALLINT (max 32.767) zijn gedefineerd, maar waarbij de brondata inmiddels buiten dat bereik valt — bijvoorbeeld door groei van een auto-increment primary key, een financieel bedrag in centen, of een aggregatieberekening.
Common causes
- 1De kolom is gedefineerd als INTEGER (32-bit, max 2.147.483.647) maar de brondata — bijvoorbeeld een transactie-ID of klantnummer uit een extern systeem — heeft dat plafond bereikt of overschreden. De bronkolom is waarschijnlijk al BIGINT, maar de doeltabel is nooit gemigreerd.
- 2Een SERIAL of BIGSERIAL primary key nadert of overschrijdt de sequencelimiet. Bij SERIAL loopt de onderliggende sequence tot 2.147.483.647; zodra die vol is, mislukken alle nieuwe INSERTs met 22003 terwijl bestaande rows intact blijven.
- 3ETL-transformaties produceren overflow: een berekening als `quantity * unit_price` in de pipeline levert een INTEGER-resultaat dat groter is dan 2,1 miljard, ook al zijn de invoerwaarden individueel klein. PostgreSQL evalueert de expressie in het type van de invoerkolommen tenzij je expliciet cast.
- 4De bronbron gebruikt NUMERIC of DECIMAL met hoge precisie (bijv. NUMERIC(20,6)) en de doeltabel heeft NUMERIC(10,2). Bij het laden past de waarde niet in de lagere precisie, wat ook 22003 triggert — niet alleen bij gehele getallen maar ook bij decimale overflow.
- 5Een SMALLINT-kolom (max 32.767) wordt gevuld vanuit een bron die gewone integers levert, bijvoorbeeld een tellerkolom die in productie voorbij 32.767 is gegroeid. Dit patroon is subtiel: de kolom werkte jaren prima en faalt pas wanneer een drempel wordt bereikt.
- 6Aggregate-queries in een stored procedure of view berekenen SUM() over een INTEGER-kolom. Als de som van alle waarden de 32-bit grens overschrijdt, faalt niet de opslag maar de query zelf, wat downstream views en materialized tables kapot maakt.
- 7Bij gebruik van Foreign Data Wrappers (FDW) of logical replication naar een replica met een afwijkend schema: de bronserver schrijft BIGINT-waarden weg die de replica probeert te mappen naar een INTEGER-kolom met hetzelfde kolomnaam maar een smaller type.
How to fix it
- 1Stap 1 — Bevestig het kolomtype en de huidige maximumwaarde: `SELECT column_name, data_type, numeric_precision FROM information_schema.columns WHERE table_name = '<table_name>';` en `SELECT MAX(<column_name>) FROM <table_name>;` — vergelijk de max met de typelimiet (INTEGER: 2.147.483.647, SMALLINT: 32.767).
- 2Stap 2 — Migreer de kolom naar BIGINT. Voor een reguliere kolom: `ALTER TABLE <table_name> ALTER COLUMN <column_name> TYPE BIGINT;` Dit herschrijft de tabel; plan dit in een onderhoudsvenster voor grote tabellen. Op Postgres 12+ kun je CONCURRENTLY overwegen via een nieuwe kolom + backfill + rename-strategie om locking te vermijden.
- 3Stap 3 — Als het een SERIAL primary key betreft, vervang dan ook de sequence: `ALTER SEQUENCE <table_name>_id_seq AS BIGINT; ALTER TABLE <table_name> ALTER COLUMN id TYPE BIGINT;` Controleer daarna met `SELECT last_value, max_value FROM <table_name>_id_seq;` of de nieuwe limiet (9,2 * 10^18) correct is overgenomen.
- 4Stap 4 — Voeg een pre-load validatie toe in de ETL-pipeline. In dbt: `{% if execute %} {{ assert_column_values_between(ref('stg_orders'), 'order_id', 0, 9223372036854775807) }} {% endif %}` of in een Python-stap: `assert df['order_id'].max() <= 9_223_372_036_854_775_807, '22003 risk: value exceeds BIGINT'`.
- 5Stap 5 — Controleer afgeleide objecten na de migratie. Views, materialized views en foreign keys op de gewijzigde kolom moeten opnieuw worden gevalideerd: `SELECT viewname, definition FROM pg_views WHERE definition LIKE '%<table_name>%';` Refresh materialized views handmatig: `REFRESH MATERIALIZED VIEW <view_name>;`
- 6Stap 6 — Voor NUMERIC overflow: pas de column definitie aan naar een hogere precisie, bijv. `ALTER TABLE t ALTER COLUMN amount TYPE NUMERIC(20,6);` en controleer in de bronquery of er geen impliciete cast plaatsvindt die precisie afkapt.
- 7Stap 7 — Valideer de fix door een test-INSERT uit te voeren met de grenswaarde: `INSERT INTO <table_name> (<column_name>) VALUES (9223372036854775807);` en verifieer dat de pipeline opnieuw succesvol draait. Draai daarna `ANALYZE <table_name>;` zodat de query planner de nieuwe statistieken heeft.
Example log output
ERROR: integer out of range
CONTEXT: COPY orders, line 4847193: "4847193,2147483648,2026-04-01,EUR"
DETAIL: Failing row contains (4847193, 2147483648, 2026-04-01, EUR).