Agent SkillsAgent Skills
git-tao

postgresql-table-design

@git-tao/postgresql-table-design
git-tao
0
0 forks
Updated 4/7/2026
View on GitHub

Design a PostgreSQL-specific schema. Covers best-practices, data types, indexing, constraints, performance patterns, and advanced features

Installation

$npx agent-skills-cli install @git-tao/postgresql-table-design
Claude Code
Cursor
Copilot
Codex
Antigravity

Details

Path.claude/skills/postgresql/SKILL.md
Branchmain
Scoped Name@git-tao/postgresql-table-design

Usage

After installing, this skill will be available to your AI coding assistant.

Verify installation:

npx agent-skills-cli list

Skill Instructions


name: postgresql-table-design description: Design a PostgreSQL-specific schema. Covers best-practices, data types, indexing, constraints, performance patterns, and advanced features

PostgreSQL Table Design

Core Rules

  • Define a PRIMARY KEY for reference tables. Prefer BIGINT GENERATED ALWAYS AS IDENTITY; use UUID only when global uniqueness is needed.
  • Normalize first (to 3NF) to eliminate data redundancy; denormalize only for measured performance needs.
  • Add NOT NULL everywhere it's semantically required; use DEFAULTs for common values.
  • Create indexes for access paths you actually query: PK/unique (auto), FK columns (manual!), frequent filters/sorts.
  • Prefer TIMESTAMPTZ for event time; NUMERIC for money; TEXT for strings; BIGINT for integers.

PostgreSQL "Gotchas"

  • Identifiers: unquoted names are lowercased. Use snake_case.
  • Unique + NULLs: UNIQUE allows multiple NULLs. Use NULLS NOT DISTINCT (PG15+) to restrict.
  • FK indexes: PostgreSQL does not auto-index FK columns. Add them manually.
  • No silent coercions: length/precision overflows error out (no truncation).
  • Sequences have gaps: Normal behavior, don't try to "fix" it.

Data Types

Use CaseRecommended Type
IDsBIGINT GENERATED ALWAYS AS IDENTITY or UUID
IntegersBIGINT (prefer) or INTEGER
StringsTEXT (not VARCHAR(n) or CHAR(n))
MoneyNUMERIC(p,s) (never float)
TimestampsTIMESTAMPTZ (not TIMESTAMP)
BooleansBOOLEAN NOT NULL
JSON dataJSONB (not JSON)

Do Not Use

  • timestamp without time zone - use timestamptz
  • char(n) or varchar(n) - use text
  • money type - use numeric
  • serial - use generated always as identity

Constraints

  • PK: implicit UNIQUE + NOT NULL; creates B-tree index
  • FK: specify ON DELETE/UPDATE action; add explicit index on referencing column
  • UNIQUE: creates B-tree index; allows multiple NULLs unless NULLS NOT DISTINCT
  • CHECK: row-local constraints; NULL values pass

Indexing

Index TypeUse Case
B-treeDefault for equality/range queries
GINJSONB, arrays, full-text search
GiSTRanges, geometry, exclusion constraints
BRINVery large, naturally ordered data
-- Composite index (order matters)
CREATE INDEX ON orders (user_id, created_at);

-- Partial index
CREATE INDEX ON orders (user_id) WHERE status = 'active';

-- Expression index
CREATE INDEX ON users (LOWER(email));

-- Covering index
CREATE INDEX ON orders (id) INCLUDE (status, total);

Row-Level Security

ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Users can read own posts"
ON posts FOR SELECT
USING (user_id = auth.uid());

Examples

Users Table

CREATE TABLE users (
  user_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  email TEXT NOT NULL UNIQUE,
  name TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE UNIQUE INDEX ON users (LOWER(email));

Orders Table

CREATE TABLE orders (
  order_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  user_id BIGINT NOT NULL REFERENCES users(user_id),
  status TEXT NOT NULL DEFAULT 'PENDING' CHECK (status IN ('PENDING','PAID','CANCELED')),
  total NUMERIC(10,2) NOT NULL CHECK (total > 0),
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX ON orders (user_id);
CREATE INDEX ON orders (created_at);

JSONB Usage

CREATE TABLE profiles (
  user_id BIGINT PRIMARY KEY REFERENCES users(user_id),
  attrs JSONB NOT NULL DEFAULT '{}'
);
CREATE INDEX ON profiles USING GIN (attrs);

-- Query JSONB
SELECT * FROM profiles WHERE attrs @> '{"theme": "dark"}';