Skip to content
Back to skills

Primary Key Coverage Identity

ASecurity

Primary Key Coverage & Identity Columns

  • 17 stars
  • 0 votes
  • 0 copies
  • 5 views
  • Added September 2, 2026
ai-agentsgosqlazuretestingdatabase

Security analysis

A100/100

Scanned September 2, 2026

npx -y skills add CarlosCaPe/octorato --skill primary-key-coverage-identity --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Primary Key Coverage Identity?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Primary Key Coverage Identity
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/carloscape-primary-key-coverage-identity/badge)](https://www.skillsdirectory.com/skills/carloscape-primary-key-coverage-identity)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: primary-key-coverage-identity
description: "Primary Key Coverage & Identity Columns"
metadata:
  short-description: "Primary Key Coverage & Identity Columns"
  original-index: 28
---

# Primary Key Coverage & Identity Columns

> Source: [PostgreSQL Best Practices](../DOCUMENTS/PostgreSQL_BestPractices_Azure.md)
> -- "Primary keys: Prefer BIGINT identity or UUID" and Audit Finding #5

## What

Ensuring every table has a primary key, and that PK columns use the modern
`GENERATED BY DEFAULT AS IDENTITY` pattern instead of `serial` + `nextval()`.

## Why

Tables without primary keys are a schema smell:
- No guaranteed row uniqueness -- duplicates can accumulate silently
- `UPDATE` and `DELETE` by natural key are fragile and error-prone
- Logical replication REQUIRES a primary key (or replica identity)
- ORMs (Entity Framework, Prisma) expect a PK for change tracking
- `VACUUM` and `HOT` updates work better with a stable ctid path

## How

### Audit: Find tables without primary keys
```sql
SELECT t.tablename
FROM pg_tables t
LEFT JOIN (
    SELECT tc.table_name
    FROM information_schema.table_constraints tc
    WHERE tc.constraint_type = 'PRIMARY KEY'
      AND tc.table_schema = 'public'
) pk ON pk.table_name = t.tablename
WHERE t.schemaname = 'public'
  AND pk.table_name IS NULL
ORDER BY t.tablename;
```

### Add a PK to an existing table (BIGINT identity)
```sql
-- Step 1: Add the identity column
ALTER TABLE public."EmailConfig"
    ADD COLUMN "Id" bigint GENERATED BY DEFAULT AS IDENTITY;

-- Step 2: Backfill (identity auto-generates for new rows)
-- Existing rows already got values from Step 1

-- Step 3: Add the PK constraint
ALTER TABLE public."EmailConfig"
    ADD CONSTRAINT pk_emailconfig PRIMARY KEY ("Id");
```

### Add a PK using an existing column
```sql
-- If the table already has a candidate key column
ALTER TABLE public."Regions"
    ADD CONSTRAINT pk_regions PRIMARY KEY ("RegionId");
```

### New table template (always include PK)
```sql
CREATE TABLE public."MyNewTable" (
    "Id" bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    "Name" text NOT NULL,
    "CreatedAt" timestamptz NOT NULL DEFAULT now(),
    "UpdatedAt" timestamptz NOT NULL DEFAULT now()
);
```

## BIGINT vs UUID Decision

| Type | Pros | Cons | Use When |
|------|------|------|----------|
| `bigint IDENTITY` | Fast, compact (8 bytes), sequential, index-friendly | Not globally unique | Single-database, sequential access |
| `uuid v4` | Globally unique, no coordination needed | 16 bytes, random = index fragmentation | Distributed systems, external IDs |
| `uuid v7` | Globally unique + time-sortable | 16 bytes, requires extension | Distributed + temporal ordering |

**Default recommendation**: `bigint GENERATED BY DEFAULT AS IDENTITY` unless
there is a specific distributed/external ID requirement.

## GENERATED BY DEFAULT vs GENERATED ALWAYS

| Mode | Explicit INSERT Allowed? | Use When |
|------|--------------------------|----------|
| `BY DEFAULT` | Yes -- app can provide the ID | Backward compat, data migration, testing |
| `ALWAYS` | No (unless `OVERRIDING SYSTEM VALUE`) | Strict auto-generation |

**Default recommendation**: `BY DEFAULT` for backward compatibility with
existing application code. See Skill #17 for serial-to-identity conversion.

## When to Use

- Every table audit that finds missing PKs (Audit Finding #5)
- Every new table definition (always include PK)
- When converting serial columns to identity (Skill #17)

## Where We Used It

- ****: Added PKs to `Hospitals` and `ScribeErrorLog`
- ****: Added PK to `ReportWords` (`ReportId` -> `Id` + identity)
- ****: Added PK to `Regions`
- **scheduling Audit**: Finding #6 flagged 2 serial columns

## Related Skills

- **Skill #17** (Serial to Identity Conversion) -- convert existing serial to identity
- **Skill #27** (Naming Conventions) -- PK naming pattern: `pk_<table>`
- **Skill #09** (Foreign Key Constraints) -- parent table MUST have PK/UNIQUE

## References

- [PostgreSQL Best Practices](../DOCUMENTS/PostgreSQL_BestPractices_Azure.md)
  -- "Schema design and data types" and Audit Finding #5
- [scheduling Audit TDD](../DOCUMENTS/scheduling_DB_Audit_TDD.md)
  -- Finding #6

## Gotchas

- Adding a column to a populated table acquires `AccessExclusiveLock` briefly
- `GENERATED BY DEFAULT AS IDENTITY` creates a sequence tied to the column
  via `pg_depend` -- dropping the column drops the sequence automatically
- If the table already has data, ensure no duplicate values exist in the
  candidate key column before adding a UNIQUE or PK constraint
- ORM scaffolding may need re-running after adding PKs to previously
  keyless tables

---

*Category: DDL | Origin: , , *

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.