skip to content

In DROP TABLE, what does CASCADE do that RESTRICT does not?

level: middleimportance: must knowfreq 58%

answer

  1. one keyword refuses, one keyword proceeds
  2. the question is what else depends on the table
  3. dependents are views and foreign keys
  4. it is not ON DELETE CASCADE
  5. rows in other tables are never deleted

basics

~20 s

RESTRICT refuses the drop while another object still depends on the table; CASCADE drops those dependent objects too — typically views over the table and foreign key constraints in other tables. Neither keyword deletes rows from any other table.

solid answer

~40 s

`DROP TABLE customers RESTRICT` fails if anything else in the schema still depends on `customers` — a view selecting from it, a foreign key in `orders` referencing it — and the error names the dependency. `DROP TABLE customers CASCADE` drops those dependent objects along with the table: the view is gone, and the foreign key constraint on `orders` is dropped, though `orders` itself and all of its rows survive. That last point is the one interviewers probe, because `CASCADE` here is unrelated to `ON DELETE CASCADE`: the referential action deletes child *rows* at DML time, while `DROP TABLE ... CASCADE` removes dependent *schema objects* at DDL time. Engines differ — PostgreSQL defaults to `RESTRICT`, MySQL accepts both keywords and ignores them — so never rely on the default to protect you.

code

sql · 7 lines
sql
DROP TABLE IF EXISTS staging_orders;

-- refused while the reporting view or the orders foreign key depends on it
DROP TABLE customers RESTRICT;

-- drops the view and the foreign key on orders; orders keeps every row
DROP TABLE customers CASCADE;

go deeper

for a junior

Know the shape of the statement and that IF EXISTS makes a missing table a no-op rather than an error. Be able to say that RESTRICT refuses when something else depends on the table and CASCADE removes those dependents.

for a middle

Explain which objects count as dependents — views and foreign keys in other tables — and draw the line clearly between DROP TABLE ... CASCADE (drops schema objects) and ON DELETE CASCADE (deletes child rows at DML time).

for a senior

Show the operational instinct: use the RESTRICT error as a dependency inventory, drop dependents explicitly, and prefer rename-then-observe-then-drop for a table you merely believe is unused. Note that engines disagree on the keywords, so defaults are not a safety net.

for a principal

Own the policy on destructive DDL: whether CASCADE is permitted in migrations at all, what evidence must exist before a table is dropped, and how a drop is made recoverable given that DDL is not transactional in every engine.

