Readineer
PATTERN 13SQL Patterns / Working with Real-World Data

SQL data types and CAST: converting values safely

Learn how SQL treats numbers, text, dates, and timestamps differently, convert values explicitly with CAST, understand implicit conversions, and avoid comparisons that look correct but use the wrong data type.

9 min read11 sections2 predictions
01Foundation

A value has both content and a type

Two values can look similar while behaving very differently in SQL. Consider 8500 and '8500'. The first may be stored as a number. The second is text containing the characters 8, 5, 0, 0. To a person reading a report, they look almost identical. To SQL, their data types can change how they are compared, sorted, added, filtered, joined, and aggregated.

This becomes especially important when data arrives from CSV files, APIs, spreadsheets, or external systems where values may initially be stored as text. Imagine a raw import of our orders data:

Raw Orders
order_idcustomerquantity_textunit_price_textordered_at_textdiscount_textstatus
1001Asha27502026-07-01 09:15:00150paid
1002Ben132002026-07-02 14:30:00NULLpending
1003Carla312002026-07-15 18:45:00300.50paid
1004Dev185002026-07-31 00:00:00(empty string)cancelled
1005Asha1902026-07-31 18:30:000paid
1006Ben218002026-08-01 08:00:00200paid

The column names deliberately include _text. Although unit_price_text = '8500' looks like a number, SQL currently sees it as text. Similarly, ordered_at_text = '2026-07-31 18:30:00' looks like a timestamp, but it is still text until it is converted to a timestamp type.

Key idea
What a value looks like does not determine how SQL treats it. Its data type does.
02Type families

Common data type families

SQL databases provide many data types, but several broad families appear frequently.

Numeric values

INTEGER, BIGINT, DECIMAL, NUMERIC, REAL, DOUBLE. They represent numbers and support numeric operations.

Character values

CHAR, VARCHAR, TEXT. These represent text.

Date and time values

DATE, TIME, TIMESTAMP. These represent calendar and time information.

Boolean values

Typically TRUE and FALSE.

Different database systems provide different type names and capabilities, but the main idea remains the same: choose a data type that represents what the value actually means.

03Explicit conversion

Converting values with CAST

SQL provides CAST for explicit type conversion. The general form is CAST(value AS target_type). For example, CAST('8500' AS INTEGER) takes the text '8500' and converts it into the numeric value 8500. Now SQL can treat it as a number.

Converting text to numbers

Suppose we want to calculate the order amount, quantity × unit price. Both values currently come from text columns, quantity_text and unit_price_text. We can convert them:

Query
SELECT
    order_id,
    customer,
    CAST(quantity_text AS INTEGER) AS quantity,
    CAST(unit_price_text AS INTEGER) AS unit_price,
    CAST(quantity_text AS INTEGER)
        * CAST(unit_price_text AS INTEGER) AS order_amount
FROM raw_orders
ORDER BY order_id;
Result
order_idcustomerquantityunit_priceorder_amount
1001Asha27501500
1002Ben132003200
1003Carla312003600
1004Dev185008500
1005Asha19090
1006Ben218003600

The values now participate in numeric multiplication rather than text operations.

04The featured failure

The numeric filter that is actually comparing text

Suppose the requirement is return products whose unit price is greater than 1,000. Someone writes the query below. It may run successfully, but the comparison is between text values ('750', '3200', '1200', '8500', '90', '1800'), not numbers. Text comparison follows character ordering rules rather than numeric magnitude.

PAUSE & PREDICTUnder this text comparison, does the '90' row qualify?
Query
SELECT
    order_id,
    customer,
    unit_price_text
FROM raw_orders
WHERE unit_price_text > '1000'
ORDER BY unit_price_text;
Your prediction

The fix: convert before the numeric comparison

Make the required type explicit:

Query
SELECT
    order_id,
    customer,
    CAST(unit_price_text AS INTEGER) AS unit_price
