SQL Data Types Reference: Complete Guide

Learn the 7 essential data type categories in SQL, from strings and numbers to dates, JSON, and specialized types.

350+
SQL Examples
140+
Interview Questions
270+
Pages
4.8/5
Reader Rating

Why Understanding SQL Data Types Is Critical

Choosing the right data type is one of the most important database design decisions. Data types determine how data is stored, how much space it uses, what operations you can perform, and how queries perform. Wrong data type choices lead to wasted storage, slow queries, and data integrity issues.

Each database system (PostgreSQL, MySQL, SQL Server) has its own set of data types, though many are standard across systems. Understanding the differences between VARCHAR and TEXT, DECIMAL and FLOAT, or TIMESTAMP and TIMESTAMPTZ prevents common bugs and performance problems. Data types also enforce constraints at the database level, catching errors before they reach your application.

This guide covers 7 fundamental data type categories with practical examples. You'll learn when to use each type, common pitfalls to avoid, and how to choose between similar types. These principles apply across all major SQL databases.

Common Data Type Mistakes

Using FLOAT for Money

Storing monetary values in FLOAT or REAL causes rounding errors due to floating-point precision issues. Always use DECIMAL or NUMERIC for money. Set appropriate precision and scale (e.g., DECIMAL(10,2) for dollar amounts). Financial calculations require exact arithmetic, which only DECIMAL provides.

Storing Dates as Strings

Using VARCHAR to store dates prevents date arithmetic, range queries, and proper sorting. Always use DATE, TIMESTAMP, or DATETIME types. These types enable date functions, automatic validation, and timezone handling. String dates also waste space and make queries more complex.

Ignoring Timezone Issues

Using TIMESTAMP without timezone causes bugs in applications with users across multiple timezones. Use TIMESTAMPTZ (PostgreSQL) or store timestamps in UTC. Always convert to the user's timezone in the application layer, never store local times in the database without timezone information.

Overusing TEXT and JSON Types

Using TEXT for all strings or JSON for all structured data bypasses the benefits of proper schema design. TEXT fields cannot be efficiently indexed beyond a prefix. JSON makes queries complex and slow. Normalize data into proper columns when you need to query, filter, or join on values regularly.

Essential SQL Data Type Categories

These seven categories cover all the fundamental data types you'll use in SQL databases.

Character and String Types

Store text data with CHAR, VARCHAR, and TEXT

Example:

-- Fixed-length string (pads with spaces)
CREATE TABLE users (
  country_code CHAR(2),        -- Always 2 characters: 'US', 'UK'
  username VARCHAR(50),        -- Variable, up to 50 chars
  bio TEXT                     -- Unlimited length text
);

-- VARCHAR vs CHAR: VARCHAR saves space, CHAR is faster for fixed-length
INSERT INTO users VALUES ('US', 'john_doe', 'Software developer');

-- String functions work with all text types
SELECT username, LENGTH(username), UPPER(username)
FROM users;

When to Use:

Use CHAR for fixed-length codes (country codes, status flags). Use VARCHAR for variable-length text with a known maximum (names, emails, URLs). Use TEXT for long-form content (descriptions, articles, comments) without a length limit. VARCHAR is most common for general text storage.

Numeric Types (Integers and Decimals)

Store whole numbers and precise decimal values

Example:

-- Integer types: SMALLINT, INTEGER, BIGINT
CREATE TABLE products (
  product_id SERIAL PRIMARY KEY,     -- Auto-incrementing integer
  quantity SMALLINT,                 -- -32,768 to 32,767
  views_count INTEGER,               -- -2 billion to 2 billion
  total_sales BIGINT                 -- Very large numbers
);

-- Decimal types: DECIMAL, NUMERIC (exact), FLOAT, REAL (approximate)
CREATE TABLE orders (
  order_id INTEGER PRIMARY KEY,
  subtotal DECIMAL(10, 2),          -- 10 digits total, 2 after decimal
  tax_rate NUMERIC(5, 4),           -- 0.0825 (8.25%)
  weight FLOAT                       -- Approximate for scientific data
);

-- DECIMAL for money (exact), FLOAT for measurements (approximate)
INSERT INTO orders VALUES (1, 199.99, 0.0825, 2.456);

When to Use:

Use INTEGER for IDs, counts, and whole numbers. Use BIGINT for very large values (timestamps, large counters). Use DECIMAL/NUMERIC for money and financial calculations (exact precision). Use FLOAT/REAL only for scientific or approximate data where slight precision loss is acceptable. Never use FLOAT for money.

Date and Time Types

Store dates, times, and timestamps

Example:

-- Date and time types
CREATE TABLE events (
  event_id INTEGER PRIMARY KEY,
  event_date DATE,                   -- 2025-01-10 (no time)
  start_time TIME,                   -- 14:30:00 (no date)
  created_at TIMESTAMP,              -- 2025-01-10 14:30:00
  updated_at TIMESTAMPTZ             -- With timezone (PostgreSQL)
);

-- Insert date/time values
INSERT INTO events VALUES (
  1,
  '2025-06-15',
  '14:30:00',
  '2025-01-10 14:30:00',
  '2025-01-10 14:30:00-05:00'
);

