Menu

Database Technologies Topic

PostgreSQL Data Types (Very Important in Postgres)

A. Numeric Types

1. Integer Types

sql

-- Integer types with ranges
SMALLINT    -- -32,768 to 32,767 (2 bytes)
INTEGER     -- -2,147,483,648 to 2,147,483,647 (4 bytes) - Most common
BIGINT      -- -9.22e18 to 9.22e18 (8 bytes)
SERIAL      -- Auto-incrementing INTEGER (1 to 2,147,483,647)
BIGSERIAL   -- Auto-incrementing BIGINT (1 to 9.22e18)

-- Examples
CREATE TABLE users (
    id SERIAL PRIMARY KEY,           -- Auto-increment
    age SMALLINT,                    -- Small range
    views INTEGER DEFAULT 0,         -- Regular integer
    social_score BIGINT              -- Large numbers
);

-- SERIAL is actually:
-- INTEGER NOT NULL + DEFAULT nextval('sequence_name')

2. Decimal/Numeric Types (Exact precision)

sql

-- For monetary or exact calculations
NUMERIC(precision, scale)   -- Variable precision, exact
DECIMAL(precision, scale)   -- Same as NUMERIC (alias)

-- precision = total digits
-- scale = digits after decimal point

CREATE TABLE products (
    price NUMERIC(10, 2),      -- 8 digits before, 2 after (like 12345678.99)
    discount NUMERIC(5, 4),     -- 1 before, 4 after (like 0.1234)
    exact_value NUMERIC         -- Unlimited precision! (very large numbers)
);

-- Examples
INSERT INTO products VALUES 
    (123456.78, 0.1234, 123.4567890123456789);
-- NUMERIC is slower but exact - no floating-point errors

3. Floating-Point Types (Approximate)

sql

REAL        -- 6 decimal digits precision (4 bytes)
DOUBLE PRECISION  -- 15 decimal digits precision (8 bytes)

-- Use for scientific calculations where exactness isn't critical
CREATE TABLE measurements (
    temperature REAL,          -- Like 23.4567
    precision DOUBLE PRECISION -- Like 23.4567890123456
);

-- Be careful with equality checks!
SELECT * FROM measurements WHERE temperature = 23.4567; -- Might fail
SELECT * FROM measurements WHERE ABS(temperature - 23.4567) < 0.0001;

B. Character Types

1. Text Types Comparison

sql

VARCHAR(n)   -- Variable length with limit (n = max chars)
CHAR(n)      -- Fixed length, padded with spaces
TEXT         -- Unlimited length (no performance penalty!)

-- PostgreSQL-specific: VARCHAR without (n) = TEXT
VARCHAR      -- Same as TEXT (unlimited)

2. Examples & Best Practices

sql

CREATE TABLE messages (
    -- Fixed format (country codes, status codes)
    country_code CHAR(2),           -- Always 2 chars: 'US', 'FR'
    
    -- Limited variable length
    username VARCHAR(50),           -- Max 50 chars
    email VARCHAR(255),             -- Common email limit
    
    -- Unlimited text
    description TEXT,               -- For large content
    comments TEXT,                  -- No limit needed
    json_data TEXT                  -- Store JSON as text
    
    -- VARCHAR without limit = TEXT
    notes VARCHAR                   -- Same as TEXT
);

-- Best Practice: Use TEXT for most cases
-- TEXT has no performance penalty over VARCHAR
-- Only use VARCHAR(n) when you need constraint enforcement

3. Character Operations

sql

-- Length functions
SELECT LENGTH(description) FROM messages;  -- Character length
SELECT OCTET_LENGTH(description) FROM messages;  -- Bytes

-- Pattern matching
SELECT * FROM messages WHERE username LIKE 'A%';
SELECT * FROM messages WHERE description ~ 'error|warning';  -- Regex

-- String functions
SELECT CONCAT(username, ' (', email, ')') FROM messages;
SELECT SUBSTRING(description FROM 1 FOR 100) FROM messages;
SELECT UPPER(username), LOWER(email) FROM messages;

C. Date & Time Types

1. Types & Characteristics

sql

DATE          -- Only date: '2024-01-15'
TIME          -- Only time: '14:30:00'
TIMESTAMP     -- Date + time WITHOUT timezone: '2024-01-15 14:30:00'
TIMESTAMPTZ   -- Date + time WITH timezone (RECOMMENDED): '2024-01-15 14:30:00+00'
INTERVAL      -- Time span: '1 day 2 hours'

-- Storage sizes
-- DATE: 4 bytes
-- TIMESTAMP: 8 bytes
-- TIMESTAMPTZ: 8 bytes (stores as UTC internally)

2. TIMESTAMP vs TIMESTAMPTZ (CRITICAL!)

sql

-- TIMESTAMP (naive - stores what you give it)
CREATE TABLE events_naive (
    event_time TIMESTAMP
);
INSERT INTO events_naive VALUES ('2024-01-15 14:30:00');

