Summary: in this tutorial, you will learn about foreign keys and how to use PostgreSQL foreign key constraints to create foreign key columns for a table.
Introduction to foreign keys
In PostgreSQL, a foreign key is a column or set of columns in one table that references the primary key of another table. It serves as a link between the two tables.
For example, the following table products
has a foreign key column called brand_id
that references the brand_id
column of the table brands
:
The table with the foreign key column is called the child table, while the table with the primary key column that the child table references is known as the referenced or parent table.
In this example, the table brands
is the referenced or parent table, while the products table is the child table.
A foreign key ensures data in the child table matches the related data in the parent table. For example, if you have a brand id in the products
table, that brand id should match a brand id in the brands
table.
A foreign key helps maintain referential integrity. For instance, if a product references a brand, that brand must exist in the brands
table.
When you delete a row in the parent table, you should handle the child table properly to avoid orphaned records.
Note that orphaned records are records in a table referencing a non-existent primary key value in another table.
For example, if a row in the brands
is deleted, all the related rows in the products
table should be deleted automatically.
Foreign keys in one-to-many relationship
When you have two tables with a one-to-many relationship, the table on the “many” side will have the foreign key column.
For example, the relationship between the brands
and products
tables is one-to-many. The products
table is the “many” side; it should get the foreign key column:
The products
table has an additional column called brand_id
that references the brand_id
of the brands
table.
Similarly, the relationship between the categories
and products
tables is a one-to-many relationship.
The products
table has a foreign key column called category_id
that references the category_id
column of the categories
table:
A table may have multiple foreign key columns, like the products
table.
The values in the foreign key columns are equal to those of the primary key of the reference rows. For example, the value in the brand_id
column of row one is equal to the value in the brand_id
column of the brands
table.
By convention, the foreign key column has the format table_id
. For example, the foreign key columns are category_id
and brand_id
.
Multiple rows can have the same foreign key column values. For example, both rows with the product name iPhone 14 Pro
and iPhone 15 Pro
have the same brand_id
1.
Foreign keys in a many-to-many relationship
In PostgreSQL, you use two one-to-many relationships to model a many-to-many relationship.
For example, the relationship between products
and tags
tables is many-to-many.
To model this relationship, we create a new table called product_tags
, often called a link table:
The product_tags
will have two foreign keys; one references the product_id
column in the products table, and another references the tag_id
column in the tags
table.
Foreign key constraints
In PostgreSQL, you use the foreign key constraint to set up a foreign key.
Here’s the basic syntax for defining a foreign key constraint:
CONSTRAINT constraint_name
FOREIGN KEY (fk_column)
REFERENCES table(pk_column)
ON DELETE delete_action
ON UPDATE update_action;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
In this syntax:
- First, specify the name of the foreign key constraint in the
CONSTRAINT
clause. If you don’t, PostgreSQL will automatically generate a constraint name. - Second, place the foreign key column (
fk_column
) inside the parentheses()
after theFOREIGN KEY
keywords. - Third, provide the table and primary key column that the foreign key column references after the
REFERENCES
keyword. - Finally, set the delete and update actions to specify the behaviors when a row in the parent table primary key in the table is deleted and updated.
The FOREIGN KEY
and REFERENCES
clauses are mandatory, while the CONSTRAINT
, ON DELETE
, and ON UPDATE
clauses are optional.
Foreign key constraint example
First, create a new table called brands
:
CREATE TABLE brands (
brand_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Second, create a new table called products
with the foreign key column brand_id
that references the brand_id
column of the brands
table:
CREATE TABLE products (
product_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
brand_id INT NOT NULL,
FOREIGN KEY (brand_id) REFERENCES brands (brand_id)
);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Third, insert three rows into the brands
table:
INSERT INTO brands(name)
VALUES ('Apple'), ('Samsung'), ('Google')
RETURNING *;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Output:
brand_id | name
----------+---------
1 | Apple
2 | Samsung
3 | Google
Code language: plaintext (plaintext)
Inserting data into a foreign key table
If you insert a new row into the products
table, the value of the brand_id
must exist in the brands
table. For example, the following insert a new row into the products
table with the brand_id
1:
INSERT INTO products(name, price, brand_id)
VALUES('iPhone 14 Pro', 999.99, 1)
RETURNING *;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Output:
product_id | name | price | brand_id
------------+---------------+--------+----------
1 | iPhone 14 Pro | 999.99 | 1
Code language: plaintext (plaintext)
You will encounter an error if you attempt to insert a row with a brand id that does not exist in the brands
table.
The following statement attempts to insert a new row into the products
table with an invalid brand id:
INSERT INTO products(name, price, brand_id)
VALUES('iPhone 15 Pro', 1299.99, 11)
RETURNING *;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
PostgreSQL issues the following error:
insert or update on table "products" violates foreign key constraint "products_brand_id_fkey"
Code language: plaintext (plaintext)
Dropping tables with foreign key constraints
When you drop a table referenced by other tables via foreign key constraints, PostgreSQL will issue an error.
For example, the products
table references the brands
table via a foreign key constraint. If you drop the brands table, you will get an error:
DROP TABLE brands;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Error:
cannot drop table brands because other objects depend on it
Code language: plaintext (plaintext)
To drop the brands
table, you follow these steps:
- First, drop foreign key constraint that references the
brand_id
column in the table brands. - Second, drop the table
brands
.
You can do both steps using the DROP TABLE ... CASCADE
statement:
DROP TABLE brands CASCADE;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
This statement dropped the constraint products_brand_id_fkey
on the table products
.
Let’s also drop the products
table:
DROP TABLE products;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Deletion actions
The deletion action allows you to specify an action to the rows in the child tables when rows in the parent table are deleted:
ON DELETE delete_action;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
The delete_action
can be one of the following:
NO ACTION
– issues a constraint violation error. TheNO ACTION
is the default.SET NULL
– sets the values of foreign key column toNULL
.CASCADE
– deletes all related rows in the child table.SET DEFAULT
– sets the default values for the foreign key columns in the child table.RESTRICT
works like theNO ACTION.
ON DELETE SET NULL
First, recreate the brands
table:
CREATE TABLE brands (
brand_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Second, create the products
table that has the brand_id
as the foreign key column referenced the brand_id
column of the brands
table:
CREATE TABLE products (
product_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
brand_id INT,
FOREIGN KEY (brand_id) REFERENCES brands (brand_id)
ON DELETE SET NULL
);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Third, insert rows into the brands
and products
tables:
INSERT INTO brands(name)
VALUES ('Apple'), ('Samsung')
RETURNING *;
INSERT INTO products(name, price, brand_id)
VALUES
('iPhone 14 Pro', 999.99, 1),
('iPhone 15 Pro', 1299.99, 1),
('Galaxy S23 Ultra', 1299.99, 2)
RETURNING *;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Fourth, delete a row from the brands
table with the id 1:
DELETE FROM brands WHERE brand_id = 1;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Finally, retrieve data from the products
table:
SELECT * FROM products;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Output:
product_id | name | price | brand_id
------------+------------------+---------+----------
3 | Galaxy S23 Ultra | 1299.99 | 2
1 | iPhone 14 Pro | 999.99 | NULL
2 | iPhone 15 Pro | 1299.99 | NULL
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
The output shows that the ON DELETE SET NULL
action sets the values of the brand_id
column of the related products to NULL
.
ON DELETE CASCADE
First, drop the brands
and products
table:
DROP TABLE brands, products;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Second, create the brands
table and products
table with the ON DELETE CASCADE
action:
CREATE TABLE brands (
brand_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
CREATE TABLE products (
product_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
brand_id INT,
FOREIGN KEY (brand_id) REFERENCES brands (brand_id)
ON DELETE CASCADE
);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Third, insert rows into the products
tables:
INSERT INTO brands(name)
VALUES('Apple'), ('Samsung')
RETURNING *;
INSERT INTO products(name, price, brand_id)
VALUES
('iPhone 14 Pro', 999.99, 1),
('iPhone 15 Pro', 1299.99, 1),
('Galaxy S23 Ultra', 1299.99, 2)
RETURNING *;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Fourth, delete a row from the brands
table with the id 1:
DELETE FROM brands
WHERE brand_id = 1;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Finally, retrieve data from the products
table:
SELECT * FROM products;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Output:
product_id | name | price | brand_id
------------+------------------+---------+----------
3 | Galaxy S23 Ultra | 1299.99 | 2
Code language: plaintext (plaintext)
The output shows that the ON DELETE CASCADE
action delete the rows in the products
table with the brand_id
column 1.
ON DELETE SET DEFAULT
First, drop the brands
and products
tables:
DROP TABLE brands, products;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Second, create the brands
table:
CREATE TABLE brands (
brand_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Third, create the products
table:
CREATE TABLE products (
product_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
brand_id INT DEFAULT 1,
FOREIGN KEY (brand_id) REFERENCES brands (brand_id)
ON DELETE SET DEFAULT
);
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
In this statement, we set the default value of the brand_id column to 1.
Fourth, insert rows into the brands
table:
INSERT INTO brands(name)
VALUES('Unknown'),
('Apple'),
('Samsung')
RETURNING *;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
The row with id 1
will be “Unknown
“.
Fifth, insert rows into products
table:
INSERT INTO products(name, price, brand_id)
VALUES
('iPhone 14 Pro', 999.99, 2),
('iPhone 15 Pro', 1299.99, 2),
('Galaxy S23 Ultra', 1299.99, 3)
RETURNING *;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Sixth, delete a row from the brands
table with the id 2:
DELETE FROM brands
WHERE brand_id = 2;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Due to the ON DELETE SET DEFAULT
action, the statement sets the value of the brand_id
column of the rows with brand_id
2 to 1.
Seventh, retrieve data from the products
table:
SELECT * FROM products;
Code language: PostgreSQL SQL dialect and PL/pgSQL (pgsql)
Output:
product_id | name | price | brand_id
------------+------------------+---------+----------
3 | Galaxy S23 Ultra | 1299.99 | 3
1 | iPhone 14 Pro | 999.99 | 1
2 | iPhone 15 Pro | 1299.99 | 1
Code language: plaintext (plaintext)
The output shows that the ON DELETE SET DEFAULT
action sets the values of the brand_id
column of the related products to a default value of the brand_id
column, which is 1.
Summary
- A foreign key is a column or set of columns that references the primary key of another table.
- A table can have one or more foreign key columns.
- Use the PostgreSQL foreign key constraint to create a foreign key for a table.
- Use the
ON DELETE SET NULL
to set the value of the foreign key columns to NULL when the corresponding rows from the parent table are deleted. - Use the
ON DELETE CASCADE
to delete the rows in the child table when the corresponding rows from the parent table are deleted. - Use the
ON DELETE SET DEFAULT
to set the values of the foreign key columns to their default values when the corresponding rows from the parent table are deleted. - Use the
ON DELETE NO ACTION
to prevent the rows from the parent table to be deleted when they have referenced rows in the child tables.