Oracle Database
Constraints & Keys
Tables created in Lesson 10 have correct column types and nullability but nothing yet prevents duplicate customer IDs, an order referencing a customer that does not exist, or a payment amount of minus five thousand. Constraints are the mechanism Oracle uses to enforce these rules at the database level — not in application code, not in a stored procedure, but in the engine itself, so no path into the database can bypass them. This lesson adds constraints to every table in both schemas and covers how to query, disable, enable, and drop them.
To follow along with the examples in this lesson, use the course dataset from Lesson 8.
Primary Keys
A primary key uniquely identifies each row in a table. Oracle enforces this with two rules automatically: the column or columns making up the key must be NOT NULL, and the values must be unique across every row. Oracle creates a unique index on the primary key columns automatically when the constraint is defined.
Primary keys can be defined inline on the column, or as a separate table-level constraint. The table-level form is preferred because it allows naming the constraint explicitly — a named constraint produces a readable error message and is far easier to manage later.
-- Inline primary key — Oracle names it automatically (system-generated name like SYS_C007293)
CREATE TABLE products (
product_id NUMBER(10) PRIMARY KEY,
product_name VARCHAR2(150) NOT NULL
);
-- Table-level named primary key — preferred in production
-- Naming convention: PK_tablename
CREATE TABLE products (
product_id NUMBER(10) NOT NULL,
product_name VARCHAR2(150) NOT NULL,
CONSTRAINT pk_products PRIMARY KEY (product_id)
);
-- Adding a primary key to an existing table with ALTER TABLE
ALTER TABLE customers
ADD CONSTRAINT pk_customers PRIMARY KEY (customer_id);
-- Confirm the constraint was created
SELECT constraint_name,
constraint_type,
status
FROM user_constraints
WHERE table_name = 'CUSTOMERS';CONSTRAINT_NAME CONSTRAINT_TYPE STATUS
──────────────── ─────────────── ──────
PK_CUSTOMERS P ENABLED
constraint_type = 'P'indicates a primary key — Oracle uses single-letter codes: P = primary key, R = foreign key (referential), U = unique, C = check (including NOT NULL)- Oracle creates a unique index named after the constraint automatically — this index is what enforces uniqueness and also speeds up joins on the primary key column
- Never rely on system-generated constraint names — when a constraint is violated Oracle reports the constraint name in the error, and
SYS_C007293tells you nothing about which rule was broken
Foreign Keys
A foreign key enforces a relationship between two tables — it guarantees that a value in a child column always matches an existing value in the parent table's primary key. An order cannot reference customer ID 999 if no customer with that ID exists. Oracle raises ORA-02291 on any insert or update that would create an orphaned reference.
By default Oracle does not cascade deletes — attempting to delete a parent row that has child rows raises ORA-02292. The ON DELETE CASCADE option changes this so that deleting a parent row automatically deletes all matching child rows. Use it deliberately — it can silently remove large amounts of data.
-- Foreign key on orders.customer_id referencing customers.customer_id
-- Naming convention: FK_childtable_parenttable
ALTER TABLE orders
ADD CONSTRAINT fk_orders_customers
FOREIGN KEY (customer_id)
REFERENCES customers (customer_id);
-- Foreign key with ON DELETE CASCADE
-- Deleting an order automatically deletes all its order_items
ALTER TABLE order_items
ADD CONSTRAINT fk_orderitems_orders
FOREIGN KEY (order_id)
REFERENCES orders (order_id)
ON DELETE CASCADE;
-- Foreign key referencing the products table from order_items
ALTER TABLE order_items
ADD CONSTRAINT fk_orderitems_products
FOREIGN KEY (product_id)
REFERENCES products (product_id);
-- Foreign key from payments to orders
ALTER TABLE payments
ADD CONSTRAINT fk_payments_orders
FOREIGN KEY (order_id)
REFERENCES orders (order_id);
-- View all foreign keys in the schema with their parent tables
SELECT constraint_name,
table_name,
r_constraint_name -- name of the referenced primary key constraint
FROM user_constraints
WHERE constraint_type = 'R'
ORDER BY table_name;──────────────────────── ─────────── ─────────────────
FK_ORDERITEMS_ORDERS ORDER_ITEMS PK_ORDERS
FK_ORDERITEMS_PRODUCTS ORDER_ITEMS PK_PRODUCTS
FK_ORDERS_CUSTOMERS ORDERS PK_CUSTOMERS
FK_PAYMENTS_ORDERS PAYMENTS PK_ORDERS
- Foreign keys require the parent table's primary key constraint to already exist — add all primary keys before adding foreign keys
ON DELETE CASCADEis appropriate for order_items because an order line has no meaning without its parent order; it is not appropriate where child records have independent value — use it carefullyr_constraint_nameinuser_constraintspoints to the constraint name on the parent table, not the parent table name directly — join touser_constraintsagain on that name to find the parent table
Unique Constraints
A unique constraint ensures no two rows have the same value in the constrained column or combination of columns. Unlike a primary key, a unique constraint allows NULL — and in Oracle, multiple rows can each have NULL in a unique column because NULL is not considered equal to anything, including another NULL.
Oracle creates a unique index to enforce the constraint, just as it does for primary keys. This index also benefits queries that filter or join on the constrained column.
-- Unique constraint on customers.email — no two customers can share an email address
-- Naming convention: UQ_tablename_columnname
ALTER TABLE customers
ADD CONSTRAINT uq_customers_email UNIQUE (email);
-- Unique constraint on employees.email in the HR schema
ALTER TABLE employees
ADD CONSTRAINT uq_employees_email UNIQUE (email);
-- Composite unique constraint — the combination of order_id and product_id must be unique
-- This prevents the same product appearing on the same order twice as separate line items
ALTER TABLE order_items
ADD CONSTRAINT uq_orderitems_order_product UNIQUE (order_id, product_id);
-- Test that the unique constraint works
-- First insert succeeds
INSERT INTO customers (customer_id, full_name, email, country, loyalty_tier)
VALUES (1, 'Sarah Connor', 'sarah@example.com', 'USA', 'standard');
-- Second insert with the same email raises ORA-00001: unique constraint violated
INSERT INTO customers (customer_id, full_name, email, country, loyalty_tier)
VALUES (2, 'John Connor', 'sarah@example.com', 'USA', 'standard');-- Second INSERT: ORA-00001: unique constraint (SCHEMA.UQ_CUSTOMERS_EMAIL) violated
- The error message includes the constraint name — this is why naming constraints matters;
UQ_CUSTOMERS_EMAILimmediately tells you which rule was broken and on which column - A unique constraint on a single nullable column allows many
NULLvalues — Oracle treats eachNULLas distinct; this is correct behaviour and differs from some other databases - If a unique index already exists on the column, Oracle can use it to back the unique constraint rather than creating a second index
Check Constraints
A check constraint validates that a column value satisfies a specific condition every time a row is inserted or updated. The condition is any valid SQL expression that returns true or false. If it returns false the statement is rejected with ORA-02290. If it returns NULL — which happens when the column is NULL and the expression involves the column — Oracle allows the row, treating an unknown result as passing.
-- Check that unit_price is always greater than zero
ALTER TABLE products
ADD CONSTRAINT chk_products_price
CHECK (unit_price > 0);
-- Check that stock_qty is never negative
ALTER TABLE products
ADD CONSTRAINT chk_products_stock
CHECK (stock_qty >= 0);
-- Check that active_flag is only ever 'Y' or 'N'
ALTER TABLE products
ADD CONSTRAINT chk_products_flag
CHECK (active_flag IN ('Y', 'N'));
-- Check that order status is one of the valid workflow values
ALTER TABLE orders
ADD CONSTRAINT chk_orders_status
CHECK (status IN ('pending', 'processing', 'shipped', 'delivered', 'cancelled'));
-- Check that payment amount is positive
ALTER TABLE payments
ADD CONSTRAINT chk_payments_amount
CHECK (amount > 0);
-- HR schema — salary band enforcement
-- min_salary must always be less than max_salary within a job definition
ALTER TABLE jobs
ADD CONSTRAINT chk_jobs_salary_band
CHECK (min_salary < max_salary);
-- View all check constraints for the products table
SELECT constraint_name,
search_condition
FROM user_constraints
WHERE table_name = 'PRODUCTS'
AND constraint_type = 'C'
AND constraint_name NOT LIKE 'SYS_%';───────────────────── ────────────────────────────
CHK_PRODUCTS_PRICE unit_price > 0
CHK_PRODUCTS_STOCK stock_qty >= 0
CHK_PRODUCTS_FLAG active_flag IN ('Y', 'N')
- The
NOT LIKE 'SYS_%'filter excludes system-generatedNOT NULLconstraints — Oracle storesNOT NULLas a check constraint with typeCand a system-generated name, so filtering them out keeps the results clean - Check constraints cannot reference other tables or use subqueries — for cross-table validation use a foreign key or a trigger
- A check constraint that references a nullable column passes when the column is
NULL— ifunit_pricewere nullable, aNULLprice would bypasschk_products_price; combiningNOT NULLwith a check constraint closes this gap
Applying All Constraints to Both Schemas
With all four constraint types covered, here is the complete set applied to both schemas in the correct order — primary keys first, then unique constraints, then check constraints, then foreign keys last.
-- DataplexaStore — primary keys
ALTER TABLE customers ADD CONSTRAINT pk_customers PRIMARY KEY (customer_id);
ALTER TABLE products ADD CONSTRAINT pk_products PRIMARY KEY (product_id);
ALTER TABLE orders ADD CONSTRAINT pk_orders PRIMARY KEY (order_id);
ALTER TABLE order_items ADD CONSTRAINT pk_order_items PRIMARY KEY (order_item_id);
ALTER TABLE payments ADD CONSTRAINT pk_payments PRIMARY KEY (payment_id);
-- DataplexaHR — primary keys
ALTER TABLE departments ADD CONSTRAINT pk_departments PRIMARY KEY (department_id);
ALTER TABLE jobs ADD CONSTRAINT pk_jobs PRIMARY KEY (job_id);
ALTER TABLE employees ADD CONSTRAINT pk_employees PRIMARY KEY (employee_id);
ALTER TABLE salaries ADD CONSTRAINT pk_salaries PRIMARY KEY (salary_id);
ALTER TABLE payroll_history ADD CONSTRAINT pk_payroll_history PRIMARY KEY (payroll_id);
-- Unique constraints
ALTER TABLE customers ADD CONSTRAINT uq_customers_email UNIQUE (email);
ALTER TABLE employees ADD CONSTRAINT uq_employees_email UNIQUE (email);
-- Check constraints — DataplexaStore
ALTER TABLE products ADD CONSTRAINT chk_products_price CHECK (unit_price > 0);
ALTER TABLE products ADD CONSTRAINT chk_products_stock CHECK (stock_qty >= 0);
ALTER TABLE products ADD CONSTRAINT chk_products_flag CHECK (active_flag IN ('Y','N'));
ALTER TABLE orders ADD CONSTRAINT chk_orders_status CHECK (status IN ('pending','processing','shipped','delivered','cancelled'));
ALTER TABLE payments ADD CONSTRAINT chk_payments_amount CHECK (amount > 0);
-- Check constraints — DataplexaHR
ALTER TABLE jobs ADD CONSTRAINT chk_jobs_salary_band CHECK (min_salary < max_salary);
ALTER TABLE payroll_history ADD CONSTRAINT chk_payroll_net CHECK (net_pay >= 0);
-- Foreign keys — DataplexaStore (all parent PKs exist before this point)
ALTER TABLE orders ADD CONSTRAINT fk_orders_customers FOREIGN KEY (customer_id) REFERENCES customers (customer_id);
ALTER TABLE order_items ADD CONSTRAINT fk_orderitems_orders FOREIGN KEY (order_id) REFERENCES orders (order_id) ON DELETE CASCADE;
ALTER TABLE order_items ADD CONSTRAINT fk_orderitems_products FOREIGN KEY (product_id) REFERENCES products (product_id);
ALTER TABLE payments ADD CONSTRAINT fk_payments_orders FOREIGN KEY (order_id) REFERENCES orders (order_id);
-- Foreign keys — DataplexaHR
ALTER TABLE departments ADD CONSTRAINT fk_departments_manager FOREIGN KEY (manager_id) REFERENCES employees (employee_id);
ALTER TABLE employees ADD CONSTRAINT fk_employees_jobs FOREIGN KEY (job_id) REFERENCES jobs (job_id);
ALTER TABLE employees ADD CONSTRAINT fk_employees_departments FOREIGN KEY (department_id) REFERENCES departments (department_id);
ALTER TABLE employees ADD CONSTRAINT fk_employees_manager FOREIGN KEY (manager_id) REFERENCES employees (employee_id);
ALTER TABLE salaries ADD CONSTRAINT fk_salaries_employees FOREIGN KEY (employee_id) REFERENCES employees (employee_id);
ALTER TABLE payroll_history ADD CONSTRAINT fk_payroll_employees FOREIGN KEY (employee_id) REFERENCES employees (employee_id);-- DataplexaHR primary keys: 5 constraints created.
-- Unique constraints: 2 constraints created.
-- Check constraints: 7 constraints created.
-- Foreign keys: 10 constraints created.
- The circular dependency between
departmentsandemployees— departments reference a manager who is an employee, employees reference a department — is resolved by adding foreign keys after both tables and their primary keys exist; the tables themselves can be created in any order as long as the foreign keys come last - The self-referencing
fk_employees_managerforeign key is valid in Oracle — a table can reference itself as long as the primary key on the same table exists first - Adding foreign keys to tables that already contain data will fail if any existing values violate the constraint — always validate data integrity before adding constraints to populated tables
Managing Constraints
Constraints can be disabled temporarily without being dropped — useful during bulk data loads where enforcing every constraint on every row would be slow. Once the load is complete the constraint is re-enabled and Oracle validates all rows in one pass.
-- Disable a constraint — Oracle stops enforcing it but keeps the definition
ALTER TABLE orders
DISABLE CONSTRAINT fk_orders_customers;
-- Enable a constraint — Oracle re-validates all existing rows before enabling
-- Fails with ORA-02293 if any existing row violates the constraint
ALTER TABLE orders
ENABLE CONSTRAINT fk_orders_customers;
-- Drop a constraint permanently
ALTER TABLE orders
DROP CONSTRAINT fk_orders_customers;
-- View all constraints on a table with their type and status
SELECT constraint_name,
constraint_type,
status,
validated
FROM user_constraints
WHERE table_name = 'ORDERS'
ORDER BY constraint_type;──────────────────── ─────────────── ─────── ─────────
CHK_ORDERS_STATUS C ENABLED VALIDATED
FK_ORDERS_CUSTOMERS R ENABLED VALIDATED
PK_ORDERS P ENABLED VALIDATED
validated = 'VALIDATED'means Oracle has checked all existing rows against the constraint — if you re-enable a disabled constraint it will showNOT VALIDATEDuntil Oracle completes the validation pass- Disabling a foreign key does not disable the primary key it references — both sides must be managed independently
- Dropping a primary key that is referenced by a foreign key raises
ORA-02273— useDROP CONSTRAINT pk_name CASCADEto drop the primary key and all dependent foreign keys in one statement
Summary
| Constraint | Type Code | What It Enforces | Error on Violation |
|---|---|---|---|
| PRIMARY KEY | P | Unique, NOT NULL — one per table | ORA-00001 |
| FOREIGN KEY | R | Child value must exist in parent primary key | ORA-02291 / ORA-02292 |
| UNIQUE | U | No duplicate values — NULLs are allowed and distinct | ORA-00001 |
| CHECK | C | Column value must satisfy a SQL expression | ORA-02290 |
| NOT NULL | C | Column must always have a value | ORA-01400 |
| DISABLE CONSTRAINT | — | Suspends enforcement — definition kept | — |
| ENABLE CONSTRAINT | — | Re-enables and re-validates all existing rows | ORA-02293 if rows violate it |
Practice Questions
Practice 1. Why should constraints always be given explicit names rather than relying on Oracle to generate them?
Practice 2. What is the difference between ORA-02291 and ORA-02292 in the context of foreign keys?
Practice 3. A check constraint is added to ensure unit_price > 0. A row is inserted with unit_price = NULL. Does the constraint reject the row?
Practice 4. Why is ON DELETE CASCADE used on fk_orderitems_orders but not on fk_orders_customers?
Practice 5. What does the validated column in user_constraints tell you, and when would it show NOT VALIDATED?
Quiz
Quiz 1. Which constraint type code does Oracle use for foreign keys in user_constraints?
Quiz 2. A unique constraint is added to employees.email. Two rows are inserted with email = NULL. What happens?
Quiz 3. You attempt to drop the primary key on the customers table but it is referenced by a foreign key on orders. What error does Oracle raise?
Quiz 4. Which of the following is a valid use of a check constraint?
Quiz 5. What is the correct order for applying constraints to a new schema?
Next up — Sequences & Identity Columns — Learn how Oracle generates unique primary key values automatically using sequences and the NEXTVAL pattern used across both schemas.