-- TIMESTAMPTZ (timezone-aware - converts to UTC)
CREATE TABLE events_tz (
    event_time TIMESTAMPTZ  -- ALWAYS USE THIS!
);
INSERT INTO events_tz VALUES ('2024-01-15 14:30:00-05');  -- EST

-- Best Practice: ALWAYS use TIMESTAMPTZ
-- It handles timezone conversions automatically

3. Examples & Functions

sql

CREATE TABLE logs (
    id SERIAL PRIMARY KEY,
    created_at TIMESTAMPTZ DEFAULT NOW(),  -- Auto-set to current time
    event_date DATE,
    duration INTERVAL,
    start_time TIME
);

-- Insert with different formats
INSERT INTO logs (event_date, created_at) VALUES
    ('2024-01-15', '2024-01-15 14:30:00+00'),
    ('20240115', '2024-01-15T14:30:00Z'),  -- ISO format
    (CURRENT_DATE, CURRENT_TIMESTAMP);     -- Current time

-- Date/Time functions
SELECT 
    NOW(),                      -- Current timestamp with timezone
    CURRENT_DATE,               -- Today's date
    CURRENT_TIME,               -- Current time
    EXTRACT(YEAR FROM created_at) as year,
    EXTRACT(MONTH FROM created_at) as month,
    DATE_TRUNC('hour', created_at) as hour_start,
    created_at + INTERVAL '1 day' as tomorrow,
    AGE(created_at) as age;     -- Time since timestamp

-- Timezone conversions
SELECT 
    created_at AT TIME ZONE 'UTC' as utc_time,
    created_at AT TIME ZONE 'America/New_York' as ny_time
FROM logs;

D. Boolean Type

sql

-- Simple true/false
BOOLEAN  -- Aliases: BOOL

CREATE TABLE tasks (
    id SERIAL PRIMARY KEY,
    is_complete BOOLEAN DEFAULT FALSE,
    is_active BOOL,
    is_verified BOOLEAN
);

-- Valid boolean values
INSERT INTO tasks (is_complete, is_active) VALUES
    (TRUE, TRUE),      -- true
    (FALSE, FALSE),    -- false
    ('t', 'f'),        -- true/false
    ('yes', 'no'),     -- yes/no
    ('1', '0'),        -- 1/0
    ('on', 'off'),     -- on/off
    (NULL, NULL);      -- NULL

-- Boolean operations
SELECT * FROM tasks WHERE is_complete = TRUE;
SELECT * FROM tasks WHERE NOT is_complete;
SELECT * FROM tasks WHERE is_complete IS NULL;
SELECT * FROM tasks WHERE is_complete IS NOT FALSE;

-- Useful for flags and status indicators

E. UUID Type

sql

-- Universally Unique Identifier (128-bit)
-- Format: 123e4567-e89b-12d3-a456-426614174000
-- Requires extension for generation

-- Enable extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),  -- Version 4 (random)
    -- or DEFAULT uuid_generate_v1()  -- Version 1 (time-based)
    username VARCHAR(50)
);

-- Insert with generated UUID
INSERT INTO users (username) VALUES ('alice');
-- id auto-generated: e.g., 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11'

-- Manual insert
INSERT INTO users (id, username) VALUES 
    ('123e4567-e89b-12d3-a456-426614174000', 'bob');

-- Benefits over SERIAL:
-- 1. Globally unique (good for distributed systems)
-- 2. Obfuscates sequence (security)
-- 3. Merge data from different databases easily

-- Storage: 16 bytes (vs 4 bytes for INTEGER)

F. Arrays (PostgreSQL-Specific!)

1. Basic Array Usage

sql

-- Array of any data type: TYPE[]
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name TEXT,
    tags TEXT[],                    -- Array of text
    prices NUMERIC[],               -- Array of numbers
    available_sizes INTEGER[],      -- Array of integers
    schedule TIMESTAMPTZ[]          -- Array of timestamps
);

-- Insert array values
INSERT INTO products (name, tags, prices) VALUES
    ('Laptop', '{"electronics", "sale", "new"}', '{999.99, 899.99}'),
    ('Book', ARRAY['fiction', 'bestseller'], ARRAY[19.99, 14.99]);

-- Access elements (1-indexed!)
SELECT 
    tags[1] as primary_tag,         -- First element
    tags[2:3] as other_tags         -- Slice (elements 2-3)
FROM products;

2. Array Functions & Operators

sql

-- Array functions
SELECT 
    array_length(tags, 1) as tag_count,
    CARDINALITY(tags) as total_tags,      -- Number of elements
    unnest(tags) as individual_tag        -- Expand array to rows
FROM products;

-- Array operators
SELECT * FROM products WHERE
    'electronics' = ANY(tags),            -- Element exists
    'sale' = ALL(ARRAY['sale', 'new']),   -- All elements match
    tags @> '{"sale"}',                   -- Contains
    tags && '{"sale", "clearance"}',      -- Overlaps
    tags <@ '{"electronics", "sale", "new", "used"}';  -- Contained by

