← Back to list

postgresql-table-design
by git-tao
Personal portfolio website for taotang.io - AI Systems Engineer consulting
⭐ 0🍴 0📅 Jan 16, 2026
SKILL.md
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; useUUIDonly 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 Case | Recommended Type |
|---|---|
| IDs | BIGINT GENERATED ALWAYS AS IDENTITY or UUID |
| Integers | BIGINT (prefer) or INTEGER |
| Strings | TEXT (not VARCHAR(n) or CHAR(n)) |
| Money | NUMERIC(p,s) (never float) |
| Timestamps | TIMESTAMPTZ (not TIMESTAMP) |
| Booleans | BOOLEAN NOT NULL |
| JSON data | JSONB (not JSON) |
Do Not Use
timestampwithout time zone - usetimestamptzchar(n)orvarchar(n)- usetextmoneytype - usenumericserial- usegenerated always as identity
Constraints
- PK: implicit UNIQUE + NOT NULL; creates B-tree index
- FK: specify
ON DELETE/UPDATEaction; 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 Type | Use Case |
|---|---|
| B-tree | Default for equality/range queries |
| GIN | JSONB, arrays, full-text search |
| GiST | Ranges, geometry, exclusion constraints |
| BRIN | Very 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"}';
Score
Total Score
50/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
○LICENSE
ライセンスが設定されている
0/10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
○最近の活動
3ヶ月以内に更新がある
0/10
○フォーク
10回以上フォークされている
0/5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
○タグ
1つ以上のタグが設定されている
0/5
Reviews
💬
Reviews coming soon