BlogData ArchitectureDatabase Naming Conventions: A Complete Style Guide
Data Architecture

Database Naming Conventions: A Complete Style Guide

Establish consistent database naming conventions for tables, columns, indexes, and constraints that improve maintainability and team productivity.

mdatool Team·February 24, 2026·10 min read
["Naming""Best Practices""Standards"]

Introduction

Database naming conventions are the foundation of maintainable data architectures. Poor naming standards cost enterprises millions in technical debt—developers spend 30% more time understanding ambiguous schemas, queries break during refactoring, and data quality tools fail to parse inconsistent field names.

This comprehensive guide provides battle-tested naming conventions for tables, columns, constraints, and indexes based on patterns used by Fortune 500 companies and leading tech platforms. Whether you're building greenfield systems or standardizing legacy databases, these standards will improve code clarity and team productivity.


Why Naming Conventions Matter

Development Productivity:

  • Developers spend 60% of time reading code vs. writing (Martin Fowler)
  • Clear names reduce onboarding time by 40%
  • Consistent patterns enable code generation and automation
  • Self-documenting schemas reduce need for external documentation

Data Quality Impact:

  • ETL tools rely on naming patterns for schema inference
  • BI tools automatically generate readable labels from field names
  • Data catalogs index and search based on naming conventions
  • Migration scripts break when conventions change mid-project

Team Collaboration:

  • Multiple teams can work independently without conflicts
  • Code reviews focus on logic, not naming debates
  • Junior developers follow established patterns
  • Cross-functional teams (data, analytics, engineering) share vocabulary

Business Cost:

  • Gartner estimates poor data quality costs $12.9M annually per organization
  • Schema refactoring projects average $500K in enterprise environments
  • Support tickets increase 3x with inconsistent field names

Core Naming Principles

Principle 1: Be Descriptive and Unambiguous

Bad:

CREATE TABLE data (
    id INT,
    val VARCHAR(100),
    dt DATE
);

Good:

CREATE TABLE customer_orders (
    order_id BIGINT,
    order_total_amount DECIMAL(12,2),
    order_date DATE
);

Why: Generic names like "data", "val", "dt" require constant context switching. Descriptive names are self-documenting.


Principle 2: Consistency Over Preference

Bad (Inconsistent):

CREATE TABLE users (user_id INT);
CREATE TABLE Products (ProductID INT);
CREATE TABLE order-items (OrderItemID INT);

Good (Consistent):

CREATE TABLE users (user_id INT);
CREATE TABLE products (product_id INT);
CREATE TABLE order_items (order_item_id INT);

Why: Pick ONE convention (snake_case, camelCase, PascalCase) and apply it everywhere. Consistency matters more than the specific choice.


Principle 3: Avoid Reserved Words

Bad:

CREATE TABLE user (
    user INT,
    order VARCHAR(100),
    select VARCHAR(50)
);

Good:

CREATE TABLE users (
    user_id INT,
    order_status VARCHAR(100),
    selection_type VARCHAR(50)
);

Why: Reserved words require quoting and cause syntax errors across different SQL dialects.


Table Naming Standards

Use Plural Nouns for Entity Tables

Pattern: {entity_name}s (plural)

Examples:

CREATE TABLE customers (...);
CREATE TABLE orders (...);
CREATE TABLE products (...);
CREATE TABLE invoices (...);

Rationale: Tables hold multiple rows, so plural makes semantic sense. This is the most common convention in industry.

Alternative (Singular): Some teams use singular (customer, order, product). Either is fine—just be consistent.


Junction Tables (Many-to-Many)

Pattern: {table1}_{table2} or {table1}_to_{table2}

Examples:

CREATE TABLE users_roles (...);
CREATE TABLE products_categories (...);
CREATE TABLE students_courses (...);

Alternative with context:

CREATE TABLE course_enrollments (...);  -- Better than students_courses
CREATE TABLE product_categorizations (...);

Temporal Tables (Historical Snapshots)

Pattern: {table_name}_history or {table_name}_archive

Examples:

CREATE TABLE customers_history (...);
CREATE TABLE orders_archive (...);

Staging/Temporary Tables

Pattern: stg_{table_name} or tmp_{table_name} or {table_name}_staging

Examples:

CREATE TABLE stg_customer_imports (...);
CREATE TABLE tmp_daily_aggregations (...);
CREATE TABLE raw_api_responses (...);

Fact and Dimension Tables (Data Warehouses)

Pattern:

  • Facts: fact_{process} or fct_{process}
  • Dimensions: dim_{entity} or dimension_{entity}

Examples:

CREATE TABLE fact_sales (...);
CREATE TABLE fact_website_events (...);

CREATE TABLE dim_customer (...);
CREATE TABLE dim_product (...);
CREATE TABLE dim_date (...);

