PostgreSQL Data Types Best Practices¶
Objective: Master senior-level PostgreSQL data type patterns for production systems. When you need to choose optimal data types, when you want to optimize storage and performance, when you need enterprise-grade data type strategies—these best practices become your weapon of choice.
Core Principles¶
- Storage Efficiency: Choose types that minimize storage overhead
- Performance: Select types optimized for query patterns
- Precision: Use appropriate precision for numeric types
- Validation: Leverage type constraints for data integrity
- Compatibility: Ensure type compatibility across systems
Numeric Data Types¶
Integer Types¶
-- Choose appropriate integer types based on range requirements
CREATE TABLE user_analytics (
id BIGSERIAL PRIMARY KEY, -- 64-bit auto-incrementing
user_id INTEGER NOT NULL, -- 32-bit signed integer (-2B to +2B)
session_count SMALLINT DEFAULT 0, -- 16-bit signed integer (-32K to +32K)
page_views INTEGER DEFAULT 0, -- 32-bit signed integer
total_time BIGINT DEFAULT 0, -- 64-bit signed integer
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Use appropriate integer types for different use cases
CREATE TABLE products (
id SERIAL PRIMARY KEY, -- Auto-incrementing primary key
category_id SMALLINT NOT NULL, -- Foreign key to categories (limited range)
price_cents INTEGER NOT NULL, -- Price in cents (avoid floating point)
stock_quantity INTEGER NOT NULL, -- Stock quantity
weight_grams INTEGER, -- Weight in grams
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Use BIGINT for high-volume counters
CREATE TABLE event_logs (
id BIGSERIAL PRIMARY KEY,
event_id BIGINT NOT NULL, -- High-volume event IDs
user_id BIGINT NOT NULL, -- Large user base
timestamp BIGINT NOT NULL, -- Unix timestamp in milliseconds
data JSONB
);
Decimal and Numeric Types¶
-- Use NUMERIC for precise decimal calculations
CREATE TABLE financial_transactions (
id SERIAL PRIMARY KEY,
amount NUMERIC(10,2) NOT NULL, -- 10 digits total, 2 decimal places
currency VARCHAR(3) NOT NULL,
exchange_rate NUMERIC(8,6), -- High precision for exchange rates
fee_amount NUMERIC(8,4), -- Fee with 4 decimal places
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Use appropriate precision for different use cases
CREATE TABLE measurements (
id SERIAL PRIMARY KEY,
temperature NUMERIC(5,2), -- Temperature with 2 decimal places
humidity NUMERIC(5,2), -- Humidity percentage
pressure NUMERIC(8,2), -- Atmospheric pressure
coordinates POINT, -- Geographic coordinates
recorded_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Avoid FLOAT/REAL for financial calculations
-- Use NUMERIC instead for exact decimal arithmetic
CREATE TABLE price_history (
id SERIAL PRIMARY KEY,
product_id INTEGER NOT NULL,
price NUMERIC(10,2) NOT NULL, -- Exact decimal price
-- price REAL NOT NULL, -- DON'T use REAL for money
valid_from TIMESTAMPTZ NOT NULL,
valid_to TIMESTAMPTZ
);
Floating Point Types¶
-- Use DOUBLE PRECISION for scientific calculations
CREATE TABLE sensor_data (
id SERIAL PRIMARY KEY,
sensor_id INTEGER NOT NULL,
temperature DOUBLE PRECISION, -- Scientific temperature readings
humidity DOUBLE PRECISION, -- Humidity measurements
pressure DOUBLE PRECISION, -- Atmospheric pressure
acceleration DOUBLE PRECISION, -- 3D acceleration vector
recorded_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Use REAL for less precise measurements where storage matters
CREATE TABLE weather_stations (
id SERIAL PRIMARY KEY,
station_name VARCHAR(100) NOT NULL,
latitude REAL, -- Approximate coordinates
longitude REAL, -- Approximate coordinates
elevation REAL, -- Elevation in meters
temperature REAL, -- Temperature readings
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
Character and Text Types¶
String Types¶
-- Choose appropriate string types based on length and usage
CREATE TABLE user_profiles (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL, -- Fixed max length
email VARCHAR(255) UNIQUE NOT NULL, -- Email addresses
first_name VARCHAR(100), -- Names
last_name VARCHAR(100),
bio TEXT, -- Variable length text
website_url VARCHAR(500), -- URLs
phone VARCHAR(20), -- Phone numbers
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Use CHAR for fixed-length strings
CREATE TABLE country_codes (
id SERIAL PRIMARY KEY,
country_code CHAR(2) NOT NULL, -- ISO country codes (e.g., 'US', 'CA')
country_name VARCHAR(100) NOT NULL,
currency_code CHAR(3) NOT NULL, -- ISO currency codes (e.g., 'USD', 'EUR')
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Use TEXT for large content
CREATE TABLE articles (
id SERIAL PRIMARY KEY,
title VARCHAR(200) NOT NULL,
content TEXT NOT NULL, -- Large text content
excerpt TEXT, -- Article excerpt
tags TEXT[], -- Array of text tags
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
Text Processing and Validation¶
-- Create functions for text validation
CREATE OR REPLACE FUNCTION validate_email(email TEXT)
RETURNS BOOLEAN AS $$
BEGIN
RETURN email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$';
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION validate_phone(phone TEXT)
RETURNS BOOLEAN AS $$
BEGIN
RETURN phone ~ '^\+?[1-9]\d{1,14}$';
END;
$$ LANGUAGE plpgsql;
-- Use constraints with validation functions
ALTER TABLE user_profiles ADD CONSTRAINT users_email_valid
CHECK (validate_email(email));
ALTER TABLE user_profiles ADD CONSTRAINT users_phone_valid
CHECK (phone IS NULL OR validate_phone(phone));
-- Create indexes for text search
CREATE INDEX idx_user_profiles_username_lower ON user_profiles (LOWER(username));
CREATE INDEX idx_user_profiles_email_lower ON user_profiles (LOWER(email));
CREATE INDEX idx_articles_content_gin ON articles USING GIN (to_tsvector('english', content));
Date and Time Types¶
Timestamp Types¶
-- Use TIMESTAMPTZ for timezone-aware timestamps
CREATE TABLE events (
id SERIAL PRIMARY KEY,
event_name VARCHAR(200) NOT NULL,
start_time TIMESTAMPTZ NOT NULL, -- Timezone-aware start time
end_time TIMESTAMPTZ NOT NULL, -- Timezone-aware end time
timezone VARCHAR(50) DEFAULT 'UTC', -- Event timezone
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Use TIMESTAMP for timezone-naive timestamps
CREATE TABLE system_logs (
id SERIAL PRIMARY KEY,
log_level VARCHAR(20) NOT NULL,
message TEXT NOT NULL,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Server timezone
source VARCHAR(100),
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Use DATE for date-only values
CREATE TABLE user_sessions (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
session_date DATE NOT NULL, -- Date only
login_count INTEGER DEFAULT 1,
total_duration INTERVAL, -- Duration as interval
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
Time Zone Handling¶
-- Create timezone-aware tables
CREATE TABLE global_events (
id SERIAL PRIMARY KEY,
event_name VARCHAR(200) NOT NULL,
event_time TIMESTAMPTZ NOT NULL,
timezone VARCHAR(50) NOT NULL,
duration INTERVAL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Function to convert timezone
CREATE OR REPLACE FUNCTION convert_to_timezone(
input_time TIMESTAMPTZ,
target_timezone TEXT
)
RETURNS TIMESTAMPTZ AS $$
BEGIN
RETURN input_time AT TIME ZONE target_timezone;
END;
$$ LANGUAGE plpgsql;
-- Query with timezone conversion
SELECT
event_name,
event_time,
event_time AT TIME ZONE timezone as local_time,
EXTRACT(TIMEZONE FROM event_time) as timezone_offset
FROM global_events
WHERE event_time >= '2024-01-01'::timestamptz;
Boolean and Enum Types¶
Boolean Types¶
-- Use BOOLEAN for true/false values
CREATE TABLE user_settings (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
email_notifications BOOLEAN DEFAULT TRUE,
sms_notifications BOOLEAN DEFAULT FALSE,
push_notifications BOOLEAN DEFAULT TRUE,
two_factor_enabled BOOLEAN DEFAULT FALSE,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Use boolean expressions in queries
SELECT
user_id,
CASE
WHEN email_notifications AND sms_notifications THEN 'Both'
WHEN email_notifications THEN 'Email Only'
WHEN sms_notifications THEN 'SMS Only'
ELSE 'None'
END as notification_preference
FROM user_settings
WHERE is_active = TRUE;
Enum Types¶
-- Create enum types for controlled vocabularies
CREATE TYPE user_status AS ENUM ('active', 'inactive', 'suspended', 'deleted');
CREATE TYPE order_status AS ENUM ('pending', 'processing', 'shipped', 'delivered', 'cancelled');
CREATE TYPE priority_level AS ENUM ('low', 'medium', 'high', 'critical');
-- Use enum types in tables
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
status user_status DEFAULT 'active',
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
status order_status DEFAULT 'pending',
priority priority_level DEFAULT 'medium',
total_amount NUMERIC(10,2) NOT NULL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Query with enum values
SELECT
u.username,
u.status,
COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.status = 'active'
GROUP BY u.id, u.username, u.status;
Array Types¶
Array Data Types¶
-- Use arrays for multiple values
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL,
description TEXT,
tags TEXT[], -- Array of text tags
categories INTEGER[], -- Array of category IDs
images TEXT[], -- Array of image URLs
specifications JSONB, -- JSON specifications
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Insert data with arrays
INSERT INTO products (name, tags, categories, images) VALUES
('Laptop Computer', ARRAY['electronics', 'computers', 'portable'],
ARRAY[1, 5, 12], ARRAY['laptop1.jpg', 'laptop2.jpg']),
('Smartphone', ARRAY['electronics', 'mobile', 'communication'],
ARRAY[1, 8, 15], ARRAY['phone1.jpg', 'phone2.jpg', 'phone3.jpg']);
-- Query arrays
SELECT
name,
tags,
array_length(tags, 1) as tag_count,
categories,
images
FROM products
WHERE 'electronics' = ANY(tags);
-- Use array operators
SELECT
name,
tags
FROM products
WHERE tags && ARRAY['electronics', 'mobile']; -- Overlap operator
SELECT
name,
tags
FROM products
WHERE tags @> ARRAY['electronics']; -- Contains operator
Array Functions and Operations¶
-- Array manipulation functions
CREATE TABLE user_preferences (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
favorite_categories TEXT[],
blocked_users INTEGER[],
notification_channels TEXT[],
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Add elements to arrays
UPDATE user_preferences
SET favorite_categories = array_append(favorite_categories, 'technology')
WHERE user_id = 1;
-- Remove elements from arrays
UPDATE user_preferences
SET favorite_categories = array_remove(favorite_categories, 'sports')
WHERE user_id = 1;
-- Query array functions
SELECT
user_id,
favorite_categories,
array_length(favorite_categories, 1) as category_count,
array_to_string(favorite_categories, ', ') as categories_string
FROM user_preferences
WHERE 'technology' = ANY(favorite_categories);
JSON and JSONB Types¶
JSONB Usage Patterns¶
-- Use JSONB for semi-structured data
CREATE TABLE user_profiles (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
profile_data JSONB NOT NULL,
preferences JSONB DEFAULT '{}',
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Insert JSONB data
INSERT INTO user_profiles (user_id, profile_data, preferences) VALUES
(1, '{"name": "John Doe", "age": 30, "location": "New York", "interests": ["technology", "travel"]}',
'{"theme": "dark", "language": "en", "notifications": {"email": true, "sms": false}}'),
(2, '{"name": "Jane Smith", "age": 25, "location": "San Francisco", "interests": ["art", "music"]}',
'{"theme": "light", "language": "en", "notifications": {"email": true, "sms": true}}');
-- Query JSONB data
SELECT
user_id,
profile_data ->> 'name' as name,
profile_data ->> 'age' as age,
profile_data ->> 'location' as location,
preferences ->> 'theme' as theme
FROM user_profiles
WHERE profile_data @> '{"interests": ["technology"]}';
-- Create indexes on JSONB
CREATE INDEX idx_user_profiles_data_gin ON user_profiles USING GIN (profile_data);
CREATE INDEX idx_user_profiles_name_btree ON user_profiles ((profile_data ->> 'name'));
CREATE INDEX idx_user_profiles_age_btree ON user_profiles (((profile_data ->> 'age')::integer));
JSONB Schema Validation¶
-- Create JSONB schema validation
CREATE OR REPLACE FUNCTION validate_user_profile(profile_data JSONB)
RETURNS BOOLEAN AS $$
BEGIN
-- Check required fields
IF NOT (profile_data ? 'name' AND profile_data ? 'age') THEN
RETURN FALSE;
END IF;
-- Validate age is numeric
IF NOT (profile_data ->> 'age' ~ '^\d+$') THEN
RETURN FALSE;
END IF;
-- Validate age range
IF (profile_data ->> 'age')::integer < 0 OR (profile_data ->> 'age')::integer > 150 THEN
RETURN FALSE;
END IF;
RETURN TRUE;
END;
$$ LANGUAGE plpgsql;
-- Add constraint for JSONB validation
ALTER TABLE user_profiles ADD CONSTRAINT user_profiles_data_valid
CHECK (validate_user_profile(profile_data));
UUID and Network Types¶
UUID Usage¶
-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Use UUID for distributed systems
CREATE TABLE distributed_entities (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
entity_type VARCHAR(50) NOT NULL,
data JSONB NOT NULL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Use UUID for external references
CREATE TABLE external_integrations (
id SERIAL PRIMARY KEY,
external_id UUID NOT NULL UNIQUE,
integration_type VARCHAR(50) NOT NULL,
status VARCHAR(20) DEFAULT 'active',
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Generate UUIDs
SELECT uuid_generate_v4() as new_uuid;
SELECT uuid_generate_v1() as time_based_uuid;
SELECT uuid_generate_v1mc() as mac_based_uuid;
Network Types¶
-- Use network types for IP addresses
CREATE TABLE access_logs (
id SERIAL PRIMARY KEY,
user_id INTEGER,
ip_address INET NOT NULL,
user_agent TEXT,
request_path VARCHAR(500),
response_code INTEGER,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Insert network data
INSERT INTO access_logs (user_id, ip_address, user_agent, request_path, response_code) VALUES
(1, '192.168.1.100', 'Mozilla/5.0...', '/api/users', 200),
(2, '10.0.0.50', 'Chrome/91.0...', '/api/orders', 201),
(NULL, '203.0.113.1', 'Bot/1.0', '/robots.txt', 404);
-- Query network data
SELECT
user_id,
ip_address,
CASE
WHEN ip_address << '192.168.0.0/16' THEN 'Private'
WHEN ip_address << '10.0.0.0/8' THEN 'Private'
WHEN ip_address << '172.16.0.0/12' THEN 'Private'
ELSE 'Public'
END as network_type
FROM access_logs
WHERE created_at >= CURRENT_DATE;
Custom Data Types¶
Creating Custom Types¶
-- Create composite types
CREATE TYPE address AS (
street VARCHAR(100),
city VARCHAR(50),
state VARCHAR(50),
zip_code VARCHAR(20),
country VARCHAR(50)
);
CREATE TYPE contact_info AS (
email VARCHAR(255),
phone VARCHAR(20),
address address
);
-- Use composite types in tables
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
contact contact_info NOT NULL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- Insert composite type data
INSERT INTO customers (name, contact) VALUES
('John Doe', ROW('john@example.com', '+1-555-0123',
ROW('123 Main St', 'New York', 'NY', '10001', 'USA'))),
('Jane Smith', ROW('jane@example.com', '+1-555-0456',
ROW('456 Oak Ave', 'San Francisco', 'CA', '94102', 'USA')));
-- Query composite types
SELECT
name,
(contact).email,
(contact).phone,
(contact).address.street,
(contact).address.city
FROM customers;
Type Conversion and Casting¶
-- Create type conversion functions
CREATE OR REPLACE FUNCTION convert_temperature(
value NUMERIC,
from_unit VARCHAR(10),
to_unit VARCHAR(10)
)
RETURNS NUMERIC AS $$
BEGIN
-- Convert to Celsius first
IF from_unit = 'fahrenheit' THEN
value := (value - 32) * 5/9;
ELSIF from_unit = 'kelvin' THEN
value := value - 273.15;
END IF;
-- Convert from Celsius to target unit
IF to_unit = 'fahrenheit' THEN
RETURN (value * 9/5) + 32;
ELSIF to_unit = 'kelvin' THEN
RETURN value + 273.15;
ELSE
RETURN value; -- Celsius
END IF;
END;
$$ LANGUAGE plpgsql;
-- Use type conversion
SELECT
convert_temperature(32, 'fahrenheit', 'celsius') as celsius,
convert_temperature(0, 'celsius', 'fahrenheit') as fahrenheit,
convert_temperature(273.15, 'kelvin', 'celsius') as celsius_from_kelvin;
Performance Optimization¶
Type Performance Analysis¶
# performance/type_performance.py
import psycopg2
import time
import json
from typing import Dict, List, Any
class DataTypePerformanceAnalyzer:
def __init__(self, connection_params):
self.conn_params = connection_params
def analyze_type_performance(self) -> Dict[str, Any]:
"""Analyze performance of different data types."""
conn = psycopg2.connect(**self.conn_params)
try:
with conn.cursor() as cur:
# Test integer types
integer_performance = self.test_integer_types(cur)
# Test string types
string_performance = self.test_string_types(cur)
# Test JSONB vs JSON
json_performance = self.test_json_types(cur)
return {
'integer_performance': integer_performance,
'string_performance': string_performance,
'json_performance': json_performance,
'analysis_timestamp': '2024-01-15T10:30:00Z'
}
except Exception as e:
print(f"Error analyzing type performance: {e}")
return {}
finally:
conn.close()
def test_integer_types(self, cursor) -> Dict[str, Any]:
"""Test performance of different integer types."""
# Create test tables
cursor.execute("""
CREATE TEMP TABLE test_integers (
id SERIAL PRIMARY KEY,
smallint_col SMALLINT,
integer_col INTEGER,
bigint_col BIGINT
)
""")
# Insert test data
cursor.execute("""
INSERT INTO test_integers (smallint_col, integer_col, bigint_col)
SELECT
(random() * 32767)::SMALLINT,
(random() * 2147483647)::INTEGER,
(random() * 9223372036854775807)::BIGINT
FROM generate_series(1, 10000)
""")
# Test query performance
start_time = time.time()
cursor.execute("SELECT COUNT(*) FROM test_integers WHERE smallint_col > 1000")
smallint_time = time.time() - start_time
start_time = time.time()
cursor.execute("SELECT COUNT(*) FROM test_integers WHERE integer_col > 1000000")
integer_time = time.time() - start_time
start_time = time.time()
cursor.execute("SELECT COUNT(*) FROM test_integers WHERE bigint_col > 1000000000")
bigint_time = time.time() - start_time
return {
'smallint_time': smallint_time,
'integer_time': integer_time,
'bigint_time': bigint_time
}
def test_string_types(self, cursor) -> Dict[str, Any]:
"""Test performance of different string types."""
# Create test tables
cursor.execute("""
CREATE TEMP TABLE test_strings (
id SERIAL PRIMARY KEY,
varchar_col VARCHAR(100),
text_col TEXT,
char_col CHAR(10)
)
""")
# Insert test data
cursor.execute("""
INSERT INTO test_strings (varchar_col, text_col, char_col)
SELECT
'test_string_' || generate_series,
'long_text_content_' || generate_series,
'char' || generate_series
FROM generate_series(1, 10000)
""")
# Test query performance
start_time = time.time()
cursor.execute("SELECT COUNT(*) FROM test_strings WHERE varchar_col LIKE 'test_string_%'")
varchar_time = time.time() - start_time
start_time = time.time()
cursor.execute("SELECT COUNT(*) FROM test_strings WHERE text_col LIKE 'long_text_content_%'")
text_time = time.time() - start_time
start_time = time.time()
cursor.execute("SELECT COUNT(*) FROM test_strings WHERE char_col LIKE 'char%'")
char_time = time.time() - start_time
return {
'varchar_time': varchar_time,
'text_time': text_time,
'char_time': char_time
}
def test_json_types(self, cursor) -> Dict[str, Any]:
"""Test performance of JSON vs JSONB."""
# Create test tables
cursor.execute("""
CREATE TEMP TABLE test_json (
id SERIAL PRIMARY KEY,
json_col JSON,
jsonb_col JSONB
)
""")
# Insert test data
cursor.execute("""
INSERT INTO test_json (json_col, jsonb_col)
SELECT
'{"id": ' || generate_series || ', "name": "test_' || generate_series || '", "value": ' || (random() * 1000)::INTEGER || '}',
'{"id": ' || generate_series || ', "name": "test_' || generate_series || '", "value": ' || (random() * 1000)::INTEGER || '}'::JSONB
FROM generate_series(1, 10000)
""")
# Test query performance
start_time = time.time()
cursor.execute("SELECT COUNT(*) FROM test_json WHERE json_col ->> 'name' LIKE 'test_%'")
json_time = time.time() - start_time
start_time = time.time()
cursor.execute("SELECT COUNT(*) FROM test_json WHERE jsonb_col ->> 'name' LIKE 'test_%'")
jsonb_time = time.time() - start_time
return {
'json_time': json_time,
'jsonb_time': jsonb_time,
'jsonb_advantage': (json_time - jsonb_time) / json_time * 100
}
# Usage
if __name__ == "__main__":
analyzer = DataTypePerformanceAnalyzer({
'host': 'localhost',
'database': 'production',
'user': 'analyzer_user',
'password': 'analyzer_password'
})
results = analyzer.analyze_type_performance()
print(json.dumps(results, indent=2))
TL;DR Runbook¶
Quick Start¶
-- 1. Choose appropriate numeric types
CREATE TABLE products (
id SERIAL PRIMARY KEY, -- Auto-incrementing
price NUMERIC(10,2) NOT NULL, -- Exact decimal
stock INTEGER NOT NULL, -- 32-bit integer
weight DOUBLE PRECISION -- Scientific precision
);
-- 2. Use appropriate string types
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE, -- Fixed max length
email VARCHAR(255) UNIQUE, -- Email addresses
bio TEXT, -- Variable length
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- 3. Use JSONB for semi-structured data
CREATE TABLE user_profiles (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
profile_data JSONB NOT NULL,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
Essential Patterns¶
# Complete PostgreSQL data types setup
def setup_postgresql_data_types():
# 1. Numeric types
# 2. Character and text types
# 3. Date and time types
# 4. Boolean and enum types
# 5. Array types
# 6. JSON and JSONB types
# 7. UUID and network types
# 8. Custom types and performance optimization
print("PostgreSQL data types setup complete!")
This guide provides the complete machinery for PostgreSQL data types excellence. Each pattern includes implementation examples, optimization strategies, and real-world usage patterns for enterprise PostgreSQL data type systems.