Definition
- CAST(expr AS type) – Explicitly changes a value's data type; use it on literals and parameters, never on indexed columns.
- CONVERT – A vendor-specific function (SQL Server, MySQL) for type conversion, often with style codes for formatting.
- Implicit type conversion – The database silently coerces one operand to match the other based on type-precedence rules when the types differ in a comparison.
- Index defeat – When the engine applies the implicit cast to the indexed column instead of the literal, the B-tree ordering on the raw column becomes useless and the query falls back to a full scan.
- JDBC binding – Always use
setString()for VARCHAR columns andsetLong()for BIGINT columns to send the correct type to the database.
National Bank of India stores account numbers as text like "SAV2001". If a query accidentally compares that text column to a number, the database quietly tries to turn each stored account number into a number to compare. Doing that to every row means it cannot use the account-number index – it must read the whole table, and worse, the conversion can fail or match the wrong rows.
Understanding the Concepts
What is CAST and CONVERT?
Explicit type conversion changes a value's data type deliberately. Use CAST for standard SQL and CONVERT for vendor-specific features.
-- Standard SQL: CAST
SELECT CAST('2026-10-25' AS DATE); -- string to date
SELECT CAST(50000.00 AS VARCHAR); -- number to string
SELECT CAST('12345' AS INTEGER); -- string to integer
-- SQL Server: CONVERT with style codes
SELECT CONVERT(DATE, '2026-10-25', 23); -- ISO format
SELECT CONVERT(VARCHAR, GETDATE(), 120); -- 'YYYY-MM-DD HH:MI:SS'
-- MySQL: CONVERT or CAST
SELECT CAST('2026-10-25' AS DATE);
SELECT CONVERT('12345', SIGNED INTEGER);
The rule: always cast the literal or parameter, never the indexed column.
-- Good: cast the literal to match the column type
SELECT * FROM transactions
WHERE created_at >= CAST('2026-10-01' AS TIMESTAMP);
-- Bad: cast the indexed column (defeats the index)
SELECT * FROM transactions
WHERE CAST(created_at AS DATE) = '2026-10-25';
-- This is a function on the column -> Seq Scan
What is implicit type conversion?
Implicit conversion happens when the database silently coerces one side of a comparison to match the other, following type-precedence rules. In most engines, numeric types outrank character types.
-- account_id is VARCHAR(20)
-- Comparing to a number triggers implicit conversion
SELECT * FROM accounts WHERE account_id = 2001;
-- The database sees: VARCHAR vs INTEGER
-- Type precedence: INTEGER > VARCHAR
-- So the database casts the COLUMN: CAST(account_id AS INTEGER) = 2001
-- This is a function on the column -> index is defeated
Why does implicit conversion defeat the index?
When the engine applies the cast to the indexed column, it must compute that function on every row. The B-tree is sorted by the raw column value, not the cast value, so there is no contiguous range to seek.
-- Index on accounts(account_id) (VARCHAR)
-- Implicit cast on the column defeats it
EXPLAIN ANALYZE
SELECT * FROM accounts WHERE account_id = 2001;
Seq Scan on accounts
Filter: ((account_id)::integer = 2001)
Rows Removed by Filter: 9,999,999
Execution Time: 3,200 ms -- full table scan
-- Fix: compare string to string
EXPLAIN ANALYZE
SELECT * FROM accounts WHERE account_id = '2001';
Index Scan using accounts_pkey on accounts
Index Cond: (account_id = '2001')
Rows: 1
Execution Time: 0.05 ms -- 64,000x faster
What problems can implicit conversion cause beyond performance?
Implicit conversion can also cause runtime errors and incorrect matches.
-- Runtime error: non-numeric value in a VARCHAR column
-- account_id contains 'SAV2001' (has letters)
SELECT * FROM accounts WHERE account_id = 2001;
-- The database tries CAST('SAV2001' AS INTEGER)
-- ERROR: invalid input syntax for type integer: "SAV2001"
-- Incorrect matches: leading zeros, spaces
-- '0042' as VARCHAR vs 42 as INTEGER
-- CAST('0042' AS INTEGER) = 42 -> matches, but '0042' != '42' as strings
-- This can return unexpected rows
How do you prevent implicit conversion in JDBC?
Always bind parameters with the correct type.
// com.bank.service.AccountLookupService
package com.bank.service;
import java.math.BigDecimal;
import java.sql.*;
public class AccountLookupService {
/**
* Wrong: binding an int when the column is VARCHAR.
* This causes implicit CAST on the column, defeating the index.
*/
public void lookupWrong(Connection conn, int id)
throws SQLException {
String sql = "SELECT * FROM accounts WHERE account_id = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, id); // int sent for VARCHAR column!
// Database: CAST(account_id AS INT) = 2001 -> Seq Scan
}
}
/**
* Correct: binding a String for a VARCHAR column.
* Types match, index is used.
*/
public BigDecimal lookupCorrect(Connection conn, String accountId)
throws SQLException {
String sql = "SELECT balance FROM accounts WHERE account_id = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, accountId); // String for VARCHAR -> Index Scan
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return rs.getBigDecimal("balance");
}
return null;
}
}
}
/**
* Timestamp filter: bind as Timestamp, not String.
*/
public int countTransactionsAfter(
Connection conn, String accountId, Timestamp after)
throws SQLException {
String sql = """
SELECT COUNT(*)
FROM transactions
WHERE account_id = ?
AND created_at >= ?
""";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, accountId); // VARCHAR -> setString
ps.setTimestamp(2, after); // TIMESTAMP -> setTimestamp
try (ResultSet rs = ps.executeQuery()) {
rs.next();
return rs.getInt(1);
}
}
}
}
What are the type-precedence rules?
Different engines have different type-precedence hierarchies, but the general pattern is that numeric types outrank character types.
General type precedence (highest to lowest):
1. DATETIME / TIMESTAMP
2. NUMERIC / DECIMAL / FLOAT
3. INTEGER / BIGINT
4. VARCHAR / CHAR / TEXT
When comparing VARCHAR vs INTEGER:
- INTEGER outranks VARCHAR
- So the VARCHAR column is cast to INTEGER
- This defeats the index on the VARCHAR column
| Left Side | Right Side | Who Gets Cast | Index Impact |
|---|---|---|---|
| VARCHAR column | INT literal | Column cast to INT | Index defeated |
| VARCHAR column | VARCHAR literal | No cast needed | Index used |
| INT column | VARCHAR literal | Literal cast to INT | Index used |
| TIMESTAMP column | VARCHAR literal | Literal cast to TS | Index used |
How do you detect implicit conversions?
Check EXPLAIN ANALYZE for unexpected casts or filter functions.
-- Look for a cast in the filter
EXPLAIN ANALYZE
SELECT * FROM accounts WHERE account_id = 2001;
-- Filter: ((account_id)::integer = 2001)
-- The ::integer cast is the implicit conversion
-- If you see this -> fix the literal type
-- After fix:
EXPLAIN ANALYZE
SELECT * FROM accounts WHERE account_id = '2001';
-- Index Cond: (account_id = '2001')
-- No cast in the condition -> index is used
What should a senior engineer know?
- Implicit conversion bugs often hide in production for months until a non-numeric value is inserted or table growth makes the scan slow enough to notice.
- ORMs can generate implicit conversions if the entity field type does not match the column type.
- Enable SQL logging in development to catch mismatched parameter types early.
- Always test new queries with
EXPLAIN ANALYZEon production-size data.
What are common interview follow-up questions?
- What is a sargable predicate and how does implicit conversion make it non-sargable?
- How do type-precedence rules differ between PostgreSQL, MySQL, and SQL Server?
- Why should you cast the literal instead of the column?
- How can you detect implicit conversions in EXPLAIN plans?
Visual Overview
┌─────────────────────────────────────────────────┐ │ Implicit Type Conversion and Index Defeat │ ├─────────────────────────────────────────────────┤ │ │ │ Query: WHERE account_id = 2001 │ │ Column type: VARCHAR(20) │ │ Literal type: INTEGER │ │ │ │ Type precedence: INTEGER > VARCHAR │ │ │ │ Database applies: │ │ CAST(account_id AS INTEGER) = 2001 │ │ ▲ │ │ │ function on the column! │ │ │ │ │ B-tree sorted by VARCHAR values: │ │ '1001' | '2001' | 'SAV2001' | 'SAV3001' │ │ Cannot seek by INTEGER value ──► Seq Scan │ │ │ │ Fix: WHERE account_id = '2001' │ │ No cast needed ──► Index Scan │ │ │ │ JDBC rule: │ │ VARCHAR column ──► ps.setString() │ │ BIGINT column ──► ps.setLong() │ │ TIMESTAMP col ──► ps.setTimestamp() │ └─────────────────────────────────────────────────┘
Key Takeaway
CAST with the AS type form, and the vendor-specific CONVERT, explicitly change a value's data type, and you use them on literals and bound parameters when you need a specific type. Implicit type conversion is when the database silently coerces one side of a comparison to match the other based on type-precedence rules, and the danger is that to make the types line up the engine often applies the conversion to the indexed column rather than to the literal. Once a function wraps the indexed column, the B-tree can no longer be traversed, so the query degrades to a full scan, and it can also throw at runtime or match the wrong rows.
In the National Bank of India lookup service, account numbers are stored as text like SAV2001, and a developer passed the id as an integer, which made the database implicitly convert every stored account number to a number – skipping the primary-key index and even failing to parse the alphabetic prefix. The fix was simply to compare text to text by passing a string literal and binding the JDBC parameter as a string with setString, which restored the index seek. Rajesh also made a rule to only ever cast the literal, never the column, so a timestamp filter casts the string literal to a timestamp rather than casting the column to text.
The habit Pankaj enforces is that every new query is tested with EXPLAIN ANALYZE on production-size data to catch implicit conversions early. Implicit conversion bugs often hide for months until a non-numeric value is inserted or data growth makes the scan slow enough to notice, and by then the damage is done.
Remember one sentence: match the literal's type to the column's type – an implicit conversion on the column silently kills the index.
---