Recipe

Convert a string to a number

Short answer: CAST(s AS INTEGER) (or DECIMAL, FLOAT) on every engine. Postgres / DuckDB / Snowflake also accept s::INTEGER. TRY_CAST (most engines) returns NULL instead of erroring on invalid input, which is useful when the source data isn't clean. Format-aware parsing (commas, currency symbols) usually requires TO_NUMBER or pre-stripping the string.

Canonical SQL #

sql
-- Portable.
SELECT CAST('42' AS INTEGER);
SELECT CAST('3.14' AS DECIMAL(10, 4));
SELECT CAST('1.5e3' AS DOUBLE);

-- Postgres / DuckDB / Snowflake / Redshift shorthand.
SELECT '42'::INTEGER;
SELECT '3.14'::NUMERIC;

-- Safe — return NULL on parse failure.
SELECT TRY_CAST('not-a-number' AS INTEGER);            -- DuckDB / SQL Server / Snowflake
SELECT SAFE_CAST('not-a-number' AS INT64);             -- BigQuery
-- Postgres has no native TRY_CAST; use a regex pre-check or a CASE.

-- Format-aware parse.
SELECT TO_NUMBER('1,234.56', '999,999.99');            -- Postgres / Oracle / Snowflake
SELECT CAST(REPLACE('1,234.56', ',', '') AS DECIMAL);  -- portable workaround

Example #

Loading SQL editor...

Common patterns #

sql
-- Pre-clean before cast.
WITH listings(price_text) AS (
  VALUES ('$1,299.00'), ('1 299 kr'), ('-42.50')
)
SELECT CAST(REGEXP_REPLACE(price_text, '[^0-9.\-]', '', 'g') AS DECIMAL(10, 2)) AS price
FROM   listings;

-- Filter only rows where the cast would succeed (Postgres workaround for TRY_CAST).
WITH t(s) AS (VALUES ('42'), ('-7'), ('abc'))
SELECT CAST(s AS INTEGER) FROM t WHERE s ~ '^-?[0-9]+$';

-- Currency parsing.
SELECT CAST(REPLACE(REPLACE('$1,234.56', '$', ''), ',', '') AS DECIMAL(10, 2));

-- Percent parsing.
SELECT CAST(REPLACE('23.4%', '%', '') AS DECIMAL(5, 2)) / 100 AS rate;

-- Boolean-ish strings.
WITH raw_input(s) AS (VALUES ('true'), ('Yes'), ('0'), ('1'))
SELECT CASE LOWER(s)
         WHEN 'true' THEN 1
         WHEN 'yes'  THEN 1
         WHEN '1'    THEN 1
         ELSE 0
       END AS as_bool
FROM   raw_input;

Dialect comparison #

EngineSyntaxGotcha
PostgreSQLCAST(s AS NUMERIC)no TRY_CAST — errors on bad input; pre-validate with a regex
MySQLCAST(s AS DECIMAL(10,2))'42abc' silently parses as 42
SQL ServerTRY_CAST(s AS DECIMAL(10,2))plain CAST errors; TRY_CONVERT also available
BigQuerySAFE_CAST(s AS NUMERIC)the safe variant is SAFE_CAST, not TRY_CAST
SnowflakeTRY_CAST(s AS NUMBER)TRY_TO_NUMBER adds a format model for '1,234.56'
OracleTO_NUMBER(s, '999,999.99')DEFAULT NULL ON CONVERSION ERROR (12.2+) for NULL-on-fail
SQLiteCAST(s AS REAL)never errors — non-numeric input becomes 0
DuckDBTRY_CAST(s AS INTEGER)returns NULL on parse failure

Dialect notes #

  • TRY_CAST: DuckDB, SQL Server, Snowflake, BigQuery (SAFE_CAST), MySQL (8.0+ via CONVERT with error handling). Not in Postgres.
  • Postgres: no TRY_CAST. Either pre-validate with regex (s ~ '^-?[0-9]+$') or wrap the cast in a function with EXCEPTION handling.
  • MySQL is permissive: CAST('42abc' AS INTEGER) returns 42 (parses as far as it can, no error). Often a bug source.
  • TO_NUMBER formats: Postgres / Oracle / Snowflake share a format string syntax ('999,999.99'). Different from printf / format style.
  • Implicit casts in some engines: SELECT '42' + 1 returns 43 on MySQL (auto-cast), errors on Postgres (mixed types). Don't rely on implicit casting.
Warning

Cast errors on string-to-number are usually data-quality bugs, not logic bugs. When CAST('1.234,56' AS DECIMAL) errors, the actual problem is the source data — European decimal format slipped through, or a CSV import didn't strip a header row, or a unit-of-measure suffix wasn't removed. The fix is upstream: clean the data at ingest, validate at the source, or change the column type. TRY_CAST quiets the symptom but masks the cause; combine it with a quality check that flags rows where the cast returned NULL. Detail: CAST.