Files
supabase/apps/docs/content/guides/database/postgres/cascade-deletes.mdx
3dffdefd6e fix(docs) Resolve 196 mdx lint warnings for just, quickly, actually, PostgreSQL (#47358)
Closes DOCS-1057
Contributes to DOCS-1052

## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## Problem

We have hundreds of MDX lint warnings in our docs going against style
best practices.

## Solution

Remove and replace in context the following:

- PostgreSQL. There was only one. There was concern about exceptions,
but I found none.
- Just
- Quickly
- Actually

### What changed

Edits follow the [Google developer documentation style
guide](https://developers.google.com/style): concise, direct, active
voice. The flagged words were removed when the sentence still read well,
or replaced when meaning needed to be preserved.

### Common patterns

| Flagged word | Approach | Example |
|---|---|---|
| **just** (filler) | Removed | "you just installed" → "you installed" |
| **just** (limiting) | **only** | "just one row" → "only one row" |
| **just like** | **like** / **the same as** | "function just like
regular users" → "function like regular users" |
| **not just** | **not only** | "not just errors" → "not only errors" |
| **quickly** (performance) | **efficiently** or removed | "find rows
quickly" → "find rows efficiently" |
| **quickly** (time) | **soon** / **rapidly** / removed | "expires too
quickly" → "expires too soon" |
| **actually** (filler) | Removed | "actually execute" → "execute"; "is
actually the most common" → "is the most common" |

## Tophatting

1. See the diff.
2. See that content continues to make sense in context.
3. Locally, `cd apps/docs` and run `pnpm run lint:mdx`.
4. Search for "just," "actually," "quickly", and "PostgreSQL" and see
there are 0 warnings.




<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
* Updated wording across quickstarts, guides, and troubleshooting
articles for grammar, clarity, and consistent step-by-step phrasing.
* Clarified key concepts including Row Level Security policy evaluation
across Supabase products, deferred foreign key constraint behavior, and
when `EXPLAIN ANALYZE` executes queries (and related side effects).
* Refined several troubleshooting instructions and added guidance to cap
log payload size to reduce billed Logs Ingest volume.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Nik Richers <nrichers@gmail.com>
Co-authored-by: Chris Chinchilla <chris.ward@supabase.io>
2026-06-29 09:40:25 -07:00

190 lines
7.0 KiB
Plaintext

---
title: 'Cascade Deletes'
description: 'Understand the types of foreign key constraint deletes'
footerHelpType: 'postgres'
---
There are 5 options for foreign key constraint deletes:
1. **CASCADE:** When a row is deleted from the parent table, all related rows in the child tables are deleted as well.
2. **RESTRICT:** When a row is deleted from the parent table, the delete operation is aborted if there are any related rows in the child tables.
3. **SET NULL:** When a row is deleted from the parent table, the values of the foreign key columns in the child tables are set to NULL.
4. **SET DEFAULT:** When a row is deleted from the parent table, the values of the foreign key columns in the child tables are set to their default values.
5. **NO ACTION:** This option is similar to RESTRICT, but it also has the option to be “deferred” to the end of a transaction. This means that other cascading deletes can run first, and then this delete constraint will only throw an error if there is referenced data remaining _at the end of the transaction_.
These options can be specified when defining a foreign key constraint using the "ON DELETE" clause. For example, the following SQL statement creates a foreign key constraint with the `CASCADE` option:
```sql
alter table child_table
add constraint fk_parent foreign key (parent_id) references parent_table (id)
on delete cascade;
```
This means that when a row is deleted from the `parent_table`, all related rows in the `child_table` will be deleted as well.
## `RESTRICT` vs `NO ACTION`
The difference between `NO ACTION` and `RESTRICT` is subtle and can be a bit confusing.
Both `NO ACTION` and `RESTRICT` are used to prevent deletion of a row in a parent table if there are related rows in a child table. However, there is a subtle difference in how they behave.
When a foreign key constraint is defined with the option `RESTRICT`, it means that if a row in the parent table is deleted, the database will immediately raise an error and prevent the deletion of the row in the parent table. The database will not delete, update or set to NULL any rows in the referenced tables.
When a foreign key constraint is defined with the option `NO ACTION`, it means that if a row in the parent table is deleted, the database will also raise an error and prevent the deletion of the row in the parent table. However unlike `RESTRICT`, `NO ACTION` has the option to defer the check using `INITIALLY DEFERRED`. This will only raise the above error _if_ the referenced rows still exist at the end of the transaction.
The difference from `RESTRICT` is that a constraint marked as `NO ACTION INITIALLY DEFERRED` is deferred until the end of the transaction, rather than running immediately. If, for example there is another foreign key constraint between the same tables marked as `CASCADE`, the cascade will occur first and delete the referenced rows, and no error will be thrown by the deferred constraint. Otherwise if there are still rows referencing the parent row by the end of the transaction, an error will be raised as before. Like `RESTRICT`, the database will not delete, update or set to NULL any rows in the referenced tables.
In practice, you can use either `NO ACTION` or `RESTRICT` depending on your needs. `NO ACTION` is the default behavior if you do not specify anything. If you prefer to defer the check until the end of the transaction, use `NO ACTION INITIALLY DEFERRED`.
## Example
To illustrate the difference, use the following example. We'll use the following data:
`grandparent`
| id | name |
| --- | --------- |
| 1 | Elizabeth |
`parent`
| id | name | `parent_id` |
| --- | ------- | ----------- |
| 1 | Charles | 1 |
| 2 | Diana | 1 |
`child`
| id | name | father | mother |
| --- | ------- | ------ | ------ |
| 1 | William | 1 | 2 |
To create these tables and their data, we run:
```sql
create table grandparent (
id serial primary key,
name text
);
create table parent (
id serial primary key,
name text,
parent_id integer references grandparent (id)
on delete cascade
);
create table child (
id serial primary key,
name text,
father integer references parent (id)
on delete restrict
);
insert into grandparent
(id, name)
values
(1, 'Elizabeth');
insert into parent
(id, name, parent_id)
values
(1, 'Charles', 1);
insert into parent
(id, name, parent_id)
values
(2, 'Diana', 1);
-- We'll just link the father for now
insert into child
(id, name, father)
values
(1, 'William', 1);
```
### `RESTRICT`
`RESTRICT` will prevent a delete and raise an error:
```shell
postgres=# delete from grandparent;
ERROR: update or delete on table "parent" violates foreign key constraint "child_father_fkey" on table "child"
DETAIL: Key (id)=(1) is still referenced from table "child".
```
Even though the foreign key constraint between parent and grandparent is `CASCADE`, the constraint between child and father is `RESTRICT`. Therefore an error is raised and no records are deleted.
### `NO ACTION`
Change the child-father relationship to `NO ACTION`:
```sql
alter table child
drop constraint child_father_fkey;
alter table child
add constraint child_father_fkey foreign key (father) references parent (id)
on delete no action;
```
We see that `NO ACTION` will also prevent a delete and raise an error:
```shell
postgres=# delete from grandparent;
ERROR: update or delete on table "parent" violates foreign key constraint "child_father_fkey" on table "child"
DETAIL: Key (id)=(1) is still referenced from table "child".
```
### `NO ACTION INITIALLY DEFERRED`
We'll change the foreign key constraint between child and father to be `NO ACTION INITIALLY DEFERRED`:
```sql
alter table child
drop constraint child_father_fkey;
alter table child
add constraint child_father_fkey foreign key (father) references parent (id)
on delete no action initially deferred;
```
Here you will see that `INITIALLY DEFFERED` seems to operate like `NO ACTION` or `RESTRICT`. When we run a delete, it seems to make no difference:
```shell
postgres=# delete from grandparent;
ERROR: update or delete on table "parent" violates foreign key constraint "child_father_fkey" on table "child"
DETAIL: Key (id)=(1) is still referenced from table "child".
```
But, when we combine it with _other_ constraints, then any other constraints take precedence. For example, run the same scenario but add a `mother` column that has a `CASCADE` delete:
```sql
alter table child
add column mother integer references parent (id)
on delete cascade;
update child
set mother = 2
where id = 1;
```
Then run a delete on the `grandparent` table:
```shell
postgres=# delete from grandparent;
DELETE 1
postgres=# select * from parent;
id | name | parent_id
----+------+-----------
(0 rows)
postgres=# select * from child;
id | name | father | mother
----+------+--------+--------
(0 rows)
```
The `mother` deletion took precedence over the `father`, and so William was deleted. After William was deleted, there was no reference to “Charles” and so he was free to be deleted, even though previously he wasn't (without `INITIALLY DEFERRED`).