FROM raw_orders
WHERE CAST(unit_price_text AS INTEGER) > 1000
ORDER BY unit_price;
Result
order_idcustomerunit_price
1003Carla1200
1006Ben1800
1002Ben3200
1004Dev8500

Now SQL is comparing 1200 > 1000, 1800 > 1000, 3200 > 1000, and 8500 > 1000 as numbers.

Key idea
If the business rule is numeric, make sure the values participating in the comparison are numeric.

Sorting text numbers has the same problem

ORDER BY unit_price_text DESC sorts text. It does not mean highest numeric price first. To request numeric ordering, ORDER BY CAST(unit_price_text AS INTEGER) DESC, and the correct sort becomes:

8500
3200
1800
1200
750
90

A column containing digits is not automatically a numeric column.

05Choosing a type

Choosing the right numeric type

Not every numeric value should become an integer. Consider discount_text = '300.50'. This cannot be represented accurately as an integer without losing the fractional component. For values requiring decimal precision, use an appropriate exact numeric type:

Query
SELECT
    order_id,
    CAST(discount_text AS DECIMAL(10, 2)) AS discount_amount
FROM raw_orders;

DECIMAL(10, 2) commonly means a decimal value with up to ten total digits, two of which are after the decimal point. For money-like calculations, exact decimal types such as DECIMAL or NUMERIC are generally preferable to binary floating-point types when exact decimal behaviour matters.

Number of items          -> INTEGER
Monetary amount          -> DECIMAL / NUMERIC
Approximate measurement  -> floating-point type

Choose the target type according to the meaning of the data. Do not choose a type only because the conversion succeeds.

Invalid values cannot always be cast

Consider order 1004, where discount_text = ''. An empty string is not a valid decimal number in many database systems, so CAST(discount_text AS DECIMAL(10, 2)) can fail. The same problem appears with values such as 'N/A', 'unknown', '₹500', and '10 dollars'. These are text values, not clean numeric values. CAST converts compatible representations; it does not automatically clean arbitrary source data.

Normalizing empty strings before conversion

Suppose the business has decided an empty discount_text means the discount is missing. We can first turn the empty string into NULL, then cast:

Empty string → NULL, then cast
CAST(
    NULLIF(TRIM(discount_text), '')
    AS DECIMAL(10, 2)
)

For '', NULLIF produces NULL, and the cast of NULL remains NULL. This avoids treating an empty string as though it were a number.

This still does not solve arbitrary invalid values such as 'N/A'. Those require validation or database-specific safe-conversion techniques. Many database systems provide functions that return NULL rather than failing when conversion is impossible, but their names and exact behaviour differ.
06Implicit casts

Implicit conversions

Sometimes SQL converts values automatically. This is called an implicit conversion or implicit cast. Suppose order_id is an integer column and a query contains WHERE order_id = '1001'. The right side looks like text, but some databases may determine that the comparison should be numeric and convert one side automatically. The query may work, and that convenience can make implicit conversions tempting. But their rules vary between database systems and contexts.

Why relying on implicit conversion is risky

Consider WHERE numeric_column = text_value. SQL must decide: should the number become text, or should the text become a number? The answer depends on the database’s type-resolution rules. If the text contains '100', a conversion may work. If it contains '100A', the same conversion may fail. Relying on implicit conversion is:

  • Harder to understand.
  • More dependent on database-specific behaviour.
  • More fragile when data changes.
  • Harder to debug.

When different data types meet and the intended conversion matters, make it explicit. Instead of relying on WHERE order_id = '1001', prefer a comparison where both sides clearly represent the same type, WHERE order_id = 1001, or use an explicit cast when conversion is genuinely required.

Conversion is not the same as formatting

Suppose amount = 1234.5. Casting it to text with CAST(amount AS TEXT) changes its type. It does not necessarily mean 1,234.50, ₹1,234.50, or $1,234.50. Those are formatting requirements. Formatting currency, separators, padding, and locale-specific presentation is a different problem, often handled by database-specific formatting functions or by the application displaying the result.

