Data does not always arrive in the type you need. A price stored as text cannot participate in arithmetic. A date buried inside a string cannot be compared to CURRENT_DATE. An integer divided by another integer silently drops the decimal portion. A numeric ID needs to be concatenated into a message string. These situations happen constantly in real-world databases.
SQL CAST is the standard tool for converting a value from one data type to another. It lets you explicitly tell the database "treat this text as a number" or "turn this timestamp into a date." Understanding type conversion is essential because many subtle bugs, from incorrect calculations to failed queries, trace back to values being in the wrong type at the wrong time.
This guide covers explicit conversion with CAST and CONVERT, explains how implicit conversion works behind the scenes, walks through the most common conversion scenarios with examples, and highlights the pitfalls that catch developers off guard.
The Sample Data
All examples use these tables:
CREATE TABLE raw_imports (
record_id INT PRIMARY KEY,
product_name VARCHAR(100),
price_text VARCHAR(20),
quantity_text VARCHAR(10),
order_date_text VARCHAR(20),
is_active_text VARCHAR(5),
rating_text VARCHAR(10)
);
CREATE TABLE orders (
order_id INT PRIMARY KEY,
customer_id INT,
order_date TIMESTAMP,
total_amount DECIMAL(10,2),
item_count INT
);
INSERT INTO raw_imports VALUES
(1, 'Wireless Mouse', '25.99', '150', '2024-06-15', 'true', '4.5'),
(2, 'USB-C Cable', '9.99', '300', '2024-06-18', 'true', '3.8'),
(3, 'Notebook A5', '4.50', '500', '06/20/2024', 'false', '4.2'),
(4, 'Mechanical Keyboard', '89.99', '45', '2024-06-22', 'true', '4.9'),
(5, 'Desk Lamp', 'N/A', '0', '2024-06-25', 'false', '3.0'),
(6, 'Standing Desk', '299.99', '12', '2024-06-28', 'true', NULL);
INSERT INTO orders VALUES
(1001, 1, '2024-01-15 09:30:00', 155.97, 3),
(1002, 2, '2024-03-22 14:15:00', 299.99, 1),
(1003, 1, '2024-06-05 11:00:00', 45.48, 2),
(1004, 3, '2024-06-10 16:45:00', 89.99, 1),
(1005, 2, '2024-06-14 08:20:00', 9.99, 4);CAST: The SQL Standard
CAST is the SQL standard syntax for explicit type conversion. It works in every major database: PostgreSQL, MySQL, SQL Server, Oracle, and SQLite.
Syntax
CAST(expression AS target_type)The expression can be a column, a literal value, or any valid SQL expression. The target type is the data type you want the result to be.
String to Number
The most common conversion: turning text that contains a number into an actual numeric type.
SELECT
product_name,
price_text,
CAST(price_text AS DECIMAL(10,2)) AS price_number,
CAST(quantity_text AS INT) AS quantity
FROM raw_imports
WHERE price_text != 'N/A';| product_name | price_text | price_number | quantity |
|---|---|---|---|
| Wireless Mouse | 25.99 | 25.99 | 150 |
| USB-C Cable | 9.99 | 9.99 | 300 |
| Notebook A5 | 4.50 | 4.50 | 500 |
| Mechanical Keyboard | 89.99 | 89.99 | 45 |
| Standing Desk | 299.99 | 299.99 | 12 |
Once converted, you can perform arithmetic:
SELECT
product_name,
CAST(price_text AS DECIMAL(10,2)) * CAST(quantity_text AS INT) AS inventory_value
FROM raw_imports
WHERE price_text != 'N/A';| product_name | inventory_value |
|---|---|
| Wireless Mouse | 3898.50 |
| USB-C Cable | 2997.00 |
| Notebook A5 | 2250.00 |
| Mechanical Keyboard | 4049.55 |
| Standing Desk | 3599.88 |
Number to String
Converting numbers to text for concatenation or formatted output:
SELECT
'Order #' || CAST(order_id AS VARCHAR(10)) || ' totals $' || CAST(total_amount AS VARCHAR(20)) AS summary
FROM orders;| summary |
|---|
| Order #1001 totals $155.97 |
| Order #1002 totals $299.99 |
| Order #1003 totals $45.48 |
| Order #1004 totals $89.99 |
| Order #1005 totals $9.99 |
String to Date
Converting text dates into proper date types:
SELECT
product_name,
order_date_text,
CAST(order_date_text AS DATE) AS order_date
FROM raw_imports
WHERE order_date_text LIKE '____-__-__';| product_name | order_date_text | order_date |
|---|---|---|
| Wireless Mouse | 2024-06-15 | 2024-06-15 |
| USB-C Cable | 2024-06-18 | 2024-06-18 |
| Mechanical Keyboard | 2024-06-22 | 2024-06-22 |
| Desk Lamp | 2024-06-25 | 2024-06-25 |
| Standing Desk | 2024-06-28 | 2024-06-28 |
Notice we filtered out record 3 (Notebook A5) because its date format 06/20/2024 is MM/DD/YYYY, which CAST may not recognize depending on the database's locale settings.
Timestamp to Date (Stripping Time)
SELECT
order_id,
order_date,
CAST(order_date AS DATE) AS date_only
FROM orders;| order_id | order_date | date_only |
|---|---|---|
| 1001 | 2024-01-15 09:30:00 | 2024-01-15 |
| 1002 | 2024-03-22 14:15:00 | 2024-03-22 |
| 1003 | 2024-06-05 11:00:00 | 2024-06-05 |
| 1004 | 2024-06-10 16:45:00 | 2024-06-10 |
| 1005 | 2024-06-14 08:20:00 | 2024-06-14 |
Integer to Decimal (Fixing Integer Division)
One of the most practical uses of CAST: preventing integer division truncation.
-- Without CAST: integer division truncates
SELECT 7 / 2 AS int_result;
-- PostgreSQL/SQL Server/SQLite: 3 (not 3.5!)
-- With CAST: decimal division preserves precision
SELECT CAST(7 AS DECIMAL(10,2)) / 2 AS decimal_result;
-- Result: 3.50Practical example with real columns:
SELECT
order_id,
total_amount,
item_count,
total_amount / item_count AS without_cast,
total_amount / CAST(item_count AS DECIMAL(10,2)) AS with_cast
FROM orders;| order_id | total_amount | item_count | without_cast | with_cast |
|---|---|---|---|---|
| 1001 | 155.97 | 3 | 51.9900000000000000 | 51.9900000000000000 |
| 1002 | 299.99 | 1 | 299.9900000000000000 | 299.9900000000000000 |
| 1003 | 45.48 | 2 | 22.7400000000000000 | 22.7400000000000000 |
| 1004 | 89.99 | 1 | 89.9900000000000000 | 89.9900000000000000 |
| 1005 | 9.99 | 4 | 2.4975000000000000 | 2.4975000000000000 |
In this case total_amount is already DECIMAL, so dividing by an INT produces a decimal result in most databases. But when both columns are integers, the cast becomes critical.
PostgreSQL Shorthand: :: Operator
PostgreSQL offers a concise casting syntax using the :: operator:
-- These are equivalent in PostgreSQL:
SELECT CAST('42' AS INTEGER);
SELECT '42'::INTEGER;
SELECT CAST(order_date AS DATE);
SELECT order_date::DATE;
SELECT CAST(price AS VARCHAR);
SELECT price::VARCHAR;
SELECT price::TEXT;The :: operator is PostgreSQL-specific and not portable to other databases, but it is so concise that it is used almost universally in PostgreSQL code:
SELECT
product_name,
price_text::DECIMAL(10,2) AS price,
quantity_text::INT AS quantity,
order_date_text::DATE AS order_date
FROM raw_imports
WHERE price_text != 'N/A'
AND order_date_text LIKE '____-__-__';| product_name | price | quantity | order_date |
|---|---|---|---|
| Wireless Mouse | 25.99 | 150 | 2024-06-15 |
| USB-C Cable | 9.99 | 300 | 2024-06-18 |
| Mechanical Keyboard | 89.99 | 45 | 2024-06-22 |
| Standing Desk | 299.99 | 12 | 2024-06-28 |
CONVERT: SQL Server's Extended Conversion
SQL Server supports both CAST (standard) and its own CONVERT function, which adds a style parameter for controlling date and number formatting during conversion.
Syntax
CONVERT(target_type, expression, style)The third argument (style) is optional and is primarily used with date/time conversions.
Date Formatting with CONVERT Styles
SELECT
order_date,
CONVERT(VARCHAR, order_date, 23) AS iso_format, -- yyyy-mm-dd
CONVERT(VARCHAR, order_date, 101) AS us_format, -- mm/dd/yyyy
CONVERT(VARCHAR, order_date, 103) AS uk_format, -- dd/mm/yyyy
CONVERT(VARCHAR, order_date, 104) AS german_format, -- dd.mm.yyyy
CONVERT(VARCHAR, order_date, 108) AS time_only, -- hh:mm:ss
CONVERT(VARCHAR, order_date, 120) AS odbc_format -- yyyy-mm-dd hh:mm:ss
FROM orders
WHERE order_id = 1001;| order_date | iso_format | us_format | uk_format | german_format | time_only | odbc_format |
|---|---|---|---|---|---|---|
| 2024-01-15 09:30:00.0000000 | 2024-01-15 | 01/15/2024 | 15/01/2024 | 15.01.2024 | 09:30:00 | 2024-01-15 09:30:00 |
Common SQL Server CONVERT style codes:
| Style | Format | Example |
|---|---|---|
| 23 | yyyy-mm-dd |
2024-06-15 |
| 101 | mm/dd/yyyy |
06/15/2024 |
| 103 | dd/mm/yyyy |
15/06/2024 |
| 104 | dd.mm.yyyy |
15.06.2024 |
| 108 | hh:mm:ss |
14:30:00 |
| 112 | yyyymmdd |
20240615 |
| 120 | yyyy-mm-dd hh:mm:ss |
2024-06-15 14:30:00 |
| 126 | ISO 8601 | 2024-06-15T14:30:00 |
Parsing Date Strings with CONVERT
-- SQL Server: parse a date string in a specific format
SELECT CONVERT(DATE, '15/06/2024', 103) AS parsed_date;
-- Result: 2024-06-15
SELECT CONVERT(DATE, '06/15/2024', 101) AS parsed_date;
-- Result: 2024-06-15The style code tells SQL Server which format the input string uses, preventing ambiguity.
CAST vs CONVERT in SQL Server
| Feature | CAST |
CONVERT |
|---|---|---|
| SQL Standard | Yes | No (SQL Server only) |
| Date formatting styles | No | Yes |
| Readability | Slightly clearer | Slightly more compact |
| Performance | Same | Same |
| Portability | All databases | SQL Server only |
MySQL-Specific Conversions
MySQL supports CAST with a slightly different set of target types:
-- MySQL target types in CAST
SELECT
CAST('42' AS SIGNED) AS int_val, -- integer
CAST('42' AS UNSIGNED) AS uint_val, -- unsigned integer
CAST('3.14' AS DECIMAL(5,2)) AS dec_val, -- decimal
CAST('2024-06-15' AS DATE) AS date_val, -- date
CAST('14:30:00' AS TIME) AS time_val, -- time
CAST(42 AS CHAR) AS str_val; -- string| int_val | uint_val | dec_val | date_val | time_val | str_val |
|---|---|---|---|---|---|
| 42 | 42 | 3.14 | 2024-06-15 | 14:30:00 | 42 |
MySQL CONVERT Function
MySQL's CONVERT is different from SQL Server's. It is used for character set conversion, not date formatting:
-- MySQL: CONVERT for character set conversion
SELECT CONVERT('hello' USING utf8mb4);
SELECT CONVERT(column_name, CHAR(50)); -- type conversion (alternative CAST syntax)
-- MySQL: date formatting uses DATE_FORMAT, not CONVERT
SELECT DATE_FORMAT(order_date, '%d/%m/%Y') FROM orders;Implicit vs Explicit Conversion
Not all type conversions require CAST. Databases automatically convert between compatible types in many situations. This is called implicit conversion (or type coercion).
Where Implicit Conversion Happens
Arithmetic operations:
-- INT + DECIMAL: INT is implicitly promoted to DECIMAL
SELECT 10 + 3.5;
-- Result: 13.5 (no CAST needed)
-- INT * FLOAT: INT promoted to FLOAT
SELECT 7 * 2.5;
-- Result: 17.5Comparison with literals:
-- Comparing a DATE column to a string literal
SELECT * FROM orders WHERE order_date > '2024-06-01';
-- The string '2024-06-01' is implicitly converted to a timestampINSERT with compatible types:
-- Inserting a string into a DECIMAL column
INSERT INTO orders (order_id, total_amount) VALUES (1006, '199.99');
-- '199.99' is implicitly converted to DECIMAL(10,2)CONCAT in MySQL:
-- MySQL: CONCAT implicitly converts numbers to strings
SELECT CONCAT('Order #', 1001, ' total: $', 155.97);
-- Result: 'Order #1001 total: $155.97'When Implicit Conversion Goes Wrong
Implicit conversion is convenient but can cause problems:
Silent data loss:
-- Inserting a decimal into an INT column
INSERT INTO orders (order_id, item_count) VALUES (1006, 3.7);
-- item_count becomes 3 (or 4, depending on database)
-- No error, no warning. The .7 is silently lost.Unexpected comparison behavior:
-- MySQL: comparing a VARCHAR column to a number
SELECT * FROM raw_imports WHERE price_text = 0;In MySQL, this compares every row because MySQL converts price_text to a number. Any non-numeric string (like 'N/A') converts to 0, matching the condition. This returns the Desk Lamp row unexpectedly.
-- MySQL's implicit conversion produces surprising results
SELECT 'N/A' = 0; -- Result: 1 (TRUE!)
SELECT 'hello' = 0; -- Result: 1 (TRUE!)
SELECT '5abc' + 0; -- Result: 5
SELECT 'abc5' + 0; -- Result: 0Performance impact of implicit conversion:
-- If customer_id is INT but you compare to a string:
SELECT * FROM orders WHERE customer_id = '2';
-- The database may convert EVERY customer_id to VARCHAR for comparison
-- This prevents index usage and causes a full table scanExplicit Is Always Better
-- Implicit: works but unclear intent, risky
SELECT * FROM orders WHERE order_date > '2024-06-01';
-- Explicit: clear intent, no ambiguity
SELECT * FROM orders WHERE order_date > CAST('2024-06-01' AS TIMESTAMP);
-- Explicit: guaranteed correct division
SELECT CAST(total_amount AS DECIMAL(12,4)) / CAST(item_count AS DECIMAL(12,4))
FROM orders;Common Conversion Scenarios
Scenario 1: Cleaning Imported CSV Data
Raw imports often arrive as all-text. Convert to proper types for analysis:
SELECT
record_id,
product_name,
CASE
WHEN price_text ~ '^\d+\.?\d*$' THEN CAST(price_text AS DECIMAL(10,2))
ELSE NULL
END AS price,
CAST(quantity_text AS INT) AS quantity,
CASE
WHEN order_date_text ~ '^\d{4}-\d{2}-\d{2}$' THEN CAST(order_date_text AS DATE)
ELSE NULL
END AS order_date,
CAST(is_active_text AS BOOLEAN) AS is_active
FROM raw_imports;| record_id | product_name | price | quantity | order_date | is_active |
|---|---|---|---|---|---|
| 1 | Wireless Mouse | 25.99 | 150 | 2024-06-15 | true |
| 2 | USB-C Cable | 9.99 | 300 | 2024-06-18 | true |
| 3 | Notebook A5 | 4.50 | 500 | NULL | false |
| 4 | Mechanical Keyboard | 89.99 | 45 | 2024-06-22 | true |
| 5 | Desk Lamp | NULL | 0 | 2024-06-25 | false |
| 6 | Standing Desk | 299.99 | 12 | 2024-06-28 | true |
The CASE expressions handle invalid values ('N/A' for price, wrong date format) by returning NULL instead of crashing.
Scenario 2: Building Display Strings
SELECT
order_id,
'Ordered on ' ||
TO_CHAR(order_date, 'Mon DD, YYYY') ||
' - ' ||
CAST(item_count AS VARCHAR) ||
' item(s) totaling $' ||
CAST(total_amount AS VARCHAR)
AS order_summary
FROM orders;| order_summary |
|---|
| Ordered on Jan 15, 2024 - 3 item(s) totaling $155.97 |
| Ordered on Mar 22, 2024 - 1 item(s) totaling $299.99 |
| Ordered on Jun 05, 2024 - 2 item(s) totaling $45.48 |
| Ordered on Jun 10, 2024 - 1 item(s) totaling $89.99 |
| Ordered on Jun 14, 2024 - 4 item(s) totaling $9.99 |
Scenario 3: Grouping by Date Components
-- Group timestamps by date (strip time component)
SELECT
CAST(order_date AS DATE) AS order_day,
COUNT(*) AS order_count,
SUM(total_amount) AS daily_total
FROM orders
GROUP BY CAST(order_date AS DATE)
ORDER BY order_day;| order_day | order_count | daily_total |
|---|---|---|
| 2024-01-15 | 1 | 155.97 |
| 2024-03-22 | 1 | 299.99 |
| 2024-06-05 | 1 | 45.48 |
| 2024-06-10 | 1 | 89.99 |
| 2024-06-14 | 1 | 9.99 |
Scenario 4: Conditional Type Conversion
Handle mixed data with CASE and CAST:
SELECT
product_name,
price_text,
CASE
WHEN price_text = 'N/A' THEN 0.00
WHEN price_text IS NULL THEN 0.00
ELSE CAST(price_text AS DECIMAL(10,2))
END AS price
FROM raw_imports;| product_name | price_text | price |
|---|---|---|
| Wireless Mouse | 25.99 | 25.99 |
| USB-C Cable | 9.99 | 9.99 |
| Notebook A5 | 4.50 | 4.50 |
| Mechanical Keyboard | 89.99 | 89.99 |
| Desk Lamp | N/A | 0.00 |
| Standing Desk | 299.99 | 299.99 |
Scenario 5: Safe Conversion Functions
PostgreSQL and SQL Server offer "try" variants that return NULL instead of errors for invalid conversions:
PostgreSQL (using exception handling in a function or using regex):
-- PostgreSQL: validate before casting
SELECT
price_text,
CASE
WHEN price_text ~ '^\d+\.?\d*$'
THEN CAST(price_text AS DECIMAL(10,2))
ELSE NULL
END AS safe_price
FROM raw_imports;SQL Server: TRY_CAST and TRY_CONVERT:
-- SQL Server: TRY_CAST returns NULL instead of error
SELECT
price_text,
TRY_CAST(price_text AS DECIMAL(10,2)) AS safe_price,
TRY_CONVERT(DATE, order_date_text, 23) AS safe_date
FROM raw_imports;Common Conversion Pitfalls
Pitfall 1: CAST Failure Crashes the Entire Query
A single invalid value in any row causes CAST to fail for the whole query:
Crashes:
SELECT CAST(price_text AS DECIMAL(10,2)) FROM raw_imports;
-- ERROR: invalid input syntax for type numeric: "N/A"The 'N/A' in record 5 cannot be converted to DECIMAL, and the entire query fails.
Fix: Filter or handle invalid values before casting:
-- Option A: Filter out bad rows
SELECT CAST(price_text AS DECIMAL(10,2))
FROM raw_imports
WHERE price_text NOT IN ('N/A', '', 'null');
-- Option B: CASE with validation
SELECT
CASE
WHEN price_text ~ '^\d+\.?\d*$' THEN CAST(price_text AS DECIMAL(10,2))
ELSE NULL
END AS safe_price
FROM raw_imports;
-- Option C: TRY_CAST (SQL Server only)
SELECT TRY_CAST(price_text AS DECIMAL(10,2)) FROM raw_imports;Pitfall 2: Precision Loss When Casting Decimals
-- Casting a precise number to fewer decimal places
SELECT CAST(3.14159 AS DECIMAL(5,2)) AS truncated;
-- Result: 3.14 (digits after the 2nd decimal place are lost)
-- Casting a large number to a type that's too small
SELECT CAST(123456.789 AS DECIMAL(5,2));
-- ERROR: numeric field overflow (5 total digits can't hold 123456)Always ensure the target type's precision and scale can accommodate your data.
Pitfall 3: Date Format Ambiguity
-- Is '01/02/2024' January 2nd or February 1st?
SELECT CAST('01/02/2024' AS DATE);The answer depends on the database's locale settings:
- US format (
MM/DD/YYYY): January 2nd - European format (
DD/MM/YYYY): February 1st
Fix: Use unambiguous ISO format (YYYY-MM-DD) or specify the format explicitly:
-- ISO format: always unambiguous
SELECT CAST('2024-01-02' AS DATE);
-- Always January 2nd, regardless of locale
-- PostgreSQL: TO_DATE with explicit format
SELECT TO_DATE('01/02/2024', 'MM/DD/YYYY');
-- MySQL: STR_TO_DATE with explicit format
SELECT STR_TO_DATE('01/02/2024', '%m/%d/%Y');
-- SQL Server: CONVERT with style code
SELECT CONVERT(DATE, '01/02/2024', 101); -- US format
SELECT CONVERT(DATE, '01/02/2024', 103); -- European formatPitfall 4: Boolean Conversion Inconsistency
Databases handle booleans differently:
-- PostgreSQL: native boolean type
SELECT CAST('true' AS BOOLEAN); -- true
SELECT CAST('yes' AS BOOLEAN); -- ERROR! (only 'true'/'false', 't'/'f', '1'/'0')
SELECT CAST(1 AS BOOLEAN); -- true
SELECT CAST(0 AS BOOLEAN); -- false
-- MySQL: no native BOOLEAN in CAST
SELECT CAST('true' AS UNSIGNED); -- 0 (string converted to number)
SELECT 'true' = TRUE; -- 0 (FALSE, because TRUE = 1)
-- SQL Server: no BOOLEAN type at all
SELECT CAST(1 AS BIT); -- 1
SELECT CAST(0 AS BIT); -- 0
SELECT CAST('true' AS BIT); -- ERROR!Pitfall 5: NULL Handling in CAST
CAST(NULL AS any_type) always returns NULL. This is correct behavior, but it can surprise you when combined with other operations:
SELECT CAST(NULL AS INT); -- NULL
SELECT CAST(NULL AS VARCHAR); -- NULL
SELECT CAST(NULL AS INT) + 10; -- NULL (not 10)
SELECT CAST(NULL AS VARCHAR) || 'hello'; -- NULL in PostgreSQLUse COALESCE to provide fallback values:
SELECT
product_name,
COALESCE(CAST(rating_text AS DECIMAL(3,1)), 0.0) AS rating
FROM raw_imports;| product_name | rating |
|---|---|
| Wireless Mouse | 4.5 |
| USB-C Cable | 3.8 |
| Notebook A5 | 4.2 |
| Mechanical Keyboard | 4.9 |
| Desk Lamp | 3.0 |
| Standing Desk | 0.0 |
Pitfall 6: Casting in WHERE Prevents Index Usage
-- Slow: CAST on the column prevents index use
SELECT * FROM orders
WHERE CAST(order_date AS DATE) = '2024-06-10';
-- Fast: range comparison uses the index
SELECT * FROM orders
WHERE order_date >= '2024-06-10'
AND order_date < '2024-06-11';
-- Also fast: CAST on the literal, not the column
SELECT * FROM orders
WHERE order_date >= CAST('2024-06-10' AS TIMESTAMP)
AND order_date < CAST('2024-06-11' AS TIMESTAMP);Type Conversion Compatibility Reference
| From → To | String to Number | Number to String | String to Date | Date to String | Integer to Decimal | Timestamp to Date |
|---|---|---|---|---|---|---|
| PostgreSQL | CAST(x AS INT/DECIMAL) or x::INT |
CAST(x AS VARCHAR) or x::TEXT |
CAST(x AS DATE) or TO_DATE() |
TO_CHAR() |
CAST(x AS DECIMAL) |
CAST(x AS DATE) or x::DATE |
| MySQL | CAST(x AS SIGNED/DECIMAL) |
CAST(x AS CHAR) |
CAST(x AS DATE) or STR_TO_DATE() |
DATE_FORMAT() |
CAST(x AS DECIMAL) |
CAST(x AS DATE) or DATE(x) |
| SQL Server | CAST(x AS INT/DECIMAL) or TRY_CAST() |
CAST(x AS VARCHAR) or CONVERT() |
CAST(x AS DATE) or CONVERT(DATE, x, style) |
CONVERT(VARCHAR, x, style) or FORMAT() |
CAST(x AS DECIMAL) |
CAST(x AS DATE) |
| Oracle | TO_NUMBER(x) |
TO_CHAR(x) |
TO_DATE(x, format) |
TO_CHAR(x, format) |
CAST(x AS NUMBER) |
CAST(x AS DATE) or TRUNC(x) |
Quick Reference
| Task | Syntax | Example |
|---|---|---|
| String to integer | CAST(x AS INT) |
CAST('42' AS INT) → 42 |
| String to decimal | CAST(x AS DECIMAL(p,s)) |
CAST('3.14' AS DECIMAL(5,2)) → 3.14 |
| Number to string | CAST(x AS VARCHAR(n)) |
CAST(42 AS VARCHAR(10)) → '42' |
| String to date | CAST(x AS DATE) |
CAST('2024-06-15' AS DATE) |
| Timestamp to date | CAST(x AS DATE) |
CAST(NOW() AS DATE) |
| Fix integer division | CAST(x AS DECIMAL) |
CAST(7 AS DECIMAL) / 2 → 3.5 |
| PostgreSQL shorthand | x::type |
'42'::INT, NOW()::DATE |
| SQL Server safe cast | TRY_CAST(x AS type) |
TRY_CAST('N/A' AS INT) → NULL |
| SQL Server date format | CONVERT(type, x, style) |
CONVERT(VARCHAR, date, 23) |
Summary
SQL CAST is the standard mechanism for converting values between data types. It is fundamental to working with real-world data where types do not always match what your queries need.
Key takeaways:
CAST(expression AS type)is the SQL standard syntax supported by every major database. Use it as your default conversion tool.CONVERTin SQL Server adds date/time formatting style codes thatCASTcannot express. MySQL'sCONVERTis different and handles character set conversion.- PostgreSQL's
::operator is a concise shorthand forCASTthat is idiomatic in PostgreSQL code but not portable. - Implicit conversion happens automatically when the database can safely promote types (like
INTtoDECIMALin arithmetic). But it can also cause silent bugs, especially in MySQL where string-to-number conversion treats non-numeric strings as0. - Always prefer explicit conversion over implicit. It documents your intent, prevents subtle bugs, and makes queries portable.
- Handle invalid data before casting. Use
CASEwith validation,TRY_CAST(SQL Server), or regex checks (PostgreSQL) to avoid query-crashing conversion errors. - Use ISO 8601 format (
YYYY-MM-DD) for date strings to eliminate locale ambiguity. - Never cast the column side of a
WHEREcomparison when an index exists. Cast the literal side instead to preserve index usage. - Integer division silently truncates decimals in PostgreSQL, SQL Server, and SQLite. Cast at least one operand to
DECIMALor multiply by1.0to force decimal division.
Type conversion is one of those skills that seems simple until it bites you. Master CAST, understand when implicit conversion helps versus hurts, and always validate data before converting, and you will avoid an entire category of production bugs.