-- Extract components and perform date math
SELECT
  event_date,
  EXTRACT(YEAR FROM event_date) as year,
  event_date + INTERVAL '7 days' as one_week_later
FROM events;

When to Use:

Use DATE for dates without time (birthdays, deadlines). Use TIME for times without dates (business hours, schedules). Use TIMESTAMP for exact moments (created_at, updated_at). Use TIMESTAMPTZ (PostgreSQL) or DATETIME (MySQL) for timezone-aware timestamps. Always use timezone-aware types for applications with users in multiple timezones.

Boolean and Bit Types

Store true/false values and binary flags

Example:

-- Boolean type
CREATE TABLE users (
  user_id INTEGER PRIMARY KEY,
  email VARCHAR(100),
  is_active BOOLEAN DEFAULT TRUE,
  is_verified BOOLEAN DEFAULT FALSE,
  is_admin BOOLEAN DEFAULT FALSE
);

-- Boolean values: TRUE, FALSE, NULL
INSERT INTO users VALUES (1, 'user@example.com', TRUE, FALSE, FALSE);

-- Query with boolean conditions
SELECT email, is_active
FROM users
WHERE is_active = TRUE AND is_verified = FALSE;

-- MySQL: Use TINYINT(1) or BIT(1) as boolean substitute
CREATE TABLE settings (
  setting_id INT PRIMARY KEY,
  enabled TINYINT(1) DEFAULT 1    -- 0 = false, 1 = true
);

When to Use:

Use BOOLEAN for true/false flags (is_active, is_deleted, has_access). Booleans are clear and self-documenting. In MySQL, use TINYINT(1) as a boolean substitute. Avoid storing boolean-like data as strings ('yes'/'no') or numbers (0/1) when a proper BOOLEAN type is available.

JSON and Structured Data Types

Store JSON documents and complex data structures

Example:

-- PostgreSQL: JSON and JSONB types
CREATE TABLE products (
  product_id INTEGER PRIMARY KEY,
  name VARCHAR(100),
  specifications JSONB,             -- Binary JSON (faster, indexable)
  metadata JSON                     -- Text JSON
);

-- Insert JSON data
INSERT INTO products VALUES (
  1,
  'Laptop',
  '{"brand": "Dell", "ram": "16GB", "storage": "512GB SSD"}',
  '{"tags": ["electronics", "computers"], "rating": 4.5}'
);

-- Query JSON fields
SELECT name, specifications->>'brand' as brand
FROM products
WHERE specifications->>'ram' = '16GB';

-- MySQL: Use JSON type (available in MySQL 5.7+)
CREATE TABLE orders (
  order_id INT PRIMARY KEY,
  customer_data JSON
);

When to Use:

Use JSON/JSONB for semi-structured data that varies between rows (product specifications, user preferences, API responses). JSONB (PostgreSQL) is faster for queries and supports indexing. Avoid JSON for data that should be properly normalized into tables. JSON is great for flexibility but makes complex queries harder.

Binary and Blob Types

Store binary data like images, files, and encrypted data

Example:

-- Binary types for files and encrypted data
CREATE TABLE attachments (
  attachment_id INTEGER PRIMARY KEY,
  filename VARCHAR(255),
  file_data BYTEA,                  -- PostgreSQL: binary data
  file_size INTEGER,
  mime_type VARCHAR(100)
);

-- MySQL: Use BLOB types
CREATE TABLE files (
  file_id INT PRIMARY KEY,
  file_name VARCHAR(255),
  file_content BLOB,                -- Up to 65KB
  large_file MEDIUMBLOB             -- Up to 16MB
);

-- Store encrypted passwords (use hashing, not encryption)
CREATE TABLE users (
  user_id INTEGER PRIMARY KEY,
  username VARCHAR(50),
  password_hash BYTEA               -- bcrypt/argon2 hash
);

-- Note: Storing files in database is often not recommended
-- Consider storing file paths and using object storage (S3, etc.)

When to Use:

Use BYTEA (PostgreSQL) or BLOB (MySQL) for binary data like encrypted values, small files, or cryptographic keys. However, storing large files (images, videos, PDFs) in databases is generally discouraged. Instead, store files in object storage (S3, Azure Blob) and save file paths/URLs in the database. This improves performance and scalability.

Specialized Types (UUID, ENUM, Arrays)

Use database-specific types for unique identifiers and structured data

Example:

-- UUID for globally unique identifiers
CREATE TABLE sessions (
  session_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id INTEGER,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- ENUM for predefined values
CREATE TYPE order_status AS ENUM ('pending', 'processing', 'shipped', 'delivered');
CREATE TABLE orders (
  order_id INTEGER PRIMARY KEY,
  status order_status DEFAULT 'pending'
);

-- PostgreSQL: Array types
CREATE TABLE articles (
  article_id INTEGER PRIMARY KEY,
  title VARCHAR(200),
  tags TEXT[]                       -- Array of text values
);

INSERT INTO articles VALUES (1, 'SQL Guide', ARRAY['sql', 'database', 'tutorial']);

-- Query array data
SELECT title FROM articles WHERE 'sql' = ANY(tags);

When to Use:

Use UUID for distributed systems where globally unique IDs are needed (microservices, API keys, session IDs). UUIDs avoid ID collisions across databases. Use ENUM for columns with a fixed set of values (status, priority, role). Use arrays (PostgreSQL) for lists where you need to query elements. Consider normalizing instead of using arrays for better query performance.

Your First Steps with SQL Data Types

Step 1: Match Data to Appropriate Types

Analyze what kind of data each column will store. Use VARCHAR for text, INTEGER for whole numbers, DECIMAL for money, DATE/TIMESTAMP for time data, and BOOLEAN for true/false flags.

CREATE TABLE users (
  user_id INTEGER PRIMARY KEY,
  email VARCHAR(100),
  created_at TIMESTAMP,
  is_active BOOLEAN
);

Step 2: Consider Storage and Performance

Smaller data types use less storage and perform better. Use SMALLINT instead of INTEGER when appropriate, VARCHAR with length limits instead of TEXT, and appropriate precision for DECIMAL values.

-- Good: Appropriate sizing
CREATE TABLE products (
  quantity SMALLINT,           -- -32K to 32K is enough
  price DECIMAL(10,2)         -- $99,999,999.99 max
);

Step 3: Test and Validate Your Choices

Insert sample data and test edge cases. Verify that constraints work (length limits, numeric ranges). Check query performance with realistic data volumes. Adjust types if needed before production.

-- Test edge cases
INSERT INTO products VALUES (32767, 99999999.99);  -- Max values
SELECT * FROM products WHERE price > 1000.00;      -- Test queries

Frequently Asked Questions About SQL Data Types

Should I use VARCHAR or TEXT for string columns?

Use VARCHAR with a reasonable length limit (e.g., VARCHAR(255)) when you know the maximum size and want to enforce it. Use TEXT for long-form content without a practical length limit (articles, comments, descriptions). In most modern databases, TEXT performs similarly to VARCHAR. VARCHAR with limits helps catch data errors early.

When should I use DECIMAL instead of FLOAT?

Always use DECIMAL/NUMERIC for money and financial calculations because it stores exact values. FLOAT and REAL use approximate floating-point math and can introduce rounding errors (0.1 + 0.2 might not equal 0.3). Use FLOAT only for scientific data, measurements, or when slight precision loss is acceptable.

What is the difference between TIMESTAMP and TIMESTAMPTZ?

TIMESTAMP stores date and time without timezone information. TIMESTAMPTZ (PostgreSQL) stores the timestamp with timezone, automatically converting to UTC. Use TIMESTAMPTZ for applications with users in multiple timezones to avoid timezone conversion bugs. In MySQL, DATETIME is similar to TIMESTAMP, while TIMESTAMP converts to UTC automatically.

How do I choose between INTEGER, BIGINT, and SMALLINT?

Use SMALLINT for small ranges (-32,768 to 32,767) like age or quantity. Use INTEGER for most whole numbers including IDs. Use BIGINT for very large numbers like timestamps in milliseconds or large counters. Start with INTEGER and upgrade to BIGINT only when needed. Smaller types save storage but provide less range.

When should I use JSON columns instead of normalizing data?

Use JSON for semi-structured data that varies significantly between rows (product attributes, user preferences, API responses). Use normalized tables when you need to query, index, or join on the data frequently. JSON is flexible but makes complex queries harder and less efficient. Balance flexibility with query performance needs.

Is it a good practice to store files in the database?

Generally no, especially for large files. Storing files (images, videos, PDFs) in the database increases its size, slows down backups, and reduces performance. Instead, store files in object storage (AWS S3, Azure Blob, Cloudflare R2) and save the file path or URL in the database. Only store very small files or encrypted data as binary.

What is the difference between CHAR and VARCHAR?

CHAR is fixed-length and pads values with spaces to the specified length. VARCHAR is variable-length and only uses the space needed. CHAR(10) always uses 10 bytes even for 'AB'. VARCHAR(10) uses 2 bytes for 'AB'. Use CHAR for fixed-length codes (country codes, status flags) and VARCHAR for everything else.

Should I use UUID or auto-incrementing INTEGER for primary keys?

Use INTEGER (SERIAL, AUTO_INCREMENT) for single-database applications. It is simpler, faster, and uses less storage. Use UUID for distributed systems, microservices, or when you need globally unique IDs that do not reveal record count. UUIDs are larger (16 bytes vs 4 bytes) and slightly slower but prevent ID collisions across databases.

Go Deeper with the Complete SQL Crash Course

This guide covers SQL data types fundamentals. The complete book includes comprehensive database design, normalization, and advanced data modeling techniques.

What You'll Get:

  • 350+ practical SQL examples and exercises covering all skill levels
  • 140+ SQL interview questions with detailed solutions
  • Complete e-commerce database for hands-on practice
  • SQL code library with 380+ SQL statements ready to use
  • 2 bonus SQL cheat sheets: Glossary and Dialect Differences

Trusted by developers worldwide

4.8/5 rating - Bestselling SQL book