Column Naming Standards

Primary Keys

Pattern: {table_name_singular}_id

Examples:

CREATE TABLE customers (
    customer_id BIGINT PRIMARY KEY
);

CREATE TABLE orders (
    order_id BIGINT PRIMARY KEY
);

CREATE TABLE order_items (
    order_item_id BIGINT PRIMARY KEY
);

Why: Makes foreign key relationships explicit and searchable.

Alternative (Minimal):

CREATE TABLE customers (
    id BIGINT PRIMARY KEY  -- Context is clear from table name
);

Foreign Keys

Pattern: Use exact name from referenced table

Example:

CREATE TABLE orders (
    order_id BIGINT PRIMARY KEY,
    customer_id BIGINT NOT NULL,  -- Matches customers.customer_id
    
    FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);

Why: Makes joins self-documenting and enables automated relationship detection.


Boolean Flags

Pattern: is_{adjective} or has_{noun} or {verb}ed

Examples:

CREATE TABLE users (
    is_active BOOLEAN,
    is_verified BOOLEAN,
    has_subscription BOOLEAN,
    email_confirmed BOOLEAN,
    deleted BOOLEAN  -- or is_deleted
);

Avoid: Negative booleans like is_not_active (confusing when FALSE)


Dates and Timestamps

Pattern: {event}_date or {event}_at or {event}_timestamp

Examples:

CREATE TABLE orders (
    order_id BIGINT PRIMARY KEY,
    
    created_at TIMESTAMP,      -- When record was created
    updated_at TIMESTAMP,      -- When record was last modified
    deleted_at TIMESTAMP,      -- Soft delete timestamp
    
    order_date DATE,           -- Business date of order
    shipped_date DATE,
    delivered_at TIMESTAMP     -- Precise delivery time
);

Convention:

  • _at suffix for TIMESTAMP (includes time)
  • _date suffix for DATE (date only)
  • _on alternative to _date

Amounts and Quantities

Pattern: {attribute}_amount or {attribute}_quantity or {attribute}_count

Examples:

CREATE TABLE orders (
    subtotal_amount DECIMAL(12,2),
    tax_amount DECIMAL(12,2),
    total_amount DECIMAL(12,2),
    
    item_count INT,
    units_sold INT
);

CREATE TABLE products (
    stock_quantity INT,
    reorder_quantity INT
);

Why: Distinguishes between counts (integers) and monetary amounts (decimals).


Enumerated Values / Status Fields

Pattern: {attribute}_status or {attribute}_type or {attribute}_category

Examples:

CREATE TABLE orders (
    payment_status VARCHAR(20),  -- 'PENDING', 'PAID', 'REFUNDED'
    fulfillment_status VARCHAR(20),
    order_source VARCHAR(50)     -- 'WEB', 'MOBILE', 'API'
);

CREATE TABLE products (
    product_type VARCHAR(50),
    availability_status VARCHAR(20)
);

JSON/JSONB Columns

Pattern: {attribute}_data or {attribute}_json or {attribute}_metadata

Examples:

CREATE TABLE events (
    event_id BIGINT PRIMARY KEY,
    event_data JSONB,
    metadata JSONB,
    custom_attributes JSONB
);

Constraint Naming Standards

Primary Key Constraints

Pattern: pk_{table_name} or {table_name}_pkey

Example:

ALTER TABLE customers 
ADD CONSTRAINT pk_customers PRIMARY KEY (customer_id);

-- PostgreSQL auto-generates: customers_pkey

Foreign Key Constraints

Pattern: fk_{child_table}_{parent_table} or fk_{child_table}_{column}

Examples:

ALTER TABLE orders
ADD CONSTRAINT fk_orders_customers 
FOREIGN KEY (customer_id) REFERENCES customers(customer_id);

-- More specific when multiple FKs to same table
ALTER TABLE order_items
ADD CONSTRAINT fk_order_items_product
FOREIGN KEY (product_id) REFERENCES products(product_id);

ALTER TABLE order_items
ADD CONSTRAINT fk_order_items_order
FOREIGN KEY (order_id) REFERENCES orders(order_id);

Unique Constraints

Pattern: uq_{table_name}_{column} or uk_{table_name}_{column}

Examples:

ALTER TABLE users
ADD CONSTRAINT uq_users_email UNIQUE (email);

ALTER TABLE products
ADD CONSTRAINT uq_products_sku UNIQUE (sku);

-- Composite unique
ALTER TABLE user_roles
ADD CONSTRAINT uq_user_roles_user_role UNIQUE (user_id, role_id);

Check Constraints

Pattern: chk_{table_name}_{description}

Examples:

ALTER TABLE products
ADD CONSTRAINT chk_products_price_positive 
CHECK (price >= 0);