CAST         changes the data type
Formatting   changes how the value is presented
07Dates and joins

Converting text to dates

Date values also commonly arrive as text. Our raw table contains ordered_at_text = '2026-07-31 18:30:00'. We can convert this to a timestamp:

Query
SELECT
    order_id,
    CAST(ordered_at_text AS TIMESTAMP) AS ordered_at
FROM raw_orders;

Now SQL can perform timestamp operations such as ordered_at + INTERVAL '7 days' or ordered_at >= TIMESTAMP '2026-07-01 00:00:00'. A text representation of a timestamp does not automatically give us reliable timestamp semantics. Converting it to the proper type does.

Convert to DATE when you need a calendar date

Suppose we have 2026-07-31 18:30:00 and need only 2026-07-31. We can cast a timestamp to a date:

Text → timestamp → date
SELECT
    order_id,
    CAST(
        CAST(ordered_at_text AS TIMESTAMP)
        AS DATE
    ) AS order_date
FROM raw_orders;

If the column is already a timestamp, the expression is simpler: CAST(ordered_at AS DATE). The result contains the calendar date without the time-of-day component.

Gotcha — casting to DATE loses time
Converting TIMESTAMP to DATE loses time information. After CAST(ordered_at AS DATE), 2026-07-31 18:30:00 becomes 2026-07-31, and the time 18:30:00 is no longer part of the value. This may be exactly what the query needs, but it is a loss of information. Ask: do I need a date, or do I still need the precise timestamp?

Comparing values after conversion

Once data is converted to the correct type, SQL can apply the correct semantics. CAST(unit_price_text AS INTEGER) > 1000 asks a numeric question, and CAST(order_date_text AS DATE) >= DATE '2026-07-01' asks a calendar-date question.

Data types matter in joins too

Suppose one table stores customer_id as an integer (1, 2, 3) while another stores it as text ('1', '2', '3'). A join such as ON table_a.customer_id = table_b.customer_id now compares different types. Depending on the database, SQL may perform an implicit conversion, require an explicit conversion, or produce an error.

Key idea
Even when the join works, mismatched types make the relationship harder to reason about. If two columns represent the same key, they should ideally use compatible data types in the schema. Repeated casting in joins is often a sign that the underlying data model should be corrected.
08Recognition cues

How to spot data-type problems

Look carefully at data types when:

Numbers arrive from CSV or JSON as strings.
Numeric sorting produces a strange order.
A numeric comparison includes or excludes unexpected rows.
Arithmetic fails on a column containing digits.
Dates are stored as text.
A date conversion depends on ambiguous formats.
A join compares identifiers stored with different types.
A query works only because SQL appears to convert something automatically.
An empty string causes a numeric or date conversion to fail.
Casting a timestamp to a date unexpectedly removes needed precision.

When something behaves strangely, ask what the actual data type of the expression is. Do not rely only on what the value looks like.

09A practical mental model

Ask four questions when converting

1 · What type is the value currently?

'8500' may be TEXT even though it contains digits.

2 · What does the value represent?

Money to DECIMAL, a count to INTEGER, a moment in time to TIMESTAMP.

3 · Is the source representation valid?

Values such as '', 'N/A', 'unknown', '07/08/2026' cannot be fixed by conversion alone.

4 · Am I converting because it genuinely needs it?

If the schema already stores a number as a number, do not cast it to text merely to match another poorly typed value.

Key idea
Whenever possible, fix the type mismatch at the data-model or ingestion layer rather than repeating conversions in every query.
10Check your understanding

One more prediction

Consider these values stored in a text column. Requirement: return orders whose unit price is greater than 1,000. Which condition best expresses the requirement?