-- Modify arrays
UPDATE products SET 
    tags = array_append(tags, 'featured'),
    prices = array_prepend(1099.99, prices);

-- 2D arrays
CREATE TABLE matrices (
    id SERIAL,
    matrix INTEGER[][]
);
INSERT INTO matrices VALUES (1, '{{1,2,3},{4,5,6},{7,8,9}}');

G. JSON / JSONB (PostgreSQL Superpower!)

1. JSON vs JSONB

sql

-- JSON: Stores exact text, preserves whitespace, order
-- JSONB: Binary storage, no whitespace, no order, INDEXABLE!

CREATE TABLE documents (
    id SERIAL,
    metadata JSON,      -- Exact storage
    data JSONB          -- Binary storage (USE THIS!)
);

-- Insert JSON
INSERT INTO documents (metadata, data) VALUES 
(
    '{"name": "John", "age": 30, "active": true}',  -- JSON
    '{"name": "John", "age": 30, "active": true}'   -- JSONB
);

-- JSONB advantages:
-- 1. Faster querying
-- 2. Supports indexes (GIN indexes)
-- 3. Deduplicates keys
-- 4. More operators/functions

-- Use JSONB unless you need exact text preservation

2. JSONB Operators & Functions

sql

-- Access operators
SELECT 
    data -> 'name' as name_json,        -- Returns JSON
    data ->> 'name' as name_text,       -- Returns TEXT
    data -> 'tags' -> 0 as first_tag,   -- Nested access
    data #> '{address,city}' as city    -- Path access
FROM documents;

-- Check keys/values
SELECT * FROM documents WHERE
    data ? 'name',                      -- Has key
    data ?| array['name', 'email'],     -- Has any key
    data ?& array['name', 'email'],     -- Has all keys
    data @> '{"active": true}',         -- Contains
    data <@ '{"name": "John", "age": 30}';  -- Contained by

-- Modify JSONB
UPDATE documents SET 
    data = data || '{"email": "john@example.com"}',  -- Merge
    data = data - 'temp_field';                      -- Remove key

-- JSON functions
SELECT 
    jsonb_pretty(data),                 -- Pretty print
    jsonb_array_length(data->'tags'),   -- Array length
    jsonb_object_keys(data),            -- List keys
    jsonb_each(data)                    -- Key-value pairs
FROM documents;

3. JSONB Indexing (Performance!)

sql

-- GIN indexes for JSONB (Generalized Inverted Index)
CREATE INDEX idx_documents_data ON documents USING GIN (data);

-- Specific path indexes
CREATE INDEX idx_documents_name ON documents ((data ->> 'name'));

-- Query using index
SELECT * FROM documents WHERE data @> '{"active": true}';
-- This uses the GIN index!

-- Partial indexes on JSONB
CREATE INDEX idx_active_users ON documents ((data ->> 'name'))
WHERE data @> '{"active": true}';

H. Type Conversion & Casting

sql

-- Explicit casting
SELECT '123'::INTEGER;
SELECT 123::TEXT;
SELECT CURRENT_TIMESTAMP::DATE;

-- Using CAST function
SELECT CAST('123' AS INTEGER);
SELECT CAST(123 AS TEXT);

-- Implicit casting (where supported)
SELECT '123' + 456;  -- Works: '123' converted to integer

-- Type-specific functions
SELECT to_char(123.456, '999.99');
SELECT to_date('2024-01-15', 'YYYY-MM-DD');
SELECT to_timestamp('2024-01-15 14:30', 'YYYY-MM-DD HH24:MI');

I. Choosing the Right Type

sql

-- Primary keys:
--   SERIAL/BIGSERIAL - Simple, auto-increment
--   UUID - Distributed systems, security

-- Text data:
--   TEXT - Almost always
--   VARCHAR(n) - Only for constraint enforcement

-- Numbers:
--   INTEGER - Most integers
--   NUMERIC - Money, exact calculations
--   REAL/DOUBLE - Scientific, approximate

-- Dates:
--   TIMESTAMPTZ - ALWAYS for timestamps
--   DATE - Only dates

-- Special:
--   JSONB - Semi-structured data
--   ARRAY[] - When you need arrays
--   BOOLEAN - True/false flags

J. Custom Types & Domains

sql

-- Create custom type
CREATE TYPE address AS (
    street TEXT,
    city TEXT,
    postal_code VARCHAR(10),
    country CHAR(2)
);

CREATE TABLE customers (
    id SERIAL,
    home_address address
);

-- Create domain (constrained type)
CREATE DOMAIN email_address AS TEXT
    CHECK (VALUE ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$');

CREATE TABLE users (
    email email_address  -- Now validated on insert!
);

PostgreSQL's rich type system is one of its greatest strengths, allowing you to model data more naturally and leverage database features for validation and performance.

Comments (0)

No comments yet. Be the first to comment!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?