ALTER TABLE orders
ADD CONSTRAINT chk_orders_dates 
CHECK (shipped_date >= order_date);

ALTER TABLE employees
ADD CONSTRAINT chk_employees_age_range 
CHECK (age BETWEEN 18 AND 100);

Index Naming Standards

Pattern: idx_{table_name}_{column}_{suffix}

Suffixes:

  • No suffix: Regular B-tree index
  • _unique: Unique index
  • _gin: GIN index (full-text, JSONB)
  • _gist: GiST index (spatial, range)

Examples:

-- Single column index
CREATE INDEX idx_orders_customer_id ON orders(customer_id);

-- Composite index
CREATE INDEX idx_orders_customer_date ON orders(customer_id, order_date);

-- Unique index
CREATE UNIQUE INDEX idx_users_email_unique ON users(email);

-- Full-text index
CREATE INDEX idx_products_search_gin ON products USING GIN(search_vector);

-- Partial index
CREATE INDEX idx_orders_pending ON orders(order_id) 
WHERE status = 'PENDING';

Case Conventions Comparison

ConventionExampleProsCons
snake_casecustomer_ordersReadable, SQL standard, portableLonger names
camelCasecustomerOrdersCompact, Java/C# styleCase-sensitive DBs, harder to read
PascalCaseCustomerOrders.NET standardCase-sensitive DBs, SQL Server default
kebab-casecustomer-ordersURLs, CSSRequires quoting in SQL (AVOID)
SCREAMING_SNAKECUSTOMER_ORDERSLegacy systemsSHOUTING, hard to read

Recommendation: Use snake_case for maximum compatibility and readability across all databases (PostgreSQL, MySQL, Oracle, SQL Server, Snowflake, BigQuery).


Industry-Specific Conventions

Healthcare (HL7 FHIR Alignment)

CREATE TABLE patients (
    patient_id BIGINT PRIMARY KEY,
    medical_record_number VARCHAR(50),
    first_name VARCHAR(100),
    last_name VARCHAR(100),
    birth_date DATE,
    gender VARCHAR(20)
);

CREATE TABLE encounters (
    encounter_id BIGINT PRIMARY KEY,
    patient_id BIGINT,
    encounter_type VARCHAR(50),
    admission_datetime TIMESTAMP,
    discharge_datetime TIMESTAMP
);

Finance (Regulatory Compliance)

CREATE TABLE accounts (
    account_id BIGINT PRIMARY KEY,
    account_number VARCHAR(20) UNIQUE,
    account_type VARCHAR(50),
    balance_amount DECIMAL(18,2),
    currency_code CHAR(3),
    opened_date DATE,
    closed_date DATE
);

CREATE TABLE transactions (
    transaction_id BIGINT PRIMARY KEY,
    account_id BIGINT,
    transaction_type VARCHAR(50),
    transaction_amount DECIMAL(18,2),
    transaction_timestamp TIMESTAMP(6),  -- Microsecond precision
    posting_date DATE
);

E-commerce

CREATE TABLE products (
    product_id BIGINT PRIMARY KEY,
    sku VARCHAR(100) UNIQUE,
    product_name VARCHAR(500),
    category_id INT,
    price DECIMAL(12,2),
    stock_quantity INT
);

CREATE TABLE orders (
    order_id BIGINT PRIMARY KEY,
    order_number VARCHAR(50) UNIQUE,
    customer_id BIGINT,
    order_date DATE,
    subtotal_amount DECIMAL(12,2),
    tax_amount DECIMAL(12,2),
    total_amount DECIMAL(12,2)
);

Common Naming Mistakes

Mistake 1: Cryptic Abbreviations

Bad:

CREATE TABLE cst_ord_itm (
    coi_id INT,
    ord_id INT,
    prd_id INT,
    qty INT,
    amt DECIMAL(10,2)
);

Good:

CREATE TABLE customer_order_items (
    order_item_id BIGINT,
    order_id BIGINT,
    product_id BIGINT,
    quantity INT,
    amount DECIMAL(10,2)
);

Mistake 2: Redundant Prefixes

Bad:

CREATE TABLE tbl_customers (
    customer_customer_id INT,
    customer_first_name VARCHAR(100)
);

Good:

CREATE TABLE customers (
    customer_id INT,
    first_name VARCHAR(100)
);

Mistake 3: Hungarian Notation in Columns

Bad:

CREATE TABLE products (
    int_product_id INT,
    str_product_name VARCHAR(200),
    dec_price DECIMAL(10,2),
    bool_is_active BOOLEAN
);

Good:

CREATE TABLE products (
    product_id INT,
    product_name VARCHAR(200),
    price DECIMAL(10,2),
    is_active BOOLEAN
);

Why: Data types are already defined in schema; encoding in name is redundant.


