How do you drop a UNIQUE constraint that was declared inline in CREATE TABLE with no name?
answer
- the drop statement needs an identifier
- you cannot drop by column list
- the engine invented something when you did not
- the catalog knows what it is called
- information_schema lists table constraints
basics
~20 sALTER TABLE ... DROP CONSTRAINT takes a name, so look up the system-generated one in information_schema.table_constraints for that table and drop it by that name. Generated names differ between engines and environments, which is why constraints should be named explicitly when declared.
solid answer
~50 s`ALTER TABLE orders DROP CONSTRAINT <name>` identifies the constraint only by name — there is no "drop the unique constraint on these columns" form. An inline `UNIQUE` in `CREATE TABLE` still produced a constraint, but the engine invented the name, so query `information_schema.table_constraints` (or the engine's catalog) for the table and drop what you find. The catch is that the generated name is not guaranteed identical across environments, so a migration hard-coding one may work on staging and fail in production; that is the practical argument for always writing `CONSTRAINT uq_orders_external_ref UNIQUE (...)`. Two portability notes: MySQL historically required the object-specific forms `DROP FOREIGN KEY`/`DROP INDEX`, and SQLite has no `DROP CONSTRAINT` at all — you rebuild the table. `NOT NULL` is usually a column attribute, dropped with `ALTER COLUMN ... DROP NOT NULL`, not with `DROP CONSTRAINT`.
code
sql · 5 lines-- name it, and the reverse statement writes itself
ALTER TABLE orders
ADD CONSTRAINT uq_orders_external_ref UNIQUE (external_ref);
ALTER TABLE orders DROP CONSTRAINT uq_orders_external_ref;go deeper
Know that DROP CONSTRAINT works by name only, and that a constraint declared inline without a CONSTRAINT clause still got one — generated by the engine and visible in information_schema.table_constraints.
Explain how to find the name, why baking a generated name into a migration is fragile across environments, and that NOT NULL is a column attribute removed via ALTER COLUMN rather than DROP CONSTRAINT.
Show awareness of the portability spread — MySQL's object-specific DROP FOREIGN KEY/DROP INDEX forms, SQLite's table-rebuild requirement — and note that ADD CONSTRAINT validates existing rows and can be rejected by data.
Own the naming convention as a policy: constraints named at declaration with a predictable prefix scheme so that every migration is writable and reversible from the schema source rather than from a live catalog.
## DROP CONSTRAINT is name-addressed ```sql ALTER TABLE <table> DROP CONSTRAINT <constraint name> [ CASCADE | RESTRICT ]; ``` The statement takes a name and nothing else. There is no portable syntax for "drop whatever unique constraint covers `(tenant_id, external_ref)`" or "drop the foreign key pointing at `customers`". If you do not know the name, you cannot write the statement. ## Where the name comes from When a constraint is declared with an explicit `CONSTRAINT` clause, the name is yours and is stable everywhere the schema is created: ```sql ALTER TABLE orders ADD CONSTRAINT uq_orders_external_ref UNIQUE (external_ref); ALTER TABLE orders DROP CONSTRAINT uq_orders_external_ref; ``` When it is declared inline without one — `external_ref VARCHAR(64) UNIQUE` in `CREATE TABLE` — the constraint still exists, but the engine generates the name. Generated names follow engine-specific conventions and can vary with creation order, collisions, and the engine version. That is what makes an unnamed constraint awkward: the drop statement is not knowable from the schema source, only from the live catalog of the particular database. ## Finding it ```sql SELECT constraint_name, constraint_type FROM information_schema.table_constraints WHERE table_schema = 'public' AND table_name = 'orders'; ``` `information_schema.constraint_column_usage` (and `key_column_usage`) tells you which columns each constraint covers, which is how you identify the one you actually mean when several exist. Engines expose native catalogs with more detail, and most clients have a shortcut to show a table's definition. The operational hazard: doing this by hand is fine, but *baking the discovered name into a migration* assumes every environment generated the same name. When that assumption fails, the migration errors in production. The robust options are to make the migration look the name up dynamically, or — far better — to have named the constraint in the first place so this problem never exists. ## Adding constraints later The mirror statement is `ADD CONSTRAINT`, and it accepts the same constraint kinds that a table definition does: ```sql ALTER TABLE orders ADD CONSTRAINT pk_orders PRIMARY KEY (order_id); ALTER TABLE orders ADD CONSTRAINT uq_orders_external_ref UNIQUE (external_ref); ALTER TABLE orders ADD CONSTRAINT ck_orders_qty CHECK (qty > 0); ALTER TABLE orders ADD CONSTRAINT fk_orders_customer FOREIGN KEY (customer_id) REFERENCES customers (customer_id); ``` Every one of these is checked against the rows already in the table, and the statement is rejected if any row violates it — which is the same data-versus-schema tension seen in `SET NOT NULL`. Naming each one with a convention (`pk_`, `uq_`, `ck_`, `fk_` plus table and columns) makes the reverse statement writable from the schema source alone. ## NOT NULL is usually not a named constraint A frequent dead end: you try `ALTER TABLE t DROP CONSTRAINT c_not_null` and no such object exists. In most engines `NOT NULL` is a column attribute rather than a catalogued table constraint, so it is removed through the column: ```sql ALTER TABLE orders ALTER COLUMN shipped_at DROP NOT NULL; -- PostgreSQL ``` The exception is a `CHECK (col IS NOT NULL)` you wrote yourself, which *is* a named constraint and does drop by name. ## Engine differences worth knowing - **PostgreSQL** implements `ADD CONSTRAINT` / `DROP CONSTRAINT` for all constraint kinds, with `CASCADE`/`RESTRICT` on the drop (`CASCADE` matters when another constraint depends on the one being dropped — a foreign key referencing a unique key, for instance). - **MySQL** historically required object-specific forms: `DROP FOREIGN KEY <name>`, `DROP INDEX <name>` for a unique key, `DROP CHECK <name>`. Newer versions accept a generic `DROP CONSTRAINT`, but code that must run on older servers uses the specific forms. - **SQLite** does not support dropping a constraint at all; its `ALTER TABLE` covers only rename, add column, drop column. Removing a constraint means creating a new table with the desired definition, copying the rows, dropping the original and renaming — the classic table-rebuild. ## The takeaway Name every constraint you declare. It costs one clause at creation time and it is the difference between a reversible migration and an archaeology exercise against a production catalog.
- Why is hard-coding a discovered system-generated name into a migration risky?Because the name is not guaranteed to be identical in every database. Generated names depend on engine conventions, creation order and collision handling, so the value you read from staging may not exist in production and the migration errors there. Either resolve the name dynamically at migration time, or avoid the situation by naming constraints when they are declared.
- You try DROP CONSTRAINT for a NOT NULL and the engine says no such constraint. Why?In most engines NOT NULL is a column attribute, not a catalogued table constraint, so it has no droppable name. Remove it through the column instead — `ALTER TABLE t ALTER COLUMN c DROP NOT NULL` in PostgreSQL, or by restating the column definition without NOT NULL in MySQL and SQL Server. A CHECK (c IS NOT NULL) you wrote yourself is different and does drop by name.
- How do you remove a constraint in an engine with no DROP CONSTRAINT support?Rebuild the table: create a new one with the desired definition, `INSERT INTO new SELECT ... FROM old`, drop the original, and rename the new one into its place. SQLite requires this, since its ALTER TABLE covers only rename, add column and drop column. Recreate indexes, triggers and dependent views as part of the same sequence.
- What does ADD CONSTRAINT do about rows that already violate the new rule?It rejects the statement. The constraint is validated against the current contents of the table, so a UNIQUE with existing duplicates, a CHECK with failing rows, or a FOREIGN KEY with orphans all fail to be added. The fix is to clean or quarantine the offending rows first, then add the constraint.
saying these in an interview costs you the question
- Thinks you can drop a constraint by naming its columns
- Assumes generated constraint names are identical everywhere
- Tries DROP CONSTRAINT to remove a NOT NULL attribute
- Believes every engine supports DROP CONSTRAINT
- Says ADD CONSTRAINT ignores rows already in the table