How do I choose the right data type for a column?
Choose the narrowest type that holds the entire range of values you will ever store. That single rule drives most of the decision, because a narrow column is smaller on disk, faster to compare, and able to fit more rows per index page — which means the index is smaller and lookups are faster.
CREATE TABLE type_demo (
id INTEGER PRIMARY KEY,
price_cents INTEGER NOT NULL,
ratio REAL NOT NULL,
sku VARCHAR(40) NOT NULL,
is_active INTEGER NOT NULL DEFAULT 1,
recorded_at TEXT NOT NULL
);
INSERT INTO type_demo (price_cents, ratio, sku, is_active, recorded_at)
VALUES (1999, 0.75, 'KB-01', 1, '2026-03-01');
SELECT typeof(price_cents) AS price_type,
typeof(ratio) AS ratio_type,
typeof(sku) AS sku_type,
typeof(is_active) AS flag_type,
typeof(recorded_at) AS date_type
FROM type_demo;Five types come back: integer, real, text, integer and text. SQLite has a dynamic type system with five storage classes — NULL, INTEGER, REAL, TEXT, BLOB — and it converts a value to the storage class that fits it, so a column declared VARCHAR holds text and a column declared INTEGER holds an integer. Other engines are stricter: PostgreSQL will reject a string in an integer column rather than converting it.
The practical choices. Use INTEGER for counts, identifiers and money in cents. Use REAL for genuinely fractional measurements, with the understanding that it is a binary approximation. Use a fixed-precision DECIMAL when the value must be exact and fractional — the price of a bond, a tax rate that has to reconcile to the penny — and never for money on a system that also needs to sum quickly. Use TEXT for names, codes and anything you do not control, and a native DATE or TIMESTAMP type if your engine has one, rather than text, so that comparisons and date functions behave.
Two things to avoid. Do not store a number in a text column: it sorts as a string, so '10' comes before '9', and it bloats the index. And do not use a floating point type for money, because 0.1 has no exact binary representation and a sum of prices drifts. Store cents as an integer and convert only for display.
One more consideration that is often skipped: leave room to grow. A TINYINT that holds a status code is perfect today and painful in two years when you need a hundred status codes, because widening it later means a table rewrite. Where a value is small but might grow — a country code, a status, a type flag — a smallint or a plain integer is usually the better trade.