SQL Data Types: INT, VARCHAR, DECIMAL, DATE and More
A column's data type says what kind of value it holds: whole numbers, exact decimals, text, dates or true/false. The type decides what you can do with the values (add them, sort them as dates, compare them) and what can go wrong, such as a phone number losing its leading +.
What Are SQL Data Types?
Every column is declared with a type when the table is created. You can see the declared types of a Playground table with SQLite's pragma_table_info:
SELECT name AS column_name, type AS declared_type
FROM pragma_table_info('orders');
▶ Run this query in the SQL Playground
| column_name | declared_type |
|---|---|
| id | INTEGER |
| user_id | INTEGER |
| total | REAL |
| discount | REAL |
| item_count | INTEGER |
| status | TEXT |
| order_date | TEXT |
| shipped_date | TEXT |
| shipping_city | TEXT |
| shipping_country | TEXT |
| Type | Holds | Example value | Typical columns |
|---|---|---|---|
| INT / INTEGER | Whole numbers | 42 | ids, quantities, counts |
| DECIMAL(p, s) / NUMERIC | Exact numbers with s decimal places | 1299.99 | prices, salaries, money |
| FLOAT / REAL / DOUBLE | Approximate numbers | 0.30000000000000004 | measurements, scientific values |
| VARCHAR(n) | Text up to n characters | 'Austin' | names, emails, codes, phone numbers |
| CHAR(n) | Text of exactly n characters, padded | 'US' | fixed-length codes |
| TEXT | Long text | a review comment | descriptions, comments |
| DATE / DATETIME / TIMESTAMP | Dates and times | '2024-12-24' | order dates, created_at |
| BOOLEAN | True or false | TRUE | flags such as is_premium |
INT, DECIMAL and FLOAT
Integers have no fractional part. DECIMAL stores an exact number of decimal places: DECIMAL(10,2) means up to 10 digits in total, 2 of them after the point, so up to 99,999,999.99. FLOAT and REAL store approximate binary values, which causes tiny rounding errors:
SELECT 0.1 + 0.2 AS float_sum,
0.1 + 0.2 = 0.3 AS equals_0_3,
ROUND(0.1 + 0.2, 2) AS rounded;
▶ Run this query in the SQL Playground
| float_sum | equals_0_3 | rounded |
|---|---|---|
| 0.30000000000000004 | 0 | 0.3 |
The sum is not exactly 0.3, so the comparison is false (0). For money that is not acceptable: rounding errors add up over millions of rows and make totals fail to reconcile. Use DECIMAL for prices, salaries and balances.
orders.total and products.price as REAL, a floating-point type, because SQLite has no true DECIMAL storage. That is fine for practice. In a production MySQL, PostgreSQL or SQL Server database, money columns should be DECIMAL.| Type | Range (typical) | Storage |
|---|---|---|
| SMALLINT | -32,768 to 32,767 | 2 bytes |
| INT | About ±2.1 billion | 4 bytes |
| BIGINT | About ±9.2 quintillion | 8 bytes |
| DECIMAL(10,2) | Up to 99,999,999.99, exact | About 5 to 9 bytes |
VARCHAR, CHAR and TEXT
VARCHAR(100) stores up to 100 characters and uses only the space the value needs. CHAR(100) always stores 100, padding short values with spaces, which suits fixed-length codes like country codes. TEXT holds long text.
Phone numbers are text, not numbers
A phone number is an identifier, not a quantity: you never add two of them. Stored as an integer, the formatting, the + and any leading zeros are lost. Here is what converting to INTEGER does:
SELECT phone,
CAST(phone AS INTEGER) AS phone_as_integer,
CAST('007' AS INTEGER) AS leading_zeros_as_integer
FROM users
LIMIT 3;
▶ Run this query in the SQL Playground
| phone | phone_as_integer | leading_zeros_as_integer |
|---|---|---|
| +1-263-376-2371 | 1 | 7 |
| +1-753-780-6670 | 1 | 7 |
| +1-440-743-9503 | 1 | 7 |
SQLite reads the number up to the first character that is not a digit, so +1-263-376-2371 becomes 1. Other databases reject the conversion or behave differently, but none keep the original value. The same applies to postal codes, account numbers and SKUs.
DATE, DATETIME and TIMESTAMP
| Type | Holds | Example |
|---|---|---|
| DATE | A calendar date | 2024-12-24 |
| TIME | A time of day | 14:30:00 |
| DATETIME / TIMESTAMP | Date and time | 2024-12-24 14:30:00 |
| TIMESTAMP WITH TIME ZONE (PostgreSQL: timestamptz) | A moment, time-zone aware | 2024-12-24 14:30:00+00 |
With a real date type the database can compare, sort and calculate with dates. SQLite has no date type: dates are text, and they only behave correctly in YYYY-MM-DD form. You can see that in the Playground:
SELECT typeof(order_date) AS stored_as,
MIN(order_date) AS first_order,
MAX(order_date) AS last_order,
SUM(order_date >= '2024-01-01') AS orders_in_2024
FROM orders;
▶ Run this query in the SQL Playground
| stored_as | first_order | last_order | orders_in_2024 |
|---|---|---|---|
| text | 2022-01-06 | 2024-12-24 | 66 |
Text in another format, such as 12/24/2024, would sort by month first and break range filters. Store dates in a date type, or at least in ISO format. See date functions.
BOOLEAN
PostgreSQL has a real BOOLEAN type. MySQL's BOOLEAN is TINYINT(1), SQL Server uses BIT, and SQLite stores 0 and 1 in an INTEGER column, as the Playground does with is_premium:
SELECT is_premium, COUNT(*) AS users
FROM users
GROUP BY is_premium;
▶ Run this query in the SQL Playground
| is_premium | users |
|---|---|
| 0 | 81 |
| 1 | 69 |
How SQLite Handles Types
SQLite is unusual: a declared type is a preference (an "affinity"), not a strict rule, and each value carries its own storage class. typeof() shows it:
SELECT typeof(id) AS id_type,
typeof(total) AS total_type,
typeof(status) AS status_type,
typeof(shipped_date) AS shipped_date_type
FROM orders
WHERE id = 1;
▶ Run this query in the SQL Playground
| id_type | total_type | status_type | shipped_date_type |
|---|---|---|---|
| integer | real | text | null |
An order that has not shipped has NULL there, which is its own storage class. MySQL, PostgreSQL and SQL Server enforce column types much more strictly: inserting 'abc' into an INT column is an error. SQLite has STRICT tables (3.37+) for the same behaviour.
Choosing the Right Type
| Column | Good type | Why |
|---|---|---|
| id | INT or BIGINT (or UUID) | Small, fast to compare and index |
| price, salary, balance | DECIMAL(12,2) | Exact, no rounding errors |
| quantity, stock | INT | Whole numbers you add up |
| VARCHAR(255) | Addresses can be up to 254 characters | |
| phone, postal code, SKU | VARCHAR(20) or similar | Identifiers with leading zeros, + and dashes |
| country code | CHAR(2) | Always two letters |
| order date | DATE | Date comparisons and arithmetic |
| created at | TIMESTAMP (with time zone where available) | An exact moment |
| is_premium | BOOLEAN (BIT, TINYINT(1)) | Two states |
| review text | TEXT | Long and variable |
Type Names in Each Database
| Concept | MySQL | PostgreSQL | SQL Server | SQLite |
|---|---|---|---|---|
| Whole number | INT | INTEGER | INT | INTEGER |
| Exact decimal | DECIMAL | NUMERIC / DECIMAL | DECIMAL | NUMERIC affinity (stored as integer or real) |
| Text | VARCHAR(n), TEXT | VARCHAR(n), TEXT | VARCHAR(n), NVARCHAR(n) | TEXT |
| Date and time | DATE, DATETIME, TIMESTAMP | DATE, TIMESTAMP, TIMESTAMPTZ | DATE, DATETIME2, DATETIMEOFFSET | TEXT, REAL or INTEGER |
| Boolean | BOOLEAN (TINYINT(1)) | BOOLEAN | BIT | INTEGER 0/1 |
| Auto-increment id | INT AUTO_INCREMENT | GENERATED ... AS IDENTITY / SERIAL | INT IDENTITY | INTEGER PRIMARY KEY |
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
sku VARCHAR(20) UNIQUE,
price DECIMAL(10,2) NOT NULL,
stock INT DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Common Data Type Mistakes
Five errors and how to avoid them.
1. Money in FLOAT
price FLOAT
price DECIMAL(10,2)
0.1 + 0.2 is not exactly 0.3 in floating point, as the example above shows.
2. Phone numbers as INT
phone INT
phone VARCHAR(20)
Phone numbers are identifiers. The Playground example turns +1-263-376-2371 into 1.
3. Dates as free-form text
order_date VARCHAR(20) -- '12/24/2024'
order_date DATE
Text compares character by character: '12/24/2023' sorts after '02/01/2024'.
4. VARCHAR for everything
quantity VARCHAR(10)
quantity INT
'10' < '9' as text. Numbers stored as text sort wrongly and need conversion before SUM.
5. Too-small sizes
email VARCHAR(30)
email VARCHAR(255)
Long but valid addresses fail to insert, or are truncated in databases that allow it.
Practice Data Types Queries
4 exercises on the real Playground tables.
Write each query yourself first. Every solution has been run against the Playground database.
-
1Easy List the columns of the products table and their declared types. Table:
productsShow solution
SELECT name AS column_name, type AS declared_type FROM pragma_table_info('products');▶ Run this query in the SQL Playground
pragma_table_info is SQLite's way to read a table definition; other databases use information_schema.columns.
-
2Easy Show the storage class of the salary, hire_date and manager_id values for employee 1. Table:
employeesShow solution
SELECT typeof(salary) AS salary_type, typeof(hire_date) AS hire_date_type, typeof(manager_id) AS manager_id_type FROM employees WHERE id = 1;▶ Run this query in the SQL Playground
Employee 1 has no manager, so manager_id is NULL.
-
3Medium Show what happens to a SKU such as SKU-000042 when it is converted to an integer, and how to get its number correctly. Table:
productsShow solution
SELECT sku, CAST(sku AS INTEGER) AS cast_whole_sku, CAST(SUBSTR(sku, 5) AS INTEGER) AS sku_number FROM products WHERE id = 42;▶ Run this query in the SQL Playground
The whole SKU starts with letters, so it converts to 0. Take the digits first with SUBSTR.
-
4Medium Show why numbers stored as text sort wrongly: compare '10' < '9' as text with 10 < 9 as numbers. Table:
(none)Show solution
SELECT '10' < '9' AS text_comparison, 10 < 9 AS number_comparison;▶ Run this query in the SQL Playground
As text, '10' comes before '9' because '1' is less than '9', so the first comparison is true (1). As numbers it is false (0).
📝 Quiz — Data Types
Test your understanding · 0/6 answered
Which data type is best for a person's age?
A whole number. Storing a birth date is often better still, because an age goes out of date.
Which data type should store a price such as 1299.99?
Exact decimals; FLOAT has rounding errors.
What goes wrong if phone numbers are stored as INT?
Phone numbers are identifiers, not quantities.
What is the difference between VARCHAR(100) and CHAR(100)?
CHAR suits fixed-length codes.
Which type records both the date and the exact time of a login?
DATE alone loses the time.
Which table definition uses suitable types?
Text for the name, exact decimals for the price, a date for the date.
Data Types Interview Questions
Q1. Why use DECIMAL instead of FLOAT for money?
FLOAT stores binary approximations, so values like 0.1 cannot be represented exactly and sums drift. DECIMAL stores exact decimal digits.
Q2. VARCHAR or CHAR?
VARCHAR for values of varying length; CHAR only for truly fixed-length values such as two-letter country codes, because CHAR pads shorter values with spaces.
Q3. How does SQLite treat types differently?
Declared types are affinities, not strict rules, and each value has its own storage class (NULL, INTEGER, REAL, TEXT, BLOB). Dates are stored as text or numbers. STRICT tables enforce types.
Q4. Why should dates not be stored as text?
Text compares character by character, so only ISO YYYY-MM-DD text sorts correctly, and date arithmetic needs conversion. A date type sorts, compares and calculates correctly and rejects invalid dates.
Frequently Asked Questions
What are SQL data types?
Data types define what kind of value a column can hold: numbers (INT, DECIMAL), text (VARCHAR, CHAR, TEXT), dates (DATE, DATETIME) or true/false (BOOLEAN). The type is set when the table is created. Most databases enforce it on every insert and update; SQLite treats it as a preference unless the table is STRICT.
Should I store phone numbers as INT or VARCHAR?
VARCHAR. Phone numbers can start with +, contain dashes or spaces and have leading zeros. Stored as an integer they lose all of that.
What is the difference between INT and DECIMAL?
INT stores whole numbers. DECIMAL stores exact numbers with a fixed number of decimal places: DECIMAL(10,2) allows up to 10 digits, 2 of them after the point. Use DECIMAL for prices and salaries.
Why not just use VARCHAR for everything?
Text cannot be added up, sorts numbers wrongly ('10' comes before '9') and does not validate dates or numbers. The right type gives you correct sorting, arithmetic and validation.
Why is DECIMAL better than FLOAT for money?
FLOAT uses binary floating point, so 0.1 + 0.2 gives 0.30000000000000004 rather than 0.3, as the Playground shows. DECIMAL stores exact values.
Where can I practise writing CREATE TABLE with correct types?
The SQLabHub Playground only runs SELECT and WITH queries, so CREATE TABLE needs a local database. SQLite (with the sqlite3 command-line tool or DB Browser for SQLite), PostgreSQL and MySQL are all free to install. In the Playground you can inspect the existing tables' types with pragma_table_info.
What SQL data type should I use to store money?
DECIMAL, for example DECIMAL(10,2) for ordinary prices or DECIMAL(19,4) where more precision is needed. Avoid FLOAT for money.
Can phone numbers be stored as INT in SQL?
They should not be. An integer loses the leading +, any leading zeros and the formatting. Use VARCHAR(20) or similar.
Is VARCHAR slower than INT for SQL queries?
Comparing and indexing integers is generally cheaper than comparing text, and integer indexes are smaller. The bigger point is correctness: store numbers as numbers and identifiers as text.
Which SQL data type uses the least memory?
For a flag, BOOLEAN (1 byte in MySQL as TINYINT(1); SQL Server packs up to 8 BIT columns into one byte). SMALLINT uses 2 bytes, INT 4 and BIGINT 8. VARCHAR uses the length of the value plus 1 or 2 bytes.
Should a salary column use FLOAT or DECIMAL?
DECIMAL, such as DECIMAL(12,2). Payroll needs exact cents, and floating-point rounding errors cause totals that do not reconcile.
Can SQL dates be stored as VARCHAR text?
It is possible, and SQLite always stores dates as text, but in databases with a date type you should use it. Text dates only sort and compare correctly in YYYY-MM-DD form, and different formats of the same date are not equal.
What data type should I use for database record IDs?
INT for most tables (about 2.1 billion values), BIGINT for very large ones. For ids generated in many places without a central counter, use a UUID type or CHAR(36).
What is the best SQL data type for storing email addresses?
VARCHAR(255). An email address can be up to 254 characters. CHAR(255) would pad every address with spaces, and VARCHAR is easy to index.