Convert a number to a string
Short answer: CAST(n AS VARCHAR) (or TEXT, STRING) on every engine. For padding, decimals, or currency formatting: TO_CHAR(n, format) (Postgres / Oracle / Snowflake / Redshift), printf / format (DuckDB / SQLite, printf-style), FORMAT (MySQL, locale-aware; or SQL Server, .NET-style), FORMAT('%.2f', n) (BigQuery, printf-style). For dashboard-ready strings ('$1,234.56'), pre-format here; for downstream processing, raw casts are usually enough.
Canonical SQL #
-- Portable.
SELECT CAST(42 AS VARCHAR); -- '42'
SELECT CAST(3.14 AS VARCHAR); -- '3.14'
-- Postgres / DuckDB / Snowflake shorthand.
SELECT 42::VARCHAR;
SELECT 3.14::TEXT;
-- Format-aware (Postgres / Oracle / Snowflake / Redshift).
SELECT TO_CHAR(1234.5, '999,999.99'); -- ' 1,234.50'
SELECT TO_CHAR(0.234, '99%'); -- ' 23%' (* 100 implicit with %)
SELECT TO_CHAR(42, 'FM0009'); -- '0042' (FM = no padding spaces)
-- DuckDB / SQLite (printf-style).
SELECT printf('%.2f', 3.14); -- '3.14'
SELECT printf('%05d', 42); -- '00042'
-- MySQL.
SELECT FORMAT(1234.5, 2); -- '1,234.50' (locale-aware)
SELECT CONCAT(42); -- implicit cast via concat
-- BigQuery.
SELECT FORMAT('%.2f', 3.14); -- '3.14' (printf-style)
SELECT FORMAT('%,d', 1234); -- '1,234'
-- SQL Server.
SELECT FORMAT(1234.5, 'N2'); -- '1,234.50'
SELECT CONVERT(VARCHAR, 42); -- '42'Example #
Common patterns #
-- Format with two decimal places (defensive against FLOAT precision).
SELECT TO_CHAR(ROUND(price::NUMERIC, 2), 'FM999990.00') AS price_str FROM products;
SELECT FORMAT(ROUND(price, 2), 2) AS price_str FROM products; -- MySQL
-- Build an ID with leading zeros.
SELECT 'ORD-' || LPAD(CAST(id AS VARCHAR), 6, '0') AS order_code FROM orders;
-- Display-ready currency string.
SELECT '$' || TO_CHAR(amount, 'FM999,999,999.00') AS display_amount FROM invoices;
-- Concatenate number into a sentence.
SELECT 'Order ' || order_id || ' totals $' || total AS message FROM orders;
-- Convert big numbers to k / M / B for compact display.
SELECT
CASE
WHEN total >= 1e9 THEN ROUND(total / 1e9, 1) || 'B'
WHEN total >= 1e6 THEN ROUND(total / 1e6, 1) || 'M'
WHEN total >= 1e3 THEN ROUND(total / 1e3, 1) || 'K'
ELSE total::VARCHAR
END AS compact
FROM revenue;Dialect comparison #
| Engine | Syntax | Gotcha |
|---|---|---|
| PostgreSQL | TO_CHAR(n, 'FM999,990.00') | 9 pads with spaces unless prefixed with FM |
| MySQL | FORMAT(n, 2) | locale-aware — separators change with the connection locale |
| SQL Server | FORMAT(n, 'N2') | .NET format strings; slow on large row counts |
| BigQuery | FORMAT('%.2f', n) | printf-style, format string first |
| Snowflake | TO_CHAR(n, '999,990.00') | Postgres/Oracle-style format model |
| Oracle | TO_CHAR(n, '999,990.00') | a number too wide for the format prints # characters |
| SQLite | printf('%.2f', n) | format() is a synonym in 3.38+ |
| DuckDB | printf('%.2f', n) | no TO_CHAR — printf / format only |
Format string conventions #
- Postgres / Oracle / Snowflake
TO_CHAR:'9'(digit, prints space if zero),'0'(digit, prints zero),','(thousands sep),'.'(decimal point),'FM'prefix to suppress padding spaces,'$'(currency sign at locale position). DuckDB has noTO_CHAR; its canonical number formatting isprintf/format. - MySQL
FORMAT(n, decimals): locale-aware; locale defaults to current connection. - SQL Server
FORMAT: .NET-style format strings —'N2','C','P', custom like'#,##0.00'. Slow on large datasets;STRandCONVERTare faster but less expressive. - BigQuery
FORMAT:printf-style —'%d','%f','%.2f','%,d'. Fast. - DuckDB / SQLite:
printffunction with same syntax as BigQuery.
**Format at the display layer, not at the warehouse layer.** Storing "$1,234.56" as a string in a database column means re-parsing every time you sum or compare. Store DECIMAL(10, 2) and let the BI tool, app code, or report renderer format. The exception: when SQL is the only place that touches the value before it reaches the human (e.g., emailing a CSV directly from a query). Otherwise keep numbers as numbers as long as possible. Detail: CAST, Round to decimal places.