Modelling many-to-many table relationships
Introduction
A many-to-many
relationship between two tables can be established by
creating a table typically called as bridge/junction/join table and
adding foreign-key constraints from it to the original tables.
Say we have the following two tables in our database schema:
articles (
id SERIAL PRIMARY KEY,
title TEXT
...
)
tags (
id SERIAL PRIMARY KEY,
tag_value TEXT
...
)
These two tables are related via a many-to-many
relationship. i.e:
- an
article
can have manytags
- a
tag
has manyarticles
Step 1: Set up a table relationship in the database
This many-to-many
relationship can be established in the database by:
Creating a bridge table called
article_tag
with the following structure:article_tag (
id SERIAL PRIMARY KEY
article_id INT
tag_id INT
UNIQUE (article_id, tag_id)
...
)
Note
If you can have multiple rows linking the same article
and tag
in your model, you can skip the unique constraint on
(article_id, tag_id)
Adding foreign key constraints in the
article_tag
table for:- the
articles
table using thearticle_id
andid
columns of the tables respectively - the
tags
table using thetag_id
andid
columns of the tables respectively
- the
The table article_tag
sits between the two tables involved in the
many-to-many relationship and captures possible permutations of their
association via the foreign keys.
Step 2: Set up GraphQL relationships
To access the nested objects via the GraphQL API, create the following relationships:
- Array relationship,
article_tags
fromarticles
table usingarticle_tag :: article_id -> id
- Object relationship,
tag
fromarticle_tag
table usingtag_id -> tags :: id
- Array relationship,
tag_articles
fromtags
table usingarticle_tag :: tag_id -> id
- Object relationship,
article
fromarticle_tag
table usingarticle_id -> articles :: id
Query using many-to-many relationships
We can now:
- fetch a list of
articles
with theirtags
:
- fetch a list of
tags
with theirarticles
:
Insert using many-to-many relationships
We can now:
- insert an
article
withtags
where thetag
might already exist (assume uniquevalue
fortag
):
- insert a
tag
witharticles
where thetag
might already exist (assume uniquevalue
fortag
):
Note
You can avoid the on_conflict
clause if you will never have conflicts.
Fetching relationship information
The intermediate fields article_tags
& tag_articles
can be used to
fetch extra information about the relationship. For example, you can
have a column like tagged_at
in the article_tag
table which you can
fetch as follows:
Flattening a many-to-many relationship query
In case you would like to flatten the above queries and avoid the
intermediate fields article_tags
& tag_articles
, you can
create the following views additionally and then
query using relationships created on these views:
CREATE VIEW article_tags_view AS
SELECT article_id, tags.*
FROM article_tag LEFT JOIN tags
ON article_tag.tag_id = tags.id
CREATE VIEW tag_articles_view AS
SELECT tag_id, articles.*
FROM article_tag LEFT JOIN articles
ON article_tag.article_id = articles.id
Now create the following relationships:
- Array relationship,
tags
from thearticles
table usingarticle_tags_view :: article_id -> id
- Array relationship,
articles
from thetags
table usingtag_articles_view :: tag_id -> id
We can now:
- fetch articles with their tags without an intermediate field:
- fetch tags with their articles without an intermediate field:
Note
We do not recommend this flattening pattern of modelling as this
introduces an additional overhead of managing permissions and
relationships on the newly created views. e.g. You cannot query for the
author of the nested articles without setting up a new relationship to
the authors
table from the tag_articles_view
view.
In our opinion, the cons of this approach seem to outweigh the pros.