Numeric Types
SQL splits numeric data into two families: exact types, which store the value precisely with no rounding, and approximate types, which trade some precision for a much wider range and more compact storage. Picking between them — and picking the right size within the exact family — is one of the first decisions you make for almost every table.
Integer types
Integers hold whole numbers with no fractional part. They come in several sizes so you can pick one that comfortably fits your data without wasting space.
Type | Typical size | Typical range |
|---|---|---|
SMALLINT | 2 bytes | -32,768 to 32,767 |
INTEGER / INT | 4 bytes | -2,147,483,648 to 2,147,483,647 |
BIGINT | 8 bytes | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
Choosing an integer size
CREATE TABLE order_items (
id BIGINT PRIMARY KEY, -- expect very many rows over time
order_id INTEGER NOT NULL, -- millions of orders is plenty of range
quantity SMALLINT NOT NULL -- nobody orders 32,768+ units at once
);Exact decimal types: DECIMAL / NUMERIC
DECIMAL(precision, scale) and NUMERIC(precision, scale) are interchangeable in most dialects. Precision is the total number of significant digits; scale is how many of those digits sit after the decimal point. DECIMAL(10, 2) stores up to 10 digits total, 2 of them after the point — perfect for currency amounts like 12345678.90.
Exact precision for money
CREATE TABLE invoices (
id SERIAL PRIMARY KEY,
amount DECIMAL(10, 2) NOT NULL -- exact: 199.99 is stored as exactly 199.99
);
INSERT INTO invoices (amount) VALUES (199.99);
SELECT amount * 3 FROM invoices; -- 599.97, exactlyApproximate types: FLOAT, REAL, DOUBLE PRECISION
These store an approximation of the value using a fixed number of binary digits. They can represent an enormous range very compactly, which makes them a good fit for scientific measurements, sensor readings, or machine-learning features — anywhere small rounding error is acceptable and raw performance or range matters more than exactness.
Approximate values for measurements
CREATE TABLE sensor_readings (
id SERIAL PRIMARY KEY,
temperature REAL, -- single precision, ~6-7 significant digits
pressure DOUBLE PRECISION -- double precision, ~15-17 significant digits
);Auto-incrementing primary keys
Most tables need a column that generates a new unique integer for every row automatically. Every major dialect supports this, but the syntax differs.
Dialect | Syntax |
|---|---|
PostgreSQL | SERIAL / BIGSERIAL, or GENERATED ALWAYS AS IDENTITY |
MySQL | INT ... AUTO_INCREMENT |
SQL Server | INT ... IDENTITY(1,1) |
Auto-increment across dialects
-- PostgreSQL CREATE TABLE users (id SERIAL PRIMARY KEY, email VARCHAR(255)); -- MySQL CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, email VARCHAR(255)); -- SQL Server CREATE TABLE users (id INT IDENTITY(1,1) PRIMARY KEY, email VARCHAR(255));
Choosing the smallest adequate type
Estimate the realistic maximum value a column will ever hold, then pick the smallest type that comfortably covers it with room to grow.
A
SMALLINT"age" column and aBIGINT"age" column behave identically in queries, but theBIGINTwastes 6 extra bytes per row — trivial for 100 rows, meaningful for 500 million.For primary keys on very large or fast-growing tables,
BIGINTis usually the safer default despite the extra bytes, since running out ofINTEGERrange on a live table is painful to fix.Smaller, fixed-size types also make indexes smaller and comparisons cheaper, which compounds into real query performance gains at scale.