Foreign Key Constraints
Scanned 9/2/2026
Install to Claude Code
npx -y skills add CarlosCaPe/octorato --skill foreign-key-constraints --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Foreign Key Constraints?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/carloscape-foreign-key-constraints)More formats (shields.io, HTML) on the badges page.
---
name: foreign-key-constraints
description: "Foreign Key Constraints"
metadata:
short-description: "Foreign Key Constraints"
original-index: 09
---
# Foreign Key Constraints
## What
`ALTER TABLE ADD CONSTRAINT ... FOREIGN KEY` enforces referential integrity
between tables. The FK guarantees that every value in the child column exists
in the parent table's referenced column.
## Why
Without FKs, orphaned records accumulate silently. A `HospitalId` in an
appointments table might reference a hospital that no longer exists. FKs
catch this at INSERT/UPDATE time, preventing data corruption.
## How
### Standard FK pattern (NOT VALID + VALIDATE)
```sql
-- Step 1: Add the constraint without scanning existing rows (instant)
ALTER TABLE public."AppointmentInfo"
ADD CONSTRAINT fk_appointmentinfo_hospitalid
FOREIGN KEY ("HospitalId")
REFERENCES public."Hospital"("HospitalId")
NOT VALID;
-- Step 2: Validate existing rows (non-blocking, allows concurrent reads)
ALTER TABLE public."AppointmentInfo"
VALIDATE CONSTRAINT fk_appointmentinfo_hospitalid;
```
Why NOT VALID + VALIDATE?
- `ADD FOREIGN KEY` acquires `SHARE ROW EXCLUSIVE` lock on both the
child table AND the referenced (parent) table -- lighter than the
`ACCESS EXCLUSIVE` used by most ALTER TABLE forms
(PG 16 docs: sql-altertable.html)
- `NOT VALID` skips scanning existing rows -- constraint creation is fast
- New inserts/updates ARE validated immediately against the FK
- `VALIDATE CONSTRAINT` scans existing rows with an even weaker lock
(`SHARE UPDATE EXCLUSIVE`) that allows concurrent reads AND writes;
it also acquires only `ROW SHARE` on the referenced table
(PG 16 docs: sql-altertable.html)
- This is the recommended pattern in our
[Best Practices](../DOCUMENTS/PostgreSQL_BestPractices_Azure.md)
### FK with explicit actions
```sql
ALTER TABLE public."ChildTable"
ADD CONSTRAINT fk_child_parent
FOREIGN KEY ("ParentId")
REFERENCES public."ParentTable"("Id")
ON DELETE CASCADE
ON UPDATE CASCADE
NOT VALID;
ALTER TABLE public."ChildTable"
VALIDATE CONSTRAINT fk_child_parent;
```
### Idempotent pattern
```sql
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name = 'fk_appointmentinfo_hospitalid'
AND table_schema = 'public'
) THEN
ALTER TABLE public."AppointmentInfo"
ADD CONSTRAINT fk_appointmentinfo_hospitalid
FOREIGN KEY ("HospitalId")
REFERENCES public."Hospital"("HospitalId");
RAISE NOTICE 'Created FK: fk_appointmentinfo_hospitalid';
ELSE
RAISE NOTICE 'Skipped: fk_appointmentinfo_hospitalid (already exists)';
END IF;
END $$;
```
## Pre-Requisites
Before adding a FK, verify:
1. **No orphans exist** -- see **Skill #24** (Orphan Detection & FK Rollout)
for the full scan + remediation workflow
2. **Parent column has a unique index** (usually PK or UNIQUE constraint)
3. **Data types match** between child and parent columns
## When to Use
- Any column that references another table's primary key
- After database audits identify missing referential integrity
- As part of schema hardening / best practices enforcement
## Where We Used It
- ****: FK on `AppointmentInfo.HospitalId` -> `Hospital.HospitalId`
- ****: FK on `AppointmentInfo.ProviderId` -> `Provider.ProviderId`
## Related Skills
- **Skill #24** (Orphan Detection & FK Rollout) -- orphan scan + data cleanup
before FK creation
- **Skill #10** (Index CONCURRENTLY) -- supporting indexes for FK columns
- **Skill #20** (Procedure Hardening) -- FK-aware delete ordering in procedures
## References
- [PostgreSQL Best Practices](../DOCUMENTS/PostgreSQL_BestPractices_Azure.md)
-- see "Foreign keys and constraints" in Design & Modeling
## Gotchas
- Always use `NOT VALID` + `VALIDATE CONSTRAINT` (see main example above)
- `ADD FOREIGN KEY` locks BOTH the child AND the parent table with
`SHARE ROW EXCLUSIVE` -- coordinate with teams owning the parent table
- FKs create implicit dependencies -- `DROP TABLE parent` will fail
- FKs can slow down INSERT/UPDATE/DELETE on the child table (each operation
checks the parent)
- Always create a supporting index on the FK column (Skill #10) --
PostgreSQL does NOT auto-create indexes on FK columns
- On partitioned tables, FKs cannot be declared `NOT VALID`
(PG 16 limitation -- see Skill #34)
---
*Category: DDL | Origin: , *
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!