## What DROP TABLE removes `DROP TABLE <name>` removes the table's definition and every row in it. It also removes the objects that belong to the table and have no independent existence: its indexes, its triggers, and the constraints declared on it, including foreign keys it declares *outward* to other tables. There is no undo at the statement level, and in engines where DDL implicitly commits, no surrounding transaction to roll back either. What `DROP TABLE` cannot silently remove are objects owned elsewhere that *point at* this table. Those are the dependencies the drop-behaviour keyword governs. ## RESTRICT and CASCADE SQL-92 made the drop behaviour mandatory: `DROP TABLE <name> CASCADE | RESTRICT`. In practice engines relaxed that and supply a default. - `RESTRICT` — refuse the drop if any other object depends on the table. Typical dependents are views and materialized views defined over it, foreign key constraints in other tables that reference its key, and (engine-dependent) routines or generated columns that reference it. The error message names the blocking object, which makes `RESTRICT` a useful *discovery* tool: run the drop, read the list, decide deliberately. - `CASCADE` — drop those dependent objects as part of the same statement. The view disappears. The foreign key constraint in `orders` disappears. Anything that depended on the dropped view disappears in turn, recursively. ```sql -- customers is referenced by orders.customer_id and by a reporting view DROP TABLE customers RESTRICT; -- ERROR: cannot drop table customers because other objects depend on it DROP TABLE customers CASCADE; -- customers is gone; v_customer_revenue is gone; -- the foreign key on orders is gone; every row of orders is still there ``` ## The misconception worth naming `CASCADE` in `DROP TABLE` is **not** `ON DELETE CASCADE`. They share a keyword and nothing else: | | when it acts | what it removes | |---|---|---| | `ON DELETE CASCADE` on a foreign key | DML — when a parent row is deleted | child **rows** referencing that parent | | `DROP TABLE ... CASCADE` | DDL — when the table is dropped | dependent **schema objects** (views, the FK constraint itself) | So dropping a parent table with `CASCADE` leaves the child table full of rows whose references now point at nothing — and, because the constraint that enforced the reference has been dropped too, nothing complains. That orphaned state is exactly why `CASCADE` deserves suspicion in a migration script: it converts a loud failure into a silent structural change. ## IF EXISTS `DROP TABLE IF EXISTS staging_orders` turns "table does not exist" from an error into a no-op (usually with a notice). It makes teardown and re-run scripts idempotent, which is why it is near-universal in test fixtures and in the down side of migrations. It says nothing about dependencies — `IF EXISTS` and the drop behaviour are orthogonal, and a statement may carry both: `DROP TABLE IF EXISTS customers CASCADE`. It is also a small hazard in a migration: `IF EXISTS` will happily do nothing when you have mistyped the table name, so a migration that "succeeded" may have changed nothing at all. ## Multiple tables in one statement Most engines accept a list — `DROP TABLE a, b, c` — which is more than a convenience: dropping mutually referencing tables in one statement sidesteps the ordering problem where each is blocked by the other's foreign key. Without that, drop in reverse dependency order (children before parents). ## Portability The drop-behaviour keywords are the least portable part of this statement. PostgreSQL implements both and defaults to `RESTRICT`. MySQL parses `RESTRICT` and `CASCADE` for compatibility but does nothing with them, and it refuses to drop a table that is the parent of a foreign key unless the referential checks are disabled — so writing `CASCADE` there gives you a false sense of having asked for something. SQLite has no drop-behaviour keywords at all. Because of that spread, the defensive habit is: never write `CASCADE` into a migration to make an error disappear. Read the dependency list, drop each dependent object explicitly in its own statement, and let `RESTRICT` (or the engine's refusal) stay as the tripwire it is meant to be. ## Practical guidance For a table you believe is dead, the low-risk sequence is to revoke access or rename it first, wait an observation period, and only then drop it — a rename is instantly reversible, a drop is not. When you do drop, name the dependents explicitly and let the statement fail if reality differs from your expectation.

  • What does IF EXISTS change, and what does it hide?
    It converts "table does not exist" from an error into a no-op, which makes teardown and re-runnable scripts idempotent. What it hides is a typo: `DROP TABLE IF EXISTS custmoers` succeeds while changing nothing, so a migration can report success having done nothing at all. It is orthogonal to the drop behaviour; both can appear in one statement.
  • After DROP TABLE customers CASCADE, what state is the orders table left in?
    Fully populated and structurally unguarded. Its rows are untouched — CASCADE deletes no data outside the dropped table — but the foreign key constraint that referenced customers has been dropped along with it, so orders.customer_id now holds values pointing at a table that no longer exists and nothing detects it. That silent orphaning is the main argument against reflexive CASCADE.
  • You must drop several tables that reference each other. How do you sequence it?
    Either drop them in reverse dependency order — children before parents — or name them together in one statement, `DROP TABLE order_lines, orders, customers`, which most engines accept and which sidesteps the ordering problem for mutual references entirely. Reaching for CASCADE instead works but drops whatever else happened to depend on them, unexamined.

saying these in an interview costs you the question

  • Says DROP TABLE CASCADE deletes rows in child tables
  • Treats CASCADE as the fix for any dependency error
  • Assumes every engine defaults to RESTRICT
  • Thinks IF EXISTS protects against dropping the wrong table
  • Believes DROP TABLE can be rolled back everywhere

context