Azpg schema design
Skill lestermarch/postgres-ai-experts/skills/azpg-schema-design
Composable AI agents and skills for operating Azure Database for PostgreSQL Flexible Server - PostgreSQL can be used for everything.
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.
3 things to look at
- 20 days oldThe repository was created 20 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 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.
What its author says it does
Copied from the file, not written here
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.
SKILL.md
10.6 KB, 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.