Mistake 4: Inconsistent Pluralization

Bad:

CREATE TABLE customer (...);
CREATE TABLE orders (...);
CREATE TABLE product (...);

Good (Plural):

CREATE TABLE customers (...);
CREATE TABLE orders (...);
CREATE TABLE products (...);

Migration Strategy for Legacy Databases

Step 1: Audit Current Naming

-- Find tables violating naming convention
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
  AND table_name !~ '^[a-z][a-z0-9_]*$'  -- snake_case pattern
ORDER BY table_name;

-- Find columns with inconsistent naming
SELECT table_name, column_name
FROM information_schema.columns
WHERE table_schema = 'public'
  AND column_name !~ '^[a-z][a-z0-9_]*$'
ORDER BY table_name, column_name;

Step 2: Create Naming Standard Document

# Database Naming Standards

## Tables
- Use lowercase snake_case
- Use plural nouns
- Prefix staging tables with stg_
- Prefix temporary tables with tmp_

## Columns
- Use lowercase snake_case
- Primary keys: {table_singular}_id
- Foreign keys: match referenced table PK
- Booleans: is_{adjective} or has_{noun}
- Dates: {event}_date or {event}_at

## Constraints
- PK: pk_{table_name}
- FK: fk_{child_table}_{parent_table}
- UQ: uq_{table_name}_{column}
- CHK: chk_{table_name}_{description}

## Indexes
- idx_{table_name}_{column}

Step 3: Gradual Migration with Views

-- Create new table with correct naming
CREATE TABLE customers_new (
    customer_id BIGINT PRIMARY KEY,
    first_name VARCHAR(100),
    last_name VARCHAR(100),
    email VARCHAR(255)
);

-- Migrate data
INSERT INTO customers_new 
SELECT CustomerID, FirstName, LastName, Email
FROM tbl_Customers;

-- Create compatibility view
CREATE VIEW tbl_Customers AS
SELECT 
    customer_id AS CustomerID,
    first_name AS FirstName,
    last_name AS LastName,
    email AS Email
FROM customers_new;

-- Applications continue working via view
-- Gradually migrate app code to use customers_new
-- Eventually drop view and rename table

Tools and Automation

mdatool Naming Auditor

Automated tools can enforce naming conventions:

# Audit database for violations
mdatool naming-audit --database production \
  --standards-file naming-standards.yml \
  --output violations.csv

# Auto-generate migration scripts
mdatool naming-fix --input violations.csv \
  --output migration.sql

Pre-commit Hooks for DDL

# .pre-commit-config.yaml
- repo: local
  hooks:
    - id: check-sql-naming
      name: Check SQL naming conventions
      entry: python scripts/check_naming.py
      language: python
      files: \.sql$

Real-World Style Guides

Example 1: Airbnb SQL Style Guide

-- Tables: plural, snake_case
CREATE TABLE listings (
    listing_id BIGINT PRIMARY KEY,
    host_id BIGINT,
    listing_title VARCHAR(500),
    nightly_price DECIMAL(10,2),
    created_at TIMESTAMP
);

-- CTEs: Descriptive names
WITH active_listings AS (
    SELECT * FROM listings WHERE is_active = true
),
top_hosts AS (
    SELECT host_id, COUNT(*) as listing_count
    FROM active_listings
    GROUP BY host_id
    HAVING COUNT(*) >= 10
)
SELECT * FROM top_hosts;

Example 2: GitLab Database Style Guide

  • Use snake_case for all identifiers
  • Table names are always plural
  • Primary keys named id (minimal approach)
  • Foreign keys include target table name
  • No camelCase, no PascalCase
CREATE TABLE projects (
    id BIGINT PRIMARY KEY,
    namespace_id BIGINT,  -- FK to namespaces.id
    name VARCHAR(255),
    created_at TIMESTAMP
);

Conclusion

Database naming conventions are not just cosmetic—they directly impact development velocity, data quality, and long-term maintainability. Organizations that enforce consistent standards see:

  • 40% faster onboarding for new developers
  • 60% reduction in naming-related bugs
  • Automated tooling becomes possible (code generation, migrations)
  • Cross-team collaboration improves with shared vocabulary

Key Takeaways:

  1. Choose snake_case for maximum compatibility
  2. Be consistent across all tables, columns, constraints
  3. Use descriptive names over cryptic abbreviations
  4. Document your standards in a team style guide
  5. Automate enforcement with linters and auditors

Start improving your database naming today with mdatool's Naming Auditor and automated migration tools.

Try our free tools at mdatool.com

M

mdatool Team

Data modeling experts helping enterprises build better databases and data architectures.

Ready to improve your data architecture?

Free tools for DDL conversion, SQL analysis, naming standards, and more.

Get Started Free