Loading learning content...
Every database stores numbers—sales figures, user counts, financial transactions, scientific measurements, coordinates. But how a number is stored determines everything: whether your financial calculations will be accurate to the penny, whether your scientific data preserves critical precision, whether your storage costs spiral out of control, and whether your queries execute efficiently.
Numeric types are not interchangeable. Using the wrong numeric type is one of the most common and costly mistakes in database design. This page provides an exhaustive examination of SQL numeric types, their characteristics, and the engineering judgment required to select the right type for every use case.
By the end of this page, you will understand the complete taxonomy of SQL numeric types, the critical distinction between exact and approximate numerics, precision and scale semantics, storage implications, performance characteristics, and the decision framework for selecting optimal numeric types in production systems.
SQL numeric types are divided into two fundamental categories, each serving distinct purposes and exhibiting fundamentally different behaviors:
1. Exact Numeric Types — Store numbers with complete precision; every digit is preserved exactly as specified. These are essential for financial calculations, counts, identifiers, and any domain where approximate values are unacceptable.
2. Approximate Numeric Types — Store numbers using floating-point representation, which provides a wide range but introduces potential rounding errors. These are designed for scientific calculations, statistical analysis, and scenarios where extreme precision is less critical than computational range.
| Category | Types | Precision | Primary Use Cases |
|---|---|---|---|
| Exact Integers | INTEGER, SMALLINT, BIGINT, TINYINT | Complete (no decimals) | Counts, IDs, flags, quantities |
| Exact Decimals | DECIMAL, NUMERIC | User-defined precision | Financial data, exact calculations |
| Approximate Floats | REAL, DOUBLE PRECISION, FLOAT | ~7 or ~15 significant digits | Scientific data, statistics, geometry |
The choice between exact and approximate numerics is not arbitrary. Using FLOAT for currency will introduce rounding errors that compound over thousands of transactions. Using DECIMAL for scientific calculations wastes storage and may lack the range needed. Understanding this distinction is fundamental to database design.
Integer types store whole numbers (no fractional component) with complete precision. They are the most commonly used numeric types in database systems, appearing in primary keys, counters, quantities, flags, and enumerated values.
The SQL standard defines three core integer types:
| Type | Minimum Value | Maximum Value | Storage |
|---|---|---|---|
| SMALLINT | -32,768 | 32,767 | 2 bytes |
| INTEGER (INT) | -2,147,483,648 | 2,147,483,647 | 4 bytes |
| BIGINT | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 | 8 bytes |
These ranges derive directly from the underlying binary representation: a signed n-bit integer spans from -2^(n-1) to 2^(n-1)-1.
12345678910111213141516171819202122232425262728
-- Integer type declarations in table definitionsCREATE TABLE products ( product_id INTEGER PRIMARY KEY, -- 4 bytes, ~2.1 billion range category_code SMALLINT NOT NULL, -- 2 bytes, sufficient for category codes units_in_stock INTEGER DEFAULT 0, -- 4 bytes, handles warehouse quantities total_sold BIGINT DEFAULT 0 -- 8 bytes, for cumulative lifetime sales); -- Integer ranges demonstration-- SMALLINT: Good for bounded enumerations, status codes, small countsCREATE TABLE order_status ( status_id SMALLINT PRIMARY KEY, -- -32,768 to 32,767 status_name VARCHAR(50) NOT NULL); -- INTEGER: The workhorse type for most use casesCREATE TABLE customers ( customer_id INTEGER PRIMARY KEY, -- Handles billions of customers loyalty_points INTEGER DEFAULT 0, -- Accumulating points order_count INTEGER DEFAULT 0 -- Lifetime orders); -- BIGINT: For values that may exceed ~2 billionCREATE TABLE analytics_events ( event_id BIGINT PRIMARY KEY, -- Global event counter user_id INTEGER, -- User reference timestamp_ms BIGINT NOT NULL -- Unix timestamp in milliseconds);Some database systems (MySQL, MariaDB) support UNSIGNED integer variants that double the positive range by eliminating negative values. UNSIGNED INTEGER spans 0 to 4,294,967,295. PostgreSQL does not support unsigned types natively—use CHECK constraints or domain types to enforce non-negativity.
DECIMAL and NUMERIC are exact numeric types that store numbers with a user-specified precision (total digits) and scale (digits after the decimal point). In the SQL standard, DECIMAL and NUMERIC are functionally equivalent, though some implementations treat them slightly differently.
Syntax: DECIMAL(precision, scale) or NUMERIC(precision, scale)
Examples:
DECIMAL(10, 2) — Up to 10 digits total, 2 after decimal → -99,999,999.99 to 99,999,999.99DECIMAL(5, 0) — 5-digit integer → -99,999 to 99,999DECIMAL(18, 4) — High precision with 4 decimal places → typical for financial calculations123456789101112131415161718192021222324252627282930313233343536
-- Financial data: DECIMAL is mandatory for accuracyCREATE TABLE financial_transactions ( transaction_id INTEGER PRIMARY KEY, amount DECIMAL(15, 2) NOT NULL, -- Up to $9,999,999,999,999.99 tax_rate DECIMAL(5, 4) NOT NULL, -- Up to 9.9999 (e.g., 0.0825 = 8.25%) tax_amount DECIMAL(15, 2) NOT NULL, total_amount DECIMAL(15, 2) NOT NULL); -- Currency exchange rates require higher precisionCREATE TABLE exchange_rates ( rate_id INTEGER PRIMARY KEY, base_currency CHAR(3) NOT NULL, target_currency CHAR(3) NOT NULL, rate DECIMAL(18, 8) NOT NULL, -- 8 decimal places for precision effective_date DATE NOT NULL); -- Product pricing with precise controlCREATE TABLE products ( product_id INTEGER PRIMARY KEY, cost_price DECIMAL(12, 4) NOT NULL, -- Internal cost with 4 decimals retail_price DECIMAL(12, 2) NOT NULL, -- Customer-facing with 2 decimals discount_pct DECIMAL(5, 2) DEFAULT 0 -- Percentage (0.00 to 100.00)); -- Exact arithmetic demonstration-- Unlike FLOAT, DECIMAL preserves exact valuesSELECT 0.1 + 0.2; -- Returns exactly 0.3 with DECIMAL-- With FLOAT, this might return 0.30000000000000004 -- Precision matters in aggregationsSELECT SUM(amount) as total_revenueFROM financial_transactionsWHERE transaction_date >= '2024-01-01';-- DECIMAL ensures the sum is exact to the pennyIn binary floating-point, 0.1 cannot be represented exactly—it becomes 0.1000000000000000055511151231257827021181583404541015625. After millions of financial transactions, these tiny errors compound into significant discrepancies. DECIMAL avoids this entirely by using decimal-based (base-10) representation.
Understanding precision and scale is essential for correct DECIMAL type specification. Incorrect settings lead to data truncation, insertion failures, or wasted storage.
Definitions:
Example Analysis:
DECIMAL(7, 2)
├── Precision: 7 (total significant digits)
├── Scale: 2 (digits after decimal)
├── Integer digits: 7 - 2 = 5 (digits before decimal)
├── Range: -99,999.99 to 99,999.99
└── Examples: 12345.67, -99999.99, 0.01
| Declaration | Precision | Scale | Integer Digits | Valid Range | Example Values |
|---|---|---|---|---|---|
| DECIMAL(5, 2) | 5 | 2 | 3 | -999.99 to 999.99 | 123.45, -999.99 |
| DECIMAL(10, 0) | 10 | 0 | 10 | -9,999,999,999 to 9,999,999,999 | 1234567890 |
| DECIMAL(3, 3) | 3 | 3 | 0 | -0.999 to 0.999 | 0.123, -0.999 |
| DECIMAL(18, 4) | 18 | 4 | 14 | ±99,999,999,999,999.9999 | 12345678901234.5678 |
| DECIMAL(38, 10) | 38 | 10 | 28 | Maximum precision for most DBs | Scientific calculations |
12345678910111213141516171819202122232425262728293031
-- Demonstrating precision and scale behavior -- Case 1: Value fits within specificationCREATE TABLE price_test (price DECIMAL(7, 2));INSERT INTO price_test VALUES (12345.67); -- ✓ Works: 7 total digits, 2 decimalINSERT INTO price_test VALUES (99999.99); -- ✓ Works: Maximum valueINSERT INTO price_test VALUES (0.01); -- ✓ Works: Minimum positive -- Case 2: Exceeding integer digits (left of decimal)INSERT INTO price_test VALUES (123456.78); -- ✗ ERROR: 8 digits, only 7 allowed-- Error: numeric field overflow -- Case 3: Exceeding scale (right of decimal) - ROUNDING occursINSERT INTO price_test VALUES (123.456); -- Stored as 123.46 (rounded)INSERT INTO price_test VALUES (123.454); -- Stored as 123.45 (rounded down)-- Note: Some databases error instead of rounding (configurable) -- Case 4: Scale equals precision (fractional-only values)CREATE TABLE percentage (pct DECIMAL(3, 3));INSERT INTO percentage VALUES (0.125); -- ✓ Valid: 0.125INSERT INTO percentage VALUES (1.0); -- ✗ ERROR: Needs integer digit -- Case 5: Zero scale (effectively an integer)CREATE TABLE large_int (value DECIMAL(15, 0));INSERT INTO large_int VALUES (123456789012345); -- ✓ WorksINSERT INTO large_int VALUES (123.45); -- Stored as 123 (truncated) -- Storage implications vary by database:-- PostgreSQL: Variable storage (2 bytes + n bytes based on value)-- SQL Server: 5-17 bytes based on precision-- MySQL: Approximately (precision/2) + 1 bytesAlways allow headroom for growth. If current prices max at $9,999.99, don't use DECIMAL(6,2)—use DECIMAL(10,2) or higher. The storage difference is minimal, but schema changes to increase precision require table rebuilds.
Floating-point types (REAL, DOUBLE PRECISION, FLOAT) store approximate numeric values using the IEEE 754 standard. They excel at representing very large or very small numbers within a fixed storage footprint, but sacrifice exact precision for range.
The SQL Standard Types:
| Type | Storage | Precision | Range (approximate) |
|---|---|---|---|
| REAL | 4 bytes | ~7 significant digits | ±3.4 × 10^38 |
| DOUBLE PRECISION | 8 bytes | ~15 significant digits | ±1.8 × 10^308 |
| FLOAT(n) | Varies | n bits of mantissa | Depends on n |
How Floating-Point Works:
A floating-point number is stored as: sign × mantissa × 2^exponent
This representation allows compact storage of both 0.000000001 and 1,000,000,000, but only a fixed number of significant digits are preserved.
123456789101112131415161718192021222324252627282930313233343536
-- Floating-point type declarationsCREATE TABLE scientific_data ( measurement_id INTEGER PRIMARY KEY, temperature REAL, -- ~7 significant digits pressure DOUBLE PRECISION, -- ~15 significant digits velocity DOUBLE PRECISION, measurement_error REAL); -- Geographic coordinates (floating-point is appropriate)CREATE TABLE locations ( location_id INTEGER PRIMARY KEY, latitude DOUBLE PRECISION NOT NULL, -- -90.0 to 90.0 longitude DOUBLE PRECISION NOT NULL, -- -180.0 to 180.0 altitude_meters REAL -- Altitude with ~7 digits precision); -- Statistical dataCREATE TABLE statistics ( stat_id INTEGER PRIMARY KEY, mean DOUBLE PRECISION, std_deviation DOUBLE PRECISION, variance DOUBLE PRECISION, p_value DOUBLE PRECISION -- Often very small numbers); -- Scientific notation handling (automatic)INSERT INTO scientific_data (measurement_id, temperature, pressure)VALUES (1, 2.998e8, 1.01325e5); -- Speed of light, atmospheric pressure -- Demonstrating precision limitationsSELECT CAST(0.1 AS REAL) + CAST(0.2 AS REAL);-- Result: 0.30000001192092896 (not exactly 0.3) SELECT CAST(0.1 AS DOUBLE PRECISION) + CAST(0.2 AS DOUBLE PRECISION);-- Result: 0.30000000000000004 (closer, but still not exact)Never compare floating-point values for exact equality. WHERE price = 19.99 can fail even when you inserted 19.99. Instead, compare within a tolerance: WHERE ABS(price - 19.99) < 0.001. This is a fundamental characteristic of floating-point arithmetic.
Numeric type selection directly impacts storage requirements, index sizes, and query performance. Understanding these implications is essential for designing efficient database schemas.
Storage Comparison (typical implementations):
| Type | Storage | Use Case | Performance Characteristics |
|---|---|---|---|
| TINYINT | 1 byte | Flags, small codes | Fastest integer operations |
| SMALLINT | 2 bytes | Small ranges, codes | Excellent performance |
| INTEGER | 4 bytes | General purpose | Optimal balance |
| BIGINT | 8 bytes | Large values | 2× INTEGER storage impact |
| REAL | 4 bytes | Scientific, approximate | Hardware-accelerated |
| DOUBLE PRECISION | 8 bytes | High-precision float | Hardware-accelerated |
| DECIMAL(p ≤ 9) | 5 bytes | Small precision | Software arithmetic |
| DECIMAL(10 ≤ p ≤ 19) | 9 bytes | Medium precision | Software arithmetic |
| DECIMAL(20 ≤ p ≤ 38) | 17 bytes | High precision | Slower computation |
123456789101112131415161718192021222324252627282930313233343536373839
-- Example: Impact of numeric type on table size-- Scenario: 100 million row transaction table -- Option A: Using BIGINT everywhereCREATE TABLE transactions_bigint ( id BIGINT, -- 8 bytes user_id BIGINT, -- 8 bytes amount BIGINT, -- 8 bytes (storing cents) status BIGINT -- 8 bytes);-- Row size: ~32 bytes numeric data-- Table size: ~3.2 GB just for these columns -- Option B: Appropriate type sizingCREATE TABLE transactions_optimized ( id BIGINT, -- 8 bytes (global unique) user_id INTEGER, -- 4 bytes (2B users sufficient) amount DECIMAL(12, 2), -- 5-9 bytes status SMALLINT -- 2 bytes (< 100 statuses));-- Row size: ~19-23 bytes numeric data-- Table size: ~2 GB (35% smaller) -- Index size impact-- An index on BIGINT is 2× the size of INTEGER index-- For 100M rows: INTEGER index ~400MB vs BIGINT index ~800MB -- Query performance implications-- Smaller types = more rows fit in memory/cache-- Smaller indexes = faster index scans-- INTEGER comparisons typically equal or faster than BIGINT -- DECIMAL computation overhead-- DECIMAL arithmetic is software-computed (slower)-- FLOAT/DOUBLE use CPU floating-point unit (faster)-- For aggregations over millions of rows, this adds up SELECT SUM(amount) FROM transactions_optimized; -- DECIMAL: slowerSELECT SUM(amount) FROM scientific_data; -- DOUBLE: fasterFocus optimization on high-volume tables and frequently-accessed columns. A 4-byte savings per row in a billion-row table is 4GB of storage and significant I/O reduction. The same savings in a 1000-row lookup table is irrelevant.
While the SQL standard defines core numeric types, major database systems extend these with vendor-specific types optimized for particular use cases:
12345678910111213
-- PostgreSQL-specific examplesCREATE TABLE pg_demo ( id SERIAL PRIMARY KEY, -- Auto-incrementing INTEGER big_id BIGSERIAL UNIQUE, -- Auto-incrementing BIGINT balance NUMERIC(20, 2), -- Alias for DECIMAL latitude FLOAT8 -- Alias for DOUBLE PRECISION); -- PostgreSQL numeric functionsSELECT pg_typeof(100); -- Returns 'integer'SELECT pg_typeof(100.5); -- Returns 'numeric'SELECT pg_column_size(100::int); -- Returns 4 (bytes)SELECT pg_column_size(100::bigint); -- Returns 8 (bytes)Numeric type selection is a foundational database design decision with long-term implications for data integrity, storage efficiency, and query performance. Let's consolidate the key principles:
| Data Domain | Recommended Type | Rationale |
|---|---|---|
| Currency/Financial | DECIMAL(15, 2) or higher | Exact arithmetic, no rounding errors |
| Primary Keys | INTEGER or BIGINT | Fast comparisons, compact indexes |
| Counts/Quantities | INTEGER (or SMALLINT) | Exact whole numbers |
| Percentages | DECIMAL(5, 2) or (5, 4) | Exact percentage representation |
| Geographic Coords | DOUBLE PRECISION | Sufficient precision for submeter accuracy |
| Scientific Data | DOUBLE PRECISION | Wide range, hardware-accelerated |
| Boolean/Flags | SMALLINT or BIT | Minimal storage, clear semantics |
You now have comprehensive knowledge of SQL numeric types. You understand the exact/approximate distinction, precision and scale semantics, storage implications, and vendor-specific extensions. Next, we'll explore character and string types—equally critical for storing textual data correctly.