unit_price_text
order_idunit_price_text
1001750
10023200
10031200
10048500
100590
10061800
ONE MORE PREDICTIONWhich condition best expresses the requirement?
Query
-- Option A
WHERE unit_price_text > '1000'

-- Option B
WHERE CAST(unit_price_text AS INTEGER) > 1000

-- Option C
WHERE unit_price_text LIKE '%1000%'

-- Option D
WHERE CAST(1000 AS TEXT) < unit_price_text
Your prediction
11Summary

Summary

SQL values have types, and those types affect how operations behave. 8500 as a number is not the same thing as '8500' stored as text. Use CAST when an explicit conversion is required: CAST(unit_price_text AS INTEGER) for whole numbers, CAST(discount_text AS DECIMAL(10, 2)) for decimals, CAST(ordered_at_text AS TIMESTAMP) for timestamps, CAST(value AS DATE) for dates, and CAST(order_id AS TEXT) for text.

Key idea
Convert values according to what they mean, not according to what happens to make the query run. WHERE unit_price_text > '1000' may be valid SQL, but if the column is text it is not a reliable numeric comparison. Write WHERE CAST(unit_price_text AS INTEGER) > 1000 when the business question is numeric. Be careful with source values such as '', 'N/A', 'unknown', and '07/08/2026': CAST cannot decide what ambiguous or invalid data was intended to mean. Reliable data types should ideally begin in the schema itself, so numbers are stored as numbers, dates as dates, and timestamps as timestamps.

Next, we will move into semi-structured data and learn how SQL works with JSON objects, nested fields, and arrays.

Go deeper

Ask Colearn about this pattern

Not sure why a numeric filter returns odd rows, or when an implicit conversion will bite you? Ask about the concept, or paste a simplified version of your query.

Ask Colearn

3 of 3 free questions left

Instant answers, grounded in the same verified material the diagnostic grades against.

Why does WHERE unit_price_text > '1000' return the wrong rows?

Both sides are text, so SQL compares character sequences, not numeric magnitude. As text, '90' is greater than '1000' because '9' sorts after '1', so the row qualifies even though 90 is not greater than 1000.

If the business rule is numeric, make the values numeric first: WHERE CAST(unit_price_text AS INTEGER) > 1000.

from the unit →
Why did my empty-string discount fail to cast?

An empty string is not a valid decimal in many systems, so CAST('' AS DECIMAL(10,2)) can fail. CAST converts compatible representations; it does not clean arbitrary source data.

If an empty value means missing, turn it into NULL first: CAST(NULLIF(TRIM(discount_text), '') AS DECIMAL(10,2)). The cast of NULL stays NULL.

from the unit →
Should I write order_id = '1001' or order_id = 1001?

When an integer column meets a text literal, SQL must decide which side to convert, and that rule varies by database. '1001' may work while '1001A' fails, which makes the query fragile.

Prefer a comparison where both sides clearly share a type: order_id = 1001, or an explicit CAST when conversion is genuinely required.

from the unit →
My join compares an integer key to a text key. Is that safe?

Depending on the database, SQL may implicitly convert, require an explicit cast, or error. Even when it works, mismatched types make the relationship harder to reason about.

If two columns represent the same key, they should use compatible types in the schema. Repeated casting in joins is often a sign the data model should be corrected.

from the unit →

That’s the free taste

Two ways to go deeper.

Don’t paste credentials, personal data, or confidential production records.

Related patterns

P14Pattern · 10 minJSON & semi-structuredThe same extract-then-CAST rule applied to nested values pulled out of JSON.P12Pattern · 9 minString functionsCleaning and normalizing the same text values before you convert their type.P11Pattern · 7 minDates & timestampsOnce text is cast to a timestamp, how to filter and work with time correctly.
Up next · Pattern 14
Semi-structured data: JSON objects, nested fields, and arrays

You can now convert values to the type they mean. Next we look at values that carry structure inside them.

Continue to P14 →Browse all patterns