MetricSign
Start free
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

  1. 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).
  2. 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.
  3. 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.
  4. 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'`.
  5. 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>;`
  6. 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.
  7. 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).

Frequently asked questions

Hoe los ik '22003 integer out of range' op in PostgreSQL zonder downtime?

Gebruik een shadow-kolom strategie: voeg een nieuwe BIGINT-kolom toe, backfill die via een UPDATE in batches (`UPDATE t SET new_col = old_col WHERE id BETWEEN x AND y`), swap dan de kolommen in een korte transactie. Dit vermijdt een volledige table rewrite onder load en houdt de tabel beschikbaar tijdens de migratie.

Kan 22003 ook optreden bij NUMERIC-kolommen, niet alleen bij INTEGER?

Ja. NUMERIC(p,s) heeft een vaste precisie p en schaal s. Als een waarde meer cijfers vóór de komma heeft dan p-s toelaat, gooit PostgreSQL ook 22003. Controleer de kolomdefinitie met `\d <table>` en vergelijk met de maximale bronwaarde. Oplossing: `ALTER TABLE t ALTER COLUMN c TYPE NUMERIC(20,6);`

Hoe voorkom ik dat een SERIAL primary key de 22003-limiet bereikt?

Gebruik standaard BIGSERIAL in plaats van SERIAL voor nieuwe tabellen — dat geeft een bereik tot 9,2 * 10^18. Voor bestaande tabellen: `ALTER TABLE t ALTER COLUMN id TYPE BIGINT; ALTER SEQUENCE t_id_seq AS BIGINT;` Voeg daarna een monitoring-query toe die alarmeert wanneer de sequence meer dan 80% vol is: `SELECT last_value / 2147483647.0 * 100 AS pct_used FROM t_id_seq;`

Waarom helpt een retry niet bij SQLSTATE 22003?

22003 is een schema-fout, geen transient error. De waarde past structureel niet in het kolomtype; elke retry met dezelfde data geeft exact dezelfde fout. Retry-logica is zinvol voor netwerk-timeouts (class 08) of lock-conflicts (40001), niet voor data-type-mismatches. Pas het schema of de brondata aan — daarna werkt ook de eerste poging.

Source · www.postgresql.org/docs/current/errcodes-appendix.html

Other data format errors