PostgreSQL Error 22018: invalid character value for cast
PostgreSQL error code 22018 (invalid_character_value_for_cast) is thrown when you attempt to cast a string value into a target data type, but the string's content is incompatible with that type. This commonly happens during data migrations, ETL pipelines, or when processing raw user input that hasn't been validated before being passed to the database.
Top 3 Causes
1. Casting Non-Numeric Strings to Numeric Types
Strings containing commas, letters, or multiple decimals cannot be cast directly to INTEGER, NUMERIC, or FLOAT.
-- Triggers ERROR 22018
SELECT CAST('1,234' AS INTEGER);
SELECT CAST('abc' AS NUMERIC);
SELECT CAST('12.34.56' AS FLOAT);
-- Safe fix: strip non-numeric characters first
SELECT CAST(REPLACE('1,234', ',', '') AS INTEGER); -- returns 1234
-- Or validate with regex before casting
SELECT
CASE
WHEN col ~ '^-?[0-9]+(\.[0-9]+)?$' THEN col::NUMERIC
ELSE NULL
END AS safe_num
FROM raw_data;
2. Casting Invalid Strings to Date/Timestamp Types
Strings that don't represent a real calendar date will fail when cast to DATE or TIMESTAMP.
-- Triggers ERROR 22018
SELECT CAST('2024/13/45' AS DATE);
SELECT CAST('not-a-date' AS TIMESTAMP);
-- Safe fix: use TO_DATE with explicit format
SELECT TO_DATE('31/12/2024', 'DD/MM/YYYY'); -- returns 2024-12-31
-- Safe cast function
CREATE OR REPLACE FUNCTION safe_to_date(p_val TEXT, p_fmt TEXT DEFAULT 'YYYY-MM-DD')
RETURNS DATE LANGUAGE plpgsql AS $$
BEGIN
RETURN TO_DATE(p_val, p_fmt);
EXCEPTION WHEN OTHERS THEN RETURN NULL;
END; $$;
SELECT safe_to_date('2024-12-31'); -- 2024-12-31
SELECT safe_to_date('not-a-date'); -- NULL
3. Casting Unsupported Strings to Boolean or Strict Types
PostgreSQL's BOOLEAN type only accepts a limited set of string values. Custom strings like 'Y', 'N', or 'TRUE_VALUE' will cause this error. The same applies to strict types like UUID and INET.
-- Triggers ERROR 22018
SELECT CAST('Y' AS BOOLEAN);
SELECT CAST('not-a-uuid' AS UUID);
-- Safe fix for boolean: use CASE WHEN
SELECT
CASE
WHEN upper(flag) IN ('Y','YES','1','TRUE','T') THEN TRUE
WHEN upper(flag) IN ('N','NO','0','FALSE','F') THEN FALSE
ELSE NULL
END AS bool_flag
FROM source_table;
-- Safe UUID cast function
CREATE OR REPLACE FUNCTION safe_to_uuid(p_val TEXT)
RETURNS UUID LANGUAGE plpgsql AS $$
BEGIN
RETURN p_val::UUID;
EXCEPTION WHEN OTHERS THEN RETURN NULL;
END; $$;
Quick Fix Solutions
Build a shared library of safe casting functions to use across all queries and ETL scripts:
-- One-stop safe cast toolkit
CREATE SCHEMA IF NOT EXISTS util;
CREATE OR REPLACE FUNCTION util.to_int(v TEXT) RETURNS INTEGER
LANGUAGE plpgsql AS $$ BEGIN RETURN v::INTEGER;
EXCEPTION WHEN OTHERS THEN RETURN NULL; END; $$;
CREATE OR REPLACE FUNCTION util.to_numeric(v TEXT) RETURNS NUMERIC
LANGUAGE plpgsql AS $$ BEGIN RETURN v::NUMERIC;
EXCEPTION WHEN OTHERS THEN RETURN NULL; END; $$;
CREATE OR REPLACE FUNCTION util.to_date(v TEXT) RETURNS DATE
LANGUAGE plpgsql AS $$ BEGIN RETURN v::DATE;
EXCEPTION WHEN OTHERS THEN RETURN NULL; END; $$;
-- Usage in a real ETL scenario
SELECT
util.to_int(raw_id) AS id,
util.to_numeric(raw_price) AS price,
util.to_date(raw_date) AS event_date
FROM staging_import;
Prevention Tips
-
Validate data before it enters the database. Add
CHECKconstraints on staging tables to enforce format rules, and validate values at the application layer before issuing any SQL.
ALTER TABLE staging_orders
ADD CONSTRAINT chk_price_format
CHECK (raw_price ~ '^[0-9]+(\.[0-9]{1,2})?$');
-
Never use raw
CAST()or::in ETL code. Standardize on yourutil.safe_cast_*function library so that all type conversions are failure-tolerant and centrally maintained. This makes it easy to log bad values, returnNULLgracefully, and fix behavior in one place.
Related Errors
| Code | Name | Notes |
|---|---|---|
| 22007 | invalid_datetime_format |
Wrong date format string |
| 22003 | numeric_value_out_of_range |
Value too large for target type |
| 22P02 | invalid_text_representation |
Common with UUID, INET casts |
| 42846 | cannot_coerce |
No cast path defined between types |
📖 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)