<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. SQL
  3. Functions and expressions
All lessons in this course

Lesson 17 of 54 14 minBeginner

Converting types: CAST, CONVERT and TRY_CAST

When SQL Server converts for you and when you must ask, the rules of data type precedence, date styles with CONVERT, safe conversion with TRY_CAST, and the traps in empty strings and imported text.

Every value has a type, and combining values of different types needs a conversion. Sometimes SQL Server does it silently (implicit conversion); sometimes you must ask for it (explicit conversion, with CAST or CONVERT). Knowing which happens, and in which direction, avoids a whole class of bugs.

Data type precedence

When an operator meets two different types, the one with lower precedence is converted to the one with higher precedence. Numbers outrank text, so + between text and an int is addition, not joining:

Query.sql
SELECT '10' + 5    AS TextPlusInt,       '10' + '5'  AS TextPlusText,       CONCAT('10', 5) AS Joined,       10 + 2.5    AS IntPlusDecimal;
Output
TextPlusInt  TextPlusText  Joined  IntPlusDecimal
-----------  ------------  ------  --------------
         15  105           105               12.5
Higher precedence winsExamples
date and time typesdatetimeoffset > datetime2 > datetime > date
approximate numbersfloat > real
exact numbersdecimal > money > bigint > int > smallint > tinyint > bit
text (lowest)nvarchar > nchar > varchar > char

So 'abc' + 5 fails: SQL Server tries to turn 'abc' into an int. And comparing a varchar column with an int value converts the column, row by row, which is both slow and a source of conversion errors.

CAST and CONVERT

CAST(x AS type) is standard SQL. CONVERT(type, x [, style]) is T-SQL’s version, with an optional style number that controls how dates and numbers are written as text or read from it:

Query.sql
SELECT CAST('42' AS int) AS CastText,       CAST(18.5 AS varchar(10)) AS NumberAsText,       CONVERT(varchar(10), CAST('2024-12-31' AS date), 103) AS Style103,       CONVERT(varchar(8),  CAST('2024-12-31' AS date), 112) AS Style112,       CONVERT(date, '31/12/2024', 103) AS ReadBritish;
Output
CastText  NumberAsText  Style103    Style112  ReadBritish
--------  ------------  ----------  --------  -----------
      42  18.5          31/12/2024  20241231  2024-12-31
StyleFormatExample
23yyyy-mm-dd2024-12-31
103dd/mm/yyyy (British)31/12/2024
101mm/dd/yyyy (US)12/31/2024
112yyyymmdd (ISO basic)20241231
120yyyy-mm-dd hh:mi:ss2024-12-31 14:30:00

TRY_CAST: convert or give NULL

CAST stops the whole query at the first value it cannot convert. TRY_CAST and TRY_CONVERT return NULL instead, which makes them ideal for checking imported data. Explore which texts convert to which types:

Interactive

What converts to what?

Each cell is TRY_CAST(text AS type) on SQL Server 2022. NULL means the conversion failed. Click a cell.

textintdecimal(10,2)floatmoneydatedate, style 103
'42'
'42.5'
' 7 '
'abc'
'1e3'
'$12'
'2024-02-29'
'2024-02-30'
'31/12/2024'
''
TRY_CAST('' AS date) →1900-01-01
The trap: an empty string becomes 0 as an int or float, and 1 January 1900 as a date, with no error. Only decimal refuses it. Turn empty strings into NULL first with NULLIF(x, '').

A typical use: a spreadsheet was imported as text, and you need to find the rows that will not convert before loading them into a real table:

Query.sql
CREATE TABLE #Import (RowNo int, PriceText varchar(20), DateText varchar(20));INSERT INTO #Import (RowNo, PriceText, DateText) VALUES    (1, '12.50', '2024-07-01'),    (2, ' 7',    '2024-07-02'),    (3, 'n/a',   '2024-07-03'),    (4, '9.99',  '2024-07-32'),    (5, '',      ''); SELECT RowNo, PriceText, DateText,       TRY_CAST(PriceText AS decimal(10,2)) AS Price,       TRY_CAST(DateText AS date) AS TheDateFROM #ImportWHERE TRY_CAST(PriceText AS decimal(10,2)) IS NULL   OR TRY_CAST(DateText AS date) IS NULL;
Output
RowNo  PriceText  DateText    Price  TheDate
-----  ---------  ----------  -----  ----------
    3  n/a        2024-07-03   NULL  2024-07-03
    4  9.99       2024-07-32   9.99  NULL
    5                          NULL  1900-01-01

The empty-string trap

Look again at row 5 above. It was caught only because its blank price failed to become a decimal; its blank date became 1 January 1900 without complaint. An empty string converts to 0 as an int and to 1900-01-01 as a date, so blank cells in an import can quietly become real-looking values:

Query.sql
SELECT CAST('' AS int) AS EmptyAsInt,       CAST('' AS date) AS EmptyAsDate,       TRY_CAST(NULLIF(TRIM(''), '') AS date) AS Safe;
Output
EmptyAsInt  EmptyAsDate  Safe
----------  -----------  ----
         0  1900-01-01   NULL

NULLIF(TRIM(x), '') turns blanks (and all-space values) into NULL first, so they stay unknown instead of becoming 0 or 1900-01-01.

FORMAT for display

FORMAT(value, pattern, culture) uses .NET format strings, with a culture for currency symbols and separators. Use it for display only:

Query.sql
SELECT Name,       FORMAT(Price, 'C', 'en-GB') AS UkPrice,       FORMAT(Price, 'C', 'de-DE') AS GermanPrice,       FORMAT(Stock, 'N0') AS StockWithSeparatorsFROM dbo.ProductsWHERE ProductId IN (2, 6);
Output
Name              UkPrice  GermanPrice  StockWithSeparators
----------------  -------  -----------  -------------------
Cordless drill    £89.00   89,00 €      12
Timber 2x4 2.4 m  £6.80    6,80 €       500
Quick check

What is SELECT '3' + 4;?

Quick check

What does TRY_CAST('2024-02-30' AS date) return?

Quick check

What does CAST('' AS int) return?

Practice

Imported prices arrive as text. Convert each one safely to decimal(10,2): blanks and all-space values should become NULL, and so should anything that is not a number. Use the values '12.50', ' 7', 'n/a', '' and ' '.

Show a hint

Put the values in a VALUES list in FROM: FROM (VALUES ('12.50'), (' 7'), …) AS v(PriceText). Then TRY_CAST(NULLIF(TRIM(PriceText), '') AS decimal(10,2)).

Show the solution
Solution.sql
SELECT PriceText,       TRY_CAST(NULLIF(TRIM(PriceText), '') AS decimal(10,2)) AS PriceFROM (VALUES ('12.50'), (' 7'), ('n/a'), (''), ('   ')) AS v(PriceText);
Output
PriceText  Price
---------  -----
12.50      12.50
 7          7.00
n/a         NULL
            NULL
            NULL

Remember

  • Mixed types are converted towards the higher precedence type: numbers beat text, so '10' + 5 = 15.
  • CAST is standard; CONVERT adds style numbers for dates (103 British, 112 yyyymmdd, 23 ISO).
  • TRY_CAST / TRY_CONVERT return NULL instead of failing: use them to find bad imported rows.
  • Empty strings become 0 or 1900-01-01: wrap with NULLIF(TRIM(x), '') first.
  • FORMAT is for display only, and slow; keep values in their real types for storage and maths.
0 of 54 lessons completed