Azpg schema design
Skill lestermarch/postgres-ai-experts/skills/azpg-schema-design
Design relational schemas — tables, primary/foreign keys, constraints, data types, identity/sequences and naming — on Azure Database for PostgreSQL Flexible Server. Use this skill whenever the task involves modelling data, creating or altering tables, choosing a column type, designing keys or constraints, normalising (or deliberately denormalising) a schema, planning a migration DDL, or reviewing an existing schema for correctness on Azure Postgres — even when the user just says "design a database for X" or "how should I store this". Covers type selection, keys, constraints, generated columns, JSONB-vs-columns trade-offs, and applying DDL safely under Flexible Server's no-superuser role model.From its SKILL.md
npx -y skills add lestermarch/postgres-ai-experts --skill azpg-schema-designAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
10.6 KB, ~2.4k tokens by cl100k_base, as published. Nobody here has run it
Schema design on Azure Database for PostgreSQL Flexible Server
Model data correctly the first time: pick the right types, define keys and
constraints that make illegal states unrepresentable, and apply DDL safely.
"PostgreSQL can be used for everything" — so lean on the type system, constraints,
and JSONB before reaching for another store.
This skill is read-write. Inspecting an existing schema is safe and runs
automatically; anything that mutates structure (CREATE/ALTER/DROP TABLE,
adding constraints, type changes) is a guarded write — present the DDL as a
plan, get explicit confirmation, and apply it transaction-wrapped via
scripts/apply_migration.sh (--dry-run capable)
rather than free-handing schema changes on a live database. See
Safety protocol.
Core DDL here is standard PostgreSQL. Flexible-Server-specific concerns (ownership under
azure_pg_admin, storage/IOPS impact of rewrites, connection limits that shape the model) are inazure-constraints.md. Deep type/constraint guidance is inreference.md.
Live instance context (dynamic injection)
Read the target database's real shape before proposing a model, so advice fits
what exists. PGCONN is a libpq connection string. These are read-only.
- Postgres major version (governs available syntax — e.g.
MERGEneeds 15+): !psql "$PGCONN" -tAc "SHOW server_version;" 2>/dev/null || echo "(unknown — ask the user for connection details)" - Existing tables and approximate row counts (top by volume):
!
psql "$PGCONN" -tAc "SELECT relname, n_live_tup FROM pg_stat_user_tables ORDER BY n_live_tup DESC LIMIT 15;" 2>/dev/null || echo "(could not connect)" - Extensions that unlock extra types (citext, hstore, uuid-ossp, postgis):
!
psql "$PGCONN" -tAc "SELECT extname FROM pg_extension ORDER BY extname;" 2>/dev/null || echo "(unknown)"
If injection is unavailable, run
scripts/inspect_schema.sql and read its output.
Never assume a table, column, or extension exists — verify.
When to use this skill
Trigger on: "design a database/schema for …", "how should I store …", "what
column type for …", "should this be one table or many", "add a foreign key /
constraint / unique index", "model a many-to-many", "should I use JSONB or
columns", "review my schema", or any request that produces CREATE TABLE /
ALTER TABLE. For partitioning a large table, hand off to
partitioning-strategy; for index choice on an existing schema, hand off to
azpg-index-selection; for vector columns, hand off to azpg-pgvector-rag.
Decision flow
Steps 1–2 are read/inspect (safe); steps 3–4 are write/mutate (guarded).
- Inspect what exists (section above) and clarify the domain: entities, relationships, cardinalities, access patterns, expected volume/growth.
- Design on paper first. Choose types
(
reference.md), define keys and constraints, decide normalised vs.JSONBper attribute (reference.md), and name things consistently (naming). - Generate DDL and review it as a plan — including the constraint and index implications and whether any change forces a table rewrite.
- Apply (guarded write) via
scripts/apply_migration.sh, transaction-wrapped, after a--dry-run.
Naming conventions
Consistency beats any particular style. Defaults that age well on Postgres:
snake_casefor every identifier; avoid quoted mixed-case names (they force quoting forever).- Singular or plural table names — pick one and hold it. Plural (
orders) is common. - Primary key
id(or<entity>_idif you prefer explicit FKs to readorder_id = orders.order_id). - Foreign keys
<referenced_table_singular>_id(e.g.customer_id). - Constraints named for grep-ability:
pk_orders,fk_orders_customer,uq_users_email,ck_orders_total_nonneg. Named constraints make laterALTER TABLE ... DROP CONSTRAINTunambiguous. - Reserve suffixes:
_atfortimestamptz,_onfordate,is_/has_for booleans.
Read / inspect steps (safe · auto)
These never mutate state.
-
Full schema report —
scripts/inspect_schema.sqllists tables, columns + types, primary/foreign keys, unique and check constraints, and flags common smells (un-indexed foreign keys,textPKs,timestampwithout time zone). -
Check a would-be constraint holds before adding it (safe — read-only):
-- Will "ALTER TABLE ... ADD CONSTRAINT ... CHECK (total >= 0)" succeed? SELECT count(*) AS violations FROM orders WHERE NOT (total >= 0); -
See whether a type change will rewrite the table — a rewrite locks and costs IOPS.
ALTER TYPEthat is binary-coercible (e.g.varchar(50)→text) avoids a rewrite; most others do not. Confirm on a copy first.
Write / mutate steps (explicit · guarded)
Do not apply DDL without presenting it as a plan and getting explicit
confirmation. On Flexible Server there is no superuser; objects are owned by
the connecting role and elevated actions use azure_pg_admin. Prefer
scripts/apply_migration.sh — it wraps the DDL in a single transaction (so a
failure rolls back cleanly) and prints the statements under --dry-run.
Design principles that go into the DDL
- Make illegal states unrepresentable.
NOT NULL,CHECK,UNIQUE, andFOREIGN KEYare cheaper than application bugs. Add them at design time. - Every foreign key needs its own index on the referencing column — Postgres
does not create it automatically, and its absence makes the parent's
DELETE/UPDATEscan the child. - Prefer
timestamptzovertimestamp,textovervarchar(n)unless a real length limit exists, andnumericfor money (neverfloat). - Identity over serial. Use
GENERATED ALWAYS AS IDENTITYfor surrogate keys on new tables;serialis legacy. - Natural vs surrogate keys: a surrogate
idplus aUNIQUEon the natural key gives you stable FKs and enforced business identity.
CREATE TABLE customers (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT uq_customers_email UNIQUE (email)
);
CREATE TABLE orders (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
customer_id bigint NOT NULL REFERENCES customers (id),
total numeric(12,2) NOT NULL,
status text NOT NULL DEFAULT 'pending',
placed_at timestamptz NOT NULL DEFAULT now(),
CONSTRAINT ck_orders_total_nonneg CHECK (total >= 0),
CONSTRAINT ck_orders_status CHECK (status IN ('pending','paid','shipped','cancelled'))
);
-- FKs are NOT auto-indexed — add it or pay for it on every parent delete.
CREATE INDEX ix_orders_customer_id ON orders (customer_id);
Safer patterns for changes on populated tables
-
Adding a
NOT NULLcolumn: add it nullable with a default, backfill, thenSET NOT NULL— avoids a long exclusive lock on large tables. -
Adding a FK or CHECK to a big table: add it
NOT VALID, thenVALIDATE CONSTRAINTin a separate step (validation takes a weaker lock).ALTER TABLE orders ADD CONSTRAINT fk_orders_customer FOREIGN KEY (customer_id) REFERENCES customers (id) NOT VALID; ALTER TABLE orders VALIDATE CONSTRAINT fk_orders_customer; -
New index on a live table:
CREATE INDEX CONCURRENTLY(cannot run in a transaction block — the migration script has a--concurrentlypath for this).
More patterns and the full type-selection guide: reference.md.
A worked model is in examples/ecommerce_schema.md.
Safety protocol
- Inspect first. Read the live schema and volumes before proposing changes.
- Plan → confirm → execute for every DDL statement. Call out any change that forces a table rewrite or takes an ACCESS EXCLUSIVE lock, and its blast radius (row count, estimated duration, IOPS).
- Use
apply_migration.sh, not free-hand DDL. Run--dry-runfirst and show the statements. It wraps everything in one transaction unless--concurrentlyis set (for online index builds, which cannot be transactional). - Precheck backups before destructive changes (
DROP, type rewrite) — Flexible Server provides automated backups / PITR; confirm a recent restore point. Seeazure-constraints.md. - Prefer non-blocking sequences (
NOT VALID+VALIDATE, add-nullable + backfill +SET NOT NULL) on large tables to avoid long locks. - Never guess ownership. If a change fails on privileges, check the object
owner and
azure_pg_adminmembership rather than escalating blindly.
Bundled files
reference.md— deep reference: type-selection table, JSONB-vs-columns, keys & constraints, generated/identity columns, enums vs check vs lookup table, normalization vs denormalization, safe-change recipes.azure-constraints.md— Flexible Server specifics: no-superuser ownership model, storage/IOPS and rewrites, connection limits and how they shape the model, extension-backed types on the allow-list.scripts/— one read-only inspector (inspect_schema.sql) and one guarded,--dry-run-capable migration applier (apply_migration.sh).examples/ecommerce_schema.md— a worked, fully-constrained model with commentary.
What ships with it: 7 files
24.4 KB alongside SKILL.md, 1 of them executable
examples/
- ecommerce_schema.md3.6 KB
scripts/
- apply_migration.shruns2.5 KB
- inspect_schema.sql2.7 KB
- README.md1.1 KB
- azure-constraints.md3.8 KB
- EVALUATION.md3.7 KB
- reference.md6.9 KB