The labs on this site need JavaScript to run. The same content is written out below.
Learn › Database › Relational model and design
Tables, keys, and constraints
What data gets in when there are no constraints?
Insert wrong values into the member table and the orders table, or delete a member who still has orders. A table that has constraints rejects those statements with an error, and a table without constraints accepts them as they are.
Checked against real execution · The SQL of all 48 combinations was run as it is on MySQL 8.4.11, and the error numbers, the number of rows changed, and all the rows of both tables afterwards were checked
Concepts in this lab
- Constraint
- This is a rule about the values that can go into a table, written in the definition of the table. A primary key, a unique constraint, NOT NULL, CHECK, and a foreign key are typical examples. When an INSERT, UPDATE, or DELETE breaks a rule, the database rejects that statement with an error, and the table does not change. If you check only in the application code, wrong values can get in through code that skips the check or through SQL that someone runs directly, but a constraint blocks a value in the same way whatever route it comes through. Without constraints, wrong values are saved without an error, and the problem only becomes visible when you query the data later.
- Primary key, unique key, foreign key
- A primary key (PK) is the column that uniquely identifies each row of a table. Its values cannot be duplicated or empty, and a table has only one. A unique key is a unique constraint, which prevents duplicate values, and a table can have several. A foreign key (FK) is a column that references the primary key (or a unique key) of another table. It guarantees that the referenced row really exists.
- NOT NULL and CHECK
- These are constraints that limit the values that can go into a single column. NOT NULL does not allow the column to be left empty (NULL), and CHECK does not accept a value that makes the condition written in the parentheses false. For example, CHECK (age >= 0) blocks a negative age. If the result of the condition is NULL (unknown), CHECK lets the value pass, so if a value must exist, add NOT NULL as well. MySQL actually enforces CHECK from 8.0.16, and versions before that ignored a CHECK even if you wrote one.
- Foreign key referential actions (ON DELETE)
- These are the actions that you set on a foreign key to decide what happens to the child rows that point to a parent row when that parent row is deleted. RESTRICT rejects the deletion of the parent if child rows remain. CASCADE deletes the child rows along with the parent, and SET NULL keeps the child rows and changes only the foreign key column to NULL. If you write nothing, the default is NO ACTION, which behaves the same as RESTRICT in MySQL's InnoDB. One statement with CASCADE can also delete rows of another table, so use it only for data that may disappear together, and SET NULL can be used only when the foreign key column can accept NULL. What happens when the key value of the parent is changed is set in the same way with ON UPDATE.
Try these
- Default settings — A member is deleted with no constraints. 2 orders with no owner remain.
- Foreign key RESTRICT — A member who still has orders cannot be deleted (error 1451).
- Foreign key CASCADE — When you delete the member, the orders of that member are deleted too.
- Foreign key SET NULL — The orders remain, and only the column that pointed to the member becomes NULL.
- The same id twice — Without a primary key, there are two members with id 1.
- An order for a missing member — A foreign key blocks an order that points to a parent that does not exist (error 1452).
- Unique and NULL — In MySQL, even with a unique constraint, NULL can go into several rows.
Running with the default settings
- CREATE TABLE, INSERT: Two tables are created, and 3 members and 3 orders are inserted. Only the names and types of the columns are defined, and no constraint is applied at all. That member_id in orders is a member number is known only to people, and the DB does not know it.
- DELETE FROM member WHERE id = 1: Tries to delete member 1 (Kim). In orders, there are 2 orders (101 and 102) that point to member 1.
- There is no constraint to check: The DB only looks at whether the values fit the types, and runs the statement as it is. 1 row was deleted from member. orders was not touched.
- SELECT COUNT(*) FROM orders o LEFT JOIN member m ON m.id = o.member_id WHERE o.member_id IS NOT NULL AND m.id IS NULL (check query). Orders without a member: 2. Orders 101 and 102 still have member_id 1, but member 1 no longer exists. 2 orders without an owner remain.
Result of this run
- Without constraints, the DB accepts even wrong values without an error. The statement "succeeded" but the data is wrong, and that only becomes visible when you query it later. Constraints stop such values at the moment they try to enter the table.
Code
-- Tables without constraints
CREATE TABLE member (
id INT,
email VARCHAR(50),
name VARCHAR(20),
age INT
);
CREATE TABLE orders (
id INT,
member_id INT,
item VARCHAR(30)
);
-- Initial data
INSERT INTO member (id, email, name, age) VALUES
(1, 'kim@lab.example', 'Kim', 30),
(2, 'lee@lab.example', 'Lee', 25),
(3, NULL, 'Park', 41);
INSERT INTO orders (id, member_id, item) VALUES
(101, 1, 'desk'),
(102, 1, 'mouse'),
(103, 2, 'monitor');
-- Statement to run
DELETE FROM member WHERE id = 1;
-- Check query
SELECT COUNT(*)
FROM orders o
LEFT JOIN member m ON m.id = o.member_id
WHERE o.member_id IS NOT NULL AND m.id IS NULL;
Assumptions of this model
This is a small example with 3 members and 3 orders. For all 48 combinations (8 statements × with or without constraints × 3 ON DELETE actions), the SQL on the screen was run as it is on a real MySQL 8.4.11 (InnoDB, default sql_mode), and the error number and error message, the number of rows changed, all the rows of the two tables afterwards, and the result of the check query were checked to be the same as in the model. The error numbers and messages are those of MySQL, and they differ in other databases. That an UPDATE that changes a NOT NULL column to NULL is rejected with error 1048 holds in strict SQL mode (the default in MySQL 8), and when the same UPDATE is run in a session with strict mode turned off, it saves an empty string without an error. That NULL can go into a unique column several times is behavior that was confirmed by running it on MySQL, and some databases, such as SQL Server, allow NULL only once (this comparison followed the documentation and was not checked by execution). When you do not write ON DELETE, MySQL behaves the same as RESTRICT (NO ACTION), and in InnoDB there is no difference between the two. That the child rows deleted together by CASCADE are not included in the number of rows changed is also MySQL behavior that was confirmed by running it. It was also confirmed by running it that when you write only REFERENCES right after a column definition (member_id INT REFERENCES member(id)), MySQL accepts it without an error but does not create a foreign key. That is why the screen writes a separate FOREIGN KEY clause. The foreign keys are for InnoDB, and this lab covers only the case where one statement is run at a time.
Next lab: ER modeling