Schema design Recommended
Plan how rows are removed
Decide for each table what deleting means, and make the foreign keys say it.
How to do it
Use ON DELETE CASCADE for rows that only exist as part of a parent, such as order lines. Use RESTRICT where a delete must be stopped. With a deleted_at column for soft deletes, every query must filter on it. Personal data must still be erased when the law asks.
Example
CREATE TABLE order_lines (
order_id bigint NOT NULL REFERENCES orders (id) ON DELETE CASCADE,
product_id bigint NOT NULL REFERENCES products (id) ON DELETE RESTRICT,
quantity int NOT NULL CHECK (quantity > 0)
);
How to check
Delete a test parent row. Its owned rows disappear, protected references block the delete, and no orphan rows remain.
For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers data playbook: https://agentwranglers.se/playbook/data/schema-design/deletion-design.