The Foreign KEY Table constraint
Foreign Keys are the fundamentals of any relational databases, as they are the ones that make the connection or the relations among the tables in our database system.
Foreign key as the name suggest, this is a key referencing or pointing to a foreign (other) table, and that key could be a primary key for that table, hence referred to as key.
So, with a foreign key we can connect the data/records/row from other table to the table in which the foreign key is linked.
Think of it like a string(rope) attaching one record to the other. It is a link between two tables.
Now, how we define the FOREIGN KEY
constraint is what the rope will be tied to and how.
Let's take a look at how the syntax of defining a FOREIGN KEY
constraint looks like in SQLite.
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE posts (
id INTEGER PRIMARY KEY,
content TEXT NOT NULL,
author_id INTEGER,
FOREIGN KEY (author_id) REFERENCES users (id)
);
You will need atleast two tables in order to map a foreign key constraint. In the above example, we have created a table users
that will form the base table, and the table posts
is what is linking the id
from the users
table to itself.
This means one post can link to one user, we are using that reference in the current posts
table with the alias of author_id
. This way the author_id
is the rope (string whatever you prefer saying, I think developers get confused when I say string) that connects a record in the post table to the record in the users table.
We can conclude a few things from this:
- We need to define what that
author_id
is for, for each record we insert into theposts
table. - If we don't define the
author_id
then will it be automatically populated? Nope! - The
FOREIGN KEY
at its core is just like any other column, its just that how we set that column is what is important for the linking part. - It depends on how the actual key i.e. the column in the foreign key is defined in the foreign table.
Might still not make sense, let me explain with a few example.
Let's insert a bunch of users.
INSERT INTO users(name) VALUES('harry'), ('ron'), ('malfoy');
SELECT * FROM users;
This has 3 records each assigned ids from 1 to 3.
Now, let's insert a bunch of posts.
INSERT INTO posts(content) VALUES ('hi, I am who?');
SELECT * FROM posts;
That author_id
is NULL
because we didn't inserted anythign against it.
sqlite> CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE posts (
id INTEGER PRIMARY KEY,
content TEXT NOT NULL,
author_id INTEGER,
FOREIGN KEY (author_id) REFERENCES users (id)
);
sqlite> INSERT INTO users(name) VALUES('harry'), ('ron'), ('malfoy');
sqlite> .mode table
sqlite> SELECT * FROM users;
+----+--------+
| id | name |
+----+--------+
| 1 | harry |
| 2 | ron |
| 3 | malfoy |
+----+--------+
sqlite> INSERT INTO posts(content) VALUES ('hi, I am who?');
sqlite> SELECT * FROM posts;
+----+---------------+-----------+
| id | content | author_id |
+----+---------------+-----------+
| 1 | hi, I am who? | |
+----+---------------+-----------+
sqlite> INSERT INTO users(name, id) VALUES('neville', NULL);
sqlite> SELECT * FROM posts;
+----+---------------+-----------+
| id | content | author_id |
+----+---------------+-----------+
| 1 | hi, I am who? | |
+----+---------------+-----------+
sqlite> SELECT * FROM users;
+----+---------+
| id | name |
+----+---------+
| 1 | harry |
| 2 | ron |
| 3 | malfoy |
| 4 | neville |
+----+---------+
sqlite>
We need to be careful while inserting anything in that author_id
column, becuase think for a moment:
What do we insert in the author_id
column?
Well, we need to have a valid users(id)
right, that is the id in the users table, that is what we are referencing by that FOREIGN KEY
constraint.
There are other questions that should arise now, like:
- What happens if we link something that doesn't exist in the foreign table? (in this case the
users
table) - What happens if we what we have linked is deleted or changed in the foreign table?
- What happend if multiple rows refer the same id in the foreign table?
The FOREIGN KEY
constraint will make sure we are linking the valid id and hence if we try to insert any invalid (non-existent) id of the foreign table (here the users
table) we will fail this constraint and hence the record/row won't be inserted or updated.
As I said, this is a rope, the one end of the rope is always the current table record and the other one is what you attach it when inserting or updating a record.
So, for starters, we'll insert the users with the id that exist in the first place.
In our case, we have 4 authors created with the id from 1 to 4 and names as harry
, ron
, malfoy
and neville
respectively.
Now, let's insert a post with the author_id
as 1, that is the id of the user with the name harry
which will be the author of the post.
INSERT INTO posts(content, author_id) VALUES ('hi, I am harry', 1);
This will set the author_id
column of the posts
table to 1, which is the id of the harry
user.
If you want to see some better results, just run some of these to get it clear:
SELECT * FROM posts;
Get both tables data:
SELECT * FROM posts JOIN users ON author_id = id;
Make column names clear and remove redundant columns:
SELECT posts.*, users.name FROM posts JOIN users ON posts.author_id = users.id;
sqlite> INSERT INTO posts(content, author_id) VALUES ('hi, I am harry', 1);
sqlite> SELECT * FROM posts;
+----+----------------+-----------+
| id | content | author_id |
+----+----------------+-----------+
| 1 | hi, I am who? | |
| 2 | hi, I am harry | 1 |
+----+----------------+-----------+
sqlite> SELECT * FROM posts JOIN users ON author_id = id;
Parse error: ambiguous column name: id
sqlite> SELECT * FROM posts JOIN users ON posts.author_id = users.id;
+----+----------------+-----------+----+-------+
| id | content | author_id | id | name |
+----+----------------+-----------+----+-------+
| 2 | hi, I am harry | 1 | 1 | harry |
+----+----------------+-----------+----+-------+
sqlite> SELECT posts.*, users.name FROM posts JOIN users ON posts.author_id = users.id;
+----+----------------+-----------+-------+
| id | content | author_id | name |
+----+----------------+-----------+-------+
| 2 | hi, I am harry | 1 | harry |
+----+----------------+-----------+-------+
sqlite> SELECT posts.*, users.name AS author FROM posts JOIN users ON posts.author_id = users.id;
+----+----------------+-----------+--------+
| id | content | author_id | author |
+----+----------------+-----------+--------+
| 2 | hi, I am harry | 1 | harry |
+----+----------------+-----------+--------+
sqlite>
Now, try inserting a post with the author_id
which doesn't exist in the users
table i.e. the id of the users above 4.
INSERT INTO posts(content, author_id) VALUES ('hi, I am hermoine', 5);
That inserted successfully? How can that be allowed? The user with id 5
doesn't exist and we are referencing the author (users) that doesn't exist.
Pragma Foreign keys
Well, we have been lied to all along, the SQLite is so flexible by default, you need to nudge it inorder to be a little strict:
We need to enable the foreign key constraint checking by setting the PRAGMA foreign_keys=on;
PRAGMA foreign_keys=on;
This setting is off by default due to backward compatibility reasons.
You can check if the foreign key constraint checking is enabled by running the following command:
PRAGMA foreign_keys;
If it returns 1
then its enabled, or 0
if not.
Now, now if we run the query where the author id is 5
which means the user
referenced by that id doesn't exist, we will get the following error:
INSERT INTO posts(content, author_id) VALUES ('hi, I am hermoine', 5);
Runtime error: FOREIGN KEY constraint failed
Exactly as we expected.
sqlite> INSERT INTO posts(content, author_id) VALUES ('hi, I am hermoine', 5);
sqlite> SELECT posts.*, users.name AS author FROM posts JOIN users ON posts.author_id = users.id;
+----+----------------+-----------+--------+
| id | content | author_id | author |
+----+----------------+-----------+--------+
| 2 | hi, I am harry | 1 | harry |
+----+----------------+-----------+--------+
sqlite> SELECT * FROM posts;
+----+-------------------+-----------+
| id | content | author_id |
+----+-------------------+-----------+
| 1 | hi, I am who? | |
| 2 | hi, I am harry | 1 |
| 3 | hi, I am hermoine | 5 |
+----+-------------------+-----------+
sqlite> PRAGMA foreign_keys=on
...> ;
sqlite> INSERT INTO posts(content, author_id) VALUES ('hi, I am hermoine', 5);
Runtime error: FOREIGN KEY constraint failed (19)
sqlite>
However if we insert the posts
record with a valid author_id
then:
INSERT INTO posts(content, author_id) VALUES ('hi, I am ron', 2);
This will readily insert the record, since the author_id
of 2 exists in the users
table.
SELECT * FROM posts;
Neat.
sqlite> INSERT INTO posts(content, author_id) VALUES ('hi, I am ron', 2);
sqlite> SELECT * FROM posts;
+----+-------------------+-----------+
| id | content | author_id |
+----+-------------------+-----------+
| 1 | hi, I am who? | |
| 2 | hi, I am harry | 1 |
| 3 | hi, I am hermoine | 5 |
| 4 | hi, I am ron | 2 |
+----+-------------------+-----------+
sqlite>
Now, back to those three questions:
- What happens if we link something that doesn't exist in the foreign table? (in this case the
users
table) - What happens if we what we have linked is deleted or changed in the foreign table?
- What happend if multiple rows refer the same id in the foreign table?
Let's tackle one by one
NOTE: We'll assume PRAGMA foreign_keys=on; from here on
Invalid Foreign Key
We already covered this in the previous discussion, if we link something that doesn't exist in the foreign table (in this case the users
table) then we will fail the FOREIGN KEY
constraint and hence the record/row won't be inserted or updated. But for this to fail we need to make sure the PRAGMA foreign_keys
is on or toggled on.
Deleted or Updated Foreign Key
What happens, if we inserted a record to the primary table (here the posts
table) the author_id
was existing and valid at the time of insertion, however after a while the user
got deleted, what happens to the record that the posts
record/row that still references the deleted user id that is the author_id
alias.
Enter ON DELETE
and ON UPDATE
clause on the FOREIGN KEY
constraint.
It turns out, we can define the behaviour of the FOREIGN KEY
constraint when the referenced row is deleted or updated in the foreign table (here the users
table).
So, we need to define, what happens to the record/row in the primary table (here the posts
table) when the referenced row in the foreign table (here the users
table) is deleted or updated.
There are 5 options to choose from:
NO ACTION
: Do nothing (default)RESTRICT
: Stop the foreign key table from deleting the record/key in the foreign tableSET NULL
: Set the primary table's record foreign key toNULL
SET DEFAULT
: Set the primary table's record foreign key to the default value.CASCADE
: Delete the primary table's record/row if the referenced row in the foreign table is deleted.
Phew! This opens a lot of options to play with:
We already know the NO ACTION
, it will just let it happen, nothing will be done. It's not recommended though, but SQLite is flexible, how many times I have to say it? Double edged sword.
Restrict
Let's drop the posts
table and start a fresh with the ON DELETE
and ON UPDATE
as RESTRICT
option on the FOREIGN KEY
constraint.
Restrict on delete
We can restrict the deletion of the foreign records referred by the primary record. We use the ON DELETE RESTRICT
as the option on the FOREIGN KEY
constraint.
DROP TABLE posts;
CREATE TABLE posts (
id INTEGER PRIMARY KEY,
content TEXT NOT NULL,
author_id INTEGER,
FOREIGN KEY (author_id) REFERENCES users (id) ON DELETE RESTRICT
);
This will now restrict the deletion of the foreign records referred by the primary record. In this case, if a record/row in users
table which is refereced in the posts
table is deleted, it will prevent it, it won't be deleted. However, any other record in the users
table can be deleted.
Let's first insert a few rows into the posts
table since we dropped it.
INSERT INTO posts(content, author_id) VALUES ('hi, I am harry', 1);
INSERT INTO posts(content, author_id) VALUES ('hi, I am ron', 2);
These are valid records, since the author_id
of 1 and 2 exist in the users
table.
However the following query will fail, since the author_id
of 5 doesn't exist in the users
table. We already know this:
INSERT INTO posts(content, author_id) VALUES ('hi, I am hermoine', 5);
Now, Let's look at all the posts
and their authors
SELECT * FROM posts;
This is the list of users
that we have:
SELECT * FROM users;
Now, let's try deleting a record in the users
table.
We know that users
with id 1
and 2
are referenced in the posts
table.
DELETE FROM users WHERE id = 2;
This will fail, since we are trying to delete a record that is referenced in the posts
table.
We can check if the users
with id
2 is still there:
SELECT * FROM users;
It is indeed ther, it restricted the deletion.
However if we delete the users
where id is 3
which is not referenced in the posts
table, it will succeed.
DELETE FROM users WHERE id = 3;
This deleted the users
with id
3.
SELECT * FROM users;
As you can see the dirt malfoy
was deleted.
sqlite> DROP TABLE posts;
CREATE TABLE posts (
id INTEGER PRIMARY KEY,
content TEXT NOT NULL,
author_id INTEGER,
FOREIGN KEY (author_id) REFERENCES users (id) ON DELETE RESTRICT
);
sqlite> SELECT * FROM posts;
sqlite> INSERT INTO posts(content, author_id) VALUES ('hi, I am harry', 1);
sqlite> INSERT INTO posts(content, author_id) VALUES ('hi, I am ron', 2);
sqlite> SELECT * FROM posts;
+----+----------------+-----------+
| id | content | author_id |
+----+----------------+-----------+
| 1 | hi, I am harry | 1 |
| 2 | hi, I am ron | 2 |
+----+----------------+-----------+
sqlite> INSERT INTO posts(content, author_id) VALUES ('hi, I am hermoine', 5);
Runtime error: FOREIGN KEY constraint failed (19)
sqlite> SELECT * FROM users;
+----+---------+
| id | name |
+----+---------+
| 1 | harry |
| 2 | ron |
| 3 | malfoy |
| 4 | neville |
+----+---------+
sqlite> DELETE FROM users where id = 2;
Runtime error: FOREIGN KEY constraint failed (19)
sqlite> DELETE FROM users where id = 3;
sqlite> SELECT * FROM users;
+----+---------+
| id | name |
+----+---------+
| 1 | harry |
| 2 | ron |
| 4 | neville |
+----+---------+
sqlite>
What about updates?
Restrict on update
What if we update the users record will it restrict it, nope, unless we set ON UPDATE
to RESTRICT
:
This option of
UPDATE ON
is usually not needed, as this is referring to theUPDATE ON
theFOREIGN KEY
and not on the entire foreign table columns, so only if you update the foreign key it will restrict or any action will be performed.
Which means, if you update the name
of the users
table of a record which is referenced in the posts
table, it will allow it, however when you update the users
id (which doen't happen unless you've skewed up or it's by design), it will prevent that updation. Note, updating foreign keys which are usually the primary keys of the foreign table is very rare, and not done usually, as it might corrupt the existing data.
So, unless you have a great usecase for updating the foreign key of a table, UPDATE ON
is never used.
DROP TABLE posts;
CREATE TABLE posts (
id INTEGER PRIMARY KEY,
content TEXT NOT NULL,
author_id INTEGER,
FOREIGN KEY (author_id) REFERENCES users (id) ON DELETE RESTRICT ON UPDATE RESTRICT
);
Here, we are adding the ON UPDATE RESTRICT
option on the FOREIGN KEY
constraint, the ON DELETE RESTRICT
option won't affect the update option, so it's only triggered when we try to delete the record (hard delete).
Let's add few more rows as usual:
INSERT INTO posts(content, author_id) VALUES ('hi, I am harry', 1);
INSERT INTO posts(content, author_id) VALUES ('hi, I am ron', 2);
Now, let's try updating the users
table:
UPDATE users SET name = 'harry potter' WHERE id = 1;
This is allowed, nothing got restricted, as we didn't update the id
in the users
table which is a foreign key in the posts
table, unless we update the id
of the users
table, the update
option will allow any updates of the columns in the foreign table (users table)
SELECT * FROM users;
This readily updated the row with id
1 to harry potter
as it didn't mutate/change/update the foreign key id
in the users
table which is referenced as author_id
in the posts
table.
However, if we try to update the id
of the users
table, it will fail.
UPDATE users SET id = 3 WHERE id = 2;
We are updating the id of the users
table to 3 which is not the id of the users
table referenced in the posts
table, so it will fail. The existing row of id = 2 is ron
, hence it already has a posts entry which makes it a reference, as we have added a constraint to restrict on updation of the id the foreign key.
Now, let's try to update the id
of the users
table to the id of the users
table not referenced in the posts
table
UPDATE users SET id = 5 WHERE id = 4;
The row with id = 4 in the users
table is neville
which doesn't have any posts entry, so it will succeed.
This will succeed, as we are updating the id
of the users
table to the id of the users
table not referenced in the posts
table
sqlite> DROP TABLE posts;
CREATE TABLE posts (
id INTEGER PRIMARY KEY,
content TEXT NOT NULL,
author_id INTEGER,
FOREIGN KEY (author_id) REFERENCES users (id) ON DELETE RESTRICT ON UPDATE RESTRICT
);
sqlite> SELECT * FROM posts;
sqlite> INSERT INTO posts(content, author_id) VALUES ('hi, I am harry', 1);
sqlite> INSERT INTO posts(content, author_id) VALUES ('hi, I am ron', 2);
sqlite> SELECT * FROM posts;
+----+----------------+-----------+
| id | content | author_id |
+----+----------------+-----------+
| 1 | hi, I am harry | 1 |
| 2 | hi, I am ron | 2 |
+----+----------------+-----------+
sqlite> SELECT * FROM users;
+----+---------+
| id | name |
+----+---------+
| 1 | harry |
| 2 | ron |
| 4 | neville |
+----+---------+
sqlite> SELECT posts.*, users.name AS author FROM posts JOIN users ON posts.author_id = users.id;
+----+----------------+-----------+--------+
| id | content | author_id | author |
+----+----------------+-----------+--------+
| 1 | hi, I am harry | 1 | harry |
| 2 | hi, I am ron | 2 | ron |
+----+----------------+-----------+--------+
sqlite> UPDATE users SET name = 'harry potter' where id = 1;
sqlite> SELECT * FROM users;
+----+--------------+
| id | name |
+----+--------------+
| 1 | harry potter |
| 2 | ron |
| 4 | neville |
+----+--------------+
sqlite> SELECT posts.*, users.name AS author FROM posts JOIN users ON posts.author_id = users.id;
+----+----------------+-----------+--------------+
| id | content | author_id | author |
+----+----------------+-----------+--------------+
| 1 | hi, I am harry | 1 | harry potter |
| 2 | hi, I am ron | 2 | ron |
+----+----------------+-----------+--------------+
sqlite> UPDATE users SET name = 'harry' where id = 1;
sqlite> UPDATE users SET id = 3 where id = 2;
Runtime error: FOREIGN KEY constraint failed (19)
sqlite> UPDATE users SET id = 5 where id = 4;
sqlite> SELECT * FROM users;
+----+---------+
| id | name |
+----+---------+
| 1 | harry |
| 2 | ron |
| 5 | neville |
+----+---------+
sqlite>
Set NULL
For the rest of this post, we'll only be looking at the ON DELETE
constraint, the ON UPDATE
is very rarely used and we have explored already when it is used.
In this option, when the record in the foreign table is deleted, the foreign key reference of the record in the primary table will be set to NULL
.
For our example, if we set ON DELETE SET NULL
to the FOREIGN KEY
constraint, when the users
record with id = 2 is deleted, the author_id
of the posts
record with id = 3 will be set to NULL
.
DROP TABLE posts;
CREATE TABLE posts (
id INTEGER PRIMARY KEY,
content TEXT NOT NULL,
author_id INTEGER,
FOREIGN KEY (author_id) REFERENCES users (id) ON DELETE SET NULL
);
Now, let's add few more rows as usual: NOTE: I have added one more post for harry to avoid the 1-1 mapping of posts and users in the data
INSERT INTO posts(content, author_id) VALUES ('hi, I am harry', 1);
INSERT INTO posts(content, author_id) VALUES ('Expecto Patronum', 1);
INSERT INTO posts(content, author_id) VALUES ('hi, I am ron', 2);
Let's take a look at the posts table.
SELECT * FROM posts;
Now, let's delete the users
record with id = 2:
DELETE FROM users WHERE id = 2;
This will allow and should, as there is no restriction on the posts
table, the FOREIGN KEY
constraint is SET NULL
when the reference in the users
table is deleted.
SELECT * FROM posts;
As you can see, the author_id
of the posts
record with id = 3 has been set to NULL
sqlite> DROP TABLE posts;
CREATE TABLE posts (
id INTEGER PRIMARY KEY,
content TEXT NOT NULL,
author_id INTEGER,
FOREIGN KEY (author_id) REFERENCES users (id) ON DELETE SET NULL
);
sqlite> INSERT INTO posts(content, author_id) VALUES ('hi, I am harry', 1);
INSERT INTO posts(content, author_id) VALUES ('Expecto Patronum', 1);
INSERT INTO posts(content, author_id) VALUES ('hi, I am ron', 2);
sqlite> SELECT * FROM posts;
+----+------------------+-----------+
| id | content | author_id |
+----+------------------+-----------+
| 1 | hi, I am harry | 1 |
| 2 | Expecto Patronum | 1 |
| 3 | hi, I am ron | 2 |
+----+------------------+-----------+
sqlite> SELECT posts.*, users.name AS author FROM posts JOIN users ON posts.author_id = users.id;
+----+------------------+-----------+--------+
| id | content | author_id | author |
+----+------------------+-----------+--------+
| 1 | hi, I am harry | 1 | harry |
| 2 | Expecto Patronum | 1 | harry |
| 3 | hi, I am ron | 2 | ron |
+----+------------------+-----------+--------+
sqlite> SELECT * FROM users;
+----+---------+
| id | name |
+----+---------+
| 1 | harry |
| 2 | ron |
| 5 | neville |
+----+---------+
sqlite> DELETE FROM users WHERE id = 2;
sqlite> SELECT * FROM posts;
+----+------------------+-----------+
| id | content | author_id |
+----+------------------+-----------+
| 1 | hi, I am harry | 1 |
| 2 | Expecto Patronum | 1 |
| 3 | hi, I am ron | |
+----+------------------+-----------+
sqlite>
Set Default
In this option, when the record in the foreign table is deleted, the foreign key reference of the record in the primary table will be set to the default value of the foreign key.
NOTE: We have not set the default value for the author_id
column in the posts
table, so it will be set to NULL
by default. We can add normal constraints like DEFAULT
, UNIQUE
, NOT NULL
, CHECK
, etc on the foreign key as and when required.
DROP TABLE posts;
CREATE TABLE posts (
id INTEGER PRIMARY KEY,
content TEXT NOT NULL,
author_id INTEGER DEFAULT 1,
FOREIGN KEY (author_id) REFERENCES users (id) ON DELETE SET DEFAULT
);
We are setting default foreign key as 1
which means the users
record with id = 1 will be the default reference for the posts
table. If you don't provide the author_id
, all post will be authored by harry
what a funny quirk of the posts.
Let's add rows as usual:
INSERT INTO posts(content, author_id) VALUES ('hi, I am harry', 1);
INSERT INTO posts(content, author_id) VALUES ('Expecto Patronum', 1);
INSERT INTO posts(content, author_id) VALUES ('hi, I am ron', 2);
Now, let's take a look at the posts table:
SELECT * FROM posts;
We can now try deleting the users
record with id = 2, Since it is already tied / referenced by the posts
table, the users record will be deleted, but the posts
record with author_id
2 will be set to the default value 1
which is the id of the users
record with name harry
.
DELETE FROM users WHERE id = 2;
It ran successfully, but the posts
record with author_id
2 will be set to the default value 1
which is the id of the users
record with name harry
.
SELECT * FROM posts;
Let's view more closely:
SELECT posts.*, users.name AS author FROM posts JOIN users ON posts.author_id = users.id;
View the SQLog :)
sqlite> DROP TABLE posts;
CREATE TABLE posts (
id INTEGER PRIMARY KEY,
content TEXT NOT NULL,
author_id INTEGER DEFAULT 1,
FOREIGN KEY (author_id) REFERENCES users (id) ON DELETE SET DEFAULT
);
sqlite> SELECT * FROM users;
+----+---------+
| id | name |
+----+---------+
| 1 | harry |
| 2 | ron |
| 5 | neville |
+----+---------+
sqlite> INSERT INTO posts(content, author_id) VALUES ('hi, I am harry', 1);
INSERT INTO posts(content, author_id) VALUES ('Expecto Patronum', 1);
INSERT INTO posts(content, author_id) VALUES ('hi, I am ron', 2);
sqlite> SELECT * FROM posts;
+----+------------------+-----------+
| id | content | author_id |
+----+------------------+-----------+
| 1 | hi, I am harry | 1 |
| 2 | Expecto Patronum | 1 |
| 3 | hi, I am ron | 2 |
+----+------------------+-----------+
sqlite> SELECT posts.*, users.name AS author FROM posts JOIN users ON posts.author_id = users.id;
+----+------------------+-----------+--------+
| id | content | author_id | author |
+----+------------------+-----------+--------+
| 1 | hi, I am harry | 1 | harry |
| 2 | Expecto Patronum | 1 | harry |
| 3 | hi, I am ron | 2 | ron |
+----+------------------+-----------+--------+
sqlite> DELETE FROM users where id = 2;
sqlite> SELECT * FROM posts;
+----+------------------+-----------+
| id | content | author_id |
+----+------------------+-----------+
| 1 | hi, I am harry | 1 |
| 2 | Expecto Patronum | 1 |
| 3 | hi, I am ron | 1 |
+----+------------------+-----------+
sqlite> SELECT posts.*, users.name AS author FROM posts JOIN users ON posts.author_id = users.id;
+----+------------------+-----------+--------+
| id | content | author_id | author |
+----+------------------+-----------+--------+
| 1 | hi, I am harry | 1 | harry |
| 2 | Expecto Patronum | 1 | harry |
| 3 | hi, I am ron | 1 | harry |
+----+------------------+-----------+--------+
sqlite>
Let's take a look at the CASCADE
option
Cascade
If we set ON DELETE CASCADE
to the FOREIGN KEY
constraint, when the users
record with id = 2 is deleted, the posts
record with author_id
2 will be deleted as well.
We can set the CASCADE
option to the FOREIGN KEY
constraint as follows:
DROP TABLE posts;
CREATE TABLE posts (
id INTEGER PRIMARY KEY,
content TEXT NOT NULL,
author_id INTEGER,
FOREIGN KEY (author_id) REFERENCES users (id) ON DELETE CASCADE
);
Now, let's add few more rows as usual
INSERT INTO posts(content, author_id) VALUES ('hi, I am harry', 1);
INSERT INTO posts(content, author_id) VALUES ('Expecto Patronum', 1);
INSERT INTO posts(content, author_id) VALUES ('hi, I am ron', 2);
We now have 3 posts, associated with 2 users.
SELECT * FROM posts;
Now, let's delete the users
record with id = 2
DELETE FROM users WHERE id = 2;
This will not only delete the users
record with id = 2, but it will also delete the posts
record with author_id
2.
SELECT * FROM posts;
Cascade as the name suggest, when the foreign key in the foreign table is deleted, the primary row is cascaded or deleted.
sqlite> DROP TABLE posts;
CREATE TABLE posts (
id INTEGER PRIMARY KEY,
content TEXT NOT NULL,
author_id INTEGER,
FOREIGN KEY (author_id) REFERENCES users (id) ON DELETE CASCADE
);
sqlite> SELECT * FROM users;
+----+---------+
| id | name |
+----+---------+
| 1 | harry |
| 2 | ron |
| 5 | neville |
+----+---------+
sqlite> INSERT INTO posts(content, author_id) VALUES ('hi, I am harry', 1);
INSERT INTO posts(content, author_id) VALUES ('Expecto Patronum', 1);
INSERT INTO posts(content, author_id) VALUES ('hi, I am ron', 2);
sqlite> SELECT * FROM posts;
+----+------------------+-----------+
| id | content | author_id |
+----+------------------+-----------+
| 1 | hi, I am harry | 1 |
| 2 | Expecto Patronum | 1 |
| 3 | hi, I am ron | 2 |
+----+------------------+-----------+
sqlite> DELETE FROM users where id = 2;
sqlite> SELECT * FROM posts;
+----+------------------+-----------+
| id | content | author_id |
+----+------------------+-----------+
| 1 | hi, I am harry | 1 |
| 2 | Expecto Patronum | 1 |
+----+------------------+-----------+
sqlite>
Many Foreign Keys
What would happen if there are more than one row in the primary table that references the same id in the foreign table?
That is a question for another post.