mirror of
https://github.com/directus/directus.git
synced 2026-01-28 13:17:56 -05:00
426 lines
18 KiB
Markdown
426 lines
18 KiB
Markdown
# Relationships
|
|
|
|
> Relationships are a crucial part of any relational database. Directus supports all standard relationship types, as
|
|
> well as a few more of its own _compound_ types, which are custom-tailored to make certain _common but complex_ tasks a
|
|
> breeze.
|
|
|
|
:::tip Before You Begin
|
|
|
|
Regardless of the relationship you want to configure, we recommend you read every section of this document, in order, at
|
|
least once. This is because you must understand how M2Os work in Directus to understand O2Ms, you must understand M2Os
|
|
and O2Ms to understand M2Ms, etc.
|
|
|
|
:::
|
|
|
|
## Overview
|
|
|
|
The Data Studio makes _the process_ of configuring relational data models easier, faster, and more intuitive by offering
|
|
no-code configuration. Directus _does not_ enforce opinionated schemas, rule systems, or other arbitrary limitations to
|
|
your data models. Therefore, aside from any technical limitations of your project's infrastructure or core requirements
|
|
for any relational data model, _like having a primary key field for every collection or a data type assigned to every
|
|
field,_ you are free to build the data model as you want.
|
|
|
|
In this guide, we will go over the following topics:
|
|
|
|
- What kinds of relationships exist within Directus.
|
|
- How to configure a desired relationship within the Data Studio.
|
|
- How relationships are implemented in the data model and displayed in the Data Studio.
|
|
- When it might be appropriate to use a given type of relationship.
|
|
|
|
By the end, you'll understand everything needed to start building data models in Directus, even if relational data model
|
|
concepts are a new concept to you.
|
|
|
|
### Directus vs Classic Data Model Terms
|
|
|
|
When we use classic data model terms, _such as data table, column, row, etc..._ this signals that the explanation is
|
|
focused strictly on what happens in the database. When Directus terminology is used, _such as collection, field, item,
|
|
etc..._ this signals that the explanation includes Directus logic and functionality.
|
|
|
|
## Many-to-One (M2O)
|
|
|
|
<!-- <video title="Configure an M2O Relationship" autoplay playsinline muted loop controls>
|
|
<source src="" type="video/mp4" />
|
|
</video> -->
|
|
|
|
In an M2O relationship, multiple items from the parent collection are linked to one item in a related collection. For
|
|
example, there are many cities in a country, but a city can only be in one country.
|
|
|
|
To create an M2O relationship, we add a foreign key field to the parent collection, which links items from the parent
|
|
collection to items in the related collection. If we have two tables, `cities` and `countries`, we can create a
|
|
`cities.country_id` foreign key field.
|
|
|
|

|
|
|
|
Let's take a look at the schema.
|
|
|
|
```
|
|
cities
|
|
- id
|
|
- name
|
|
- country_id (a foreign key field, stores a key from countries.id)
|
|
```
|
|
|
|
```
|
|
countries
|
|
- id
|
|
- name
|
|
```
|
|
|
|
Note the following things from the schema above:
|
|
|
|
- An M2O relationship requires just one column within the parent table.
|
|
- When an M2O relational field is configured in Directus, an Item Page on the parent collection will enable access to
|
|
the item from the related collection. So in our example above, an Item Page in `cities` will enable access to the
|
|
related country from the `countries` table.
|
|
|
|
However, in the Directus Data Studio, an M2O field does not automatically provide access to the parent collection's
|
|
items within the related collection. In our example, this means that when you open an Item Page in `countries`, you will
|
|
not see related cities.
|
|
|
|
This is where O2M fields come in to play.
|
|
|
|
:::tip Configure an M2O
|
|
|
|
The easiest way to configure an M2O field is to follow the guide on how to
|
|
[create a field (standard)](/configuration/data-model/fields/#create-a-field-standard) and select the M2O Interface from
|
|
the template wizard.
|
|
|
|
:::
|
|
|
|
## One-to-Many (O2M)
|
|
|
|
Within a relational database, an O2M relationship is the exact same type of relationship as an M2O. Remember, at the end
|
|
of the [M2O](#many-to-one-m2o) section, we learned that configuring an M2O in Directus does not let us access related
|
|
items within an Item Page on the related collection. In Directus, configuring an O2M creates an
|
|
[Alias](/getting-started/glossary#alias) field, which lets us access related items. To demonstrate this, let's continue
|
|
with the `cities` and `countries` example relationship used in the M2O section.
|
|
|
|

|
|
|
|
Let's take a look at the schema.
|
|
|
|
```
|
|
countries
|
|
- id
|
|
- name
|
|
- cities (the O2M alias field, does not exist in the data table. Allows access to all cities linked to a country.)
|
|
```
|
|
|
|
```
|
|
cities
|
|
- id
|
|
- name
|
|
- country_id (the M2O field)
|
|
```
|
|
|
|
Note the following points from the schema above. When we create an O2M in Directus:
|
|
|
|
- Since the perspective is flipped, we now consider `countries` to be the parent collection.
|
|
- It isn't always necessary to create an O2M. In some cases, you won't want or need to access items from both sides.
|
|
- At first glance, this O2M alias field might make it _look and feel_ like a new column was created, but the O2M field
|
|
is purely _virtual_. It creates an Interface within the Data Studio to access items from an O2M perspective. In other
|
|
words, the O2M alias field allows us to access any related items from `cities` within an Item Details Page in the
|
|
`countries` collection.
|
|
|
|
<!-- <video title="Configure an M2O Relationship" autoplay playsinline muted loop controls>
|
|
<source src="" type="video/mp4" />
|
|
</video> -->
|
|
|
|
:::tip Configure an O2M
|
|
|
|
The easiest way to configure an O2M is to follow the guide on how to
|
|
[create a field (standard)](/configuration/data-model/fields/#create-a-field-standard) and select the O2M alias field
|
|
type from the template wizard.
|
|
|
|
:::
|
|
|
|
## One-to-One (O2O)
|
|
|
|
Directus does not include a dedicated One-to-One (O2O) relationship type or Interface. However, in the database, O2O is
|
|
almost exactly the same as an M2O. The only difference is that an O2O enforces _cardinality_. In other words, one item
|
|
from the parent collection can be linked with one item on the related collection, and vice-versa.
|
|
|
|
For example, each country has one capital city, and vice versa. This is an O2O. To demonstrate how it works, let's add
|
|
this O2O to the `cities` and `countries` example relationship used in the previous sections.
|
|
|
|
The first strategy you may think of it to add a new `capital_city` field on the `countries` collection, storing the name
|
|
of the capital city directly. But this would create [duplicate data](/configuration/data-model#avoid-data-duplication),
|
|
because the same city would exist in both `countries.capital_city` as well as `cities.name`. But remember, we want to
|
|
_avoid duplicate data!_
|
|
|
|

|
|
|
|
Instead, we want to use an O2O relationship. Let's try adding a `cities.capital_of` field.
|
|
|
|

|
|
|
|
Let's take a look at the schema.
|
|
|
|
```
|
|
cities
|
|
- id
|
|
- name
|
|
- country_id
|
|
- capital_of (The O2O field. Actually an M2O, configured to store unique values, stores foreign key from countries.id)
|
|
```
|
|
|
|
```
|
|
countries
|
|
- id
|
|
- name
|
|
- cities
|
|
```
|
|
|
|
The O2O relationship in the schema above works, and in some cases it may not matter which collection to configure the
|
|
O2O onto. But in this case it is sub-optimal. Since _most cities_ are not capital cities, the column will mostly contain
|
|
`NULL` values. However, every single country has a capital city. So if we create the O2O on the `countries` collection,
|
|
it will be much more efficient.
|
|
|
|

|
|
|
|
Let's take a look at the schema.
|
|
|
|
```
|
|
countries
|
|
- id
|
|
- name
|
|
- cities
|
|
- capital_city (The O2O field. Actually an M2O, configured to store unique values, stores foreign key from cities.id)
|
|
```
|
|
|
|
```
|
|
cities
|
|
- id
|
|
- name
|
|
- country_id
|
|
```
|
|
|
|
Note the following points from the schema above. When we create an O2O in Directus:
|
|
|
|
- We can add the O2O field on either collection. However, in some cases it is more efficient to add it to a specific
|
|
collection.
|
|
- Since the O2O field is really just an M2O field behind the scenes, and since Directus doesn't automatically display
|
|
M2O fields in the related collection, you may want to [configure an O2M field](#one-to-many-o2m) so that you can
|
|
access items from the related collection as well.
|
|
|
|
:::tip Configure an O2O
|
|
|
|
<!-- <video title="Configure an O2O Relationship" autoplay playsinline muted loop controls>
|
|
<source src="" type="video/mp4" />
|
|
</video> -->
|
|
|
|
The easiest way to configure an O2O is to follow the guide on how to
|
|
[create a field (standard)](/configuration/data-model/fields/#create-a-field-standard) and select the **M2O** field type
|
|
from the template wizard. Then, configure the field's schema, toggling on
|
|
**Unique** so that each value in the M2O field is unique, resulting in an O2O relationship.
|
|
|
|
:::
|
|
|
|
## Many-to-Many (M2M)
|
|
|
|
The relationship types we have seen so far only required one foreign key column to link the parent collection and
|
|
related collection. An M2M relationship is composed _of two foreign key columns_ stored within an additional table,
|
|
called a _junction table_, which stores each linked row between the parent table and related table.
|
|
|
|
Junction tables are required in M2M relationships because the number of relationships created can _(and often will!)_
|
|
outnumber the number of rows in either data table. In other words, if you have `x` rows in the parent column and `y`
|
|
rows in the related column, you need room to store up to `x * y` rows. Junction tables provide a place to store all the
|
|
relationships between rows, no matter how many exist.
|
|
|
|
To demonstrate this, let's think about the relationship between recipes and ingredients: a _recipe_ can have many
|
|
_ingredients_, and _ingredients_ can be in many _recipes_.
|
|
|
|

|
|
|
|
Let's take a look at the schema.
|
|
|
|
```
|
|
recipes
|
|
- id
|
|
- name
|
|
- ingredients (An M2M alias field. Does not exist in the database, allows access to ingredients linked from recipe_ingredients)
|
|
```
|
|
|
|
```
|
|
recipes_ingredients (junction collection)
|
|
- id
|
|
- recipe (stores foreign key from recipes.id)
|
|
- ingredient (stores foreign key from ingredients.id)
|
|
- quantity (A "context" field. Stores other data associated with the relationship. These are optional.)
|
|
```
|
|
|
|
```
|
|
ingredients
|
|
- id
|
|
- name
|
|
```
|
|
|
|
Note the following points from the schema above. When we create an M2M in Directus:
|
|
|
|
- Our junction collection, `recipe_ingredients`, each row contains two foreign key columns. This is what creates the
|
|
relationships between the two tables.
|
|
- Assuming the M2M alias field is created within the `recipes` collection, Directus does not automatically add a field
|
|
to display recipes within the `ingredients` collection. However, you can configure an alias field within `ingredients`
|
|
if desired:
|
|
|
|
```
|
|
ingredients
|
|
- id
|
|
- name
|
|
- recipes (an O2M alias field, does not exist in the database, enables access to all the recipes related to an ingredient)
|
|
```
|
|
|
|
- Notice that the junction collection also has a `quantity` field, which tracks how much of each ingredient is needed
|
|
for the recipe. This is called a _contextual field_. The Data Studio provides full access to the junction collection,
|
|
so you can add any number of contextual fields needed to the junction collection.
|
|
- You can also have a self-referencing M2M relationship that connects items in the _same collection_. One example is
|
|
"Related Articles", where each article relates to many other articles.
|
|
|
|
:::tip Configure an M2M
|
|
|
|
<!-- <video title="Configure an O2O Relationship" autoplay playsinline muted loop controls>
|
|
<source src="" type="video/mp4" />
|
|
</video> -->
|
|
|
|
The easiest way to configure an M2M is to follow the guide on how to
|
|
[create a field (standard)](/configuration/data-model/fields/#create-a-field-standard) and select **Many to Many** from
|
|
the template wizard.
|
|
|
|
:::
|
|
|
|
## Many-to-Any (M2A)
|
|
|
|
Sometimes called a _matrix field_ or _replicator_, an M2A relationship allows you to link items from the parent
|
|
collection to any item in any collection in the database. When you configure an M2A in Directus, an M2A
|
|
[Alias](/getting-started/glossary#alias) field is created as well as a junction collection, like we saw on M2M
|
|
relationships. The difference is that the junction collection on an M2A also has a field to store the **collection key**
|
|
_(the name of the collection)_ for related collections.
|
|
|
|
One common example of when M2As are used is for _page builders_, which have a `pages` collection that combines multiple
|
|
collections for each type of page section, such as `heading`, `text_bodies`, `image`, `video`, _etc_.
|
|
|
|

|
|
|
|
Let's take a look at the schema:
|
|
|
|
```
|
|
pages
|
|
- id
|
|
- name
|
|
- sections (An M2A alias field, does not exist in the database. Provides access to items from page_sections.)
|
|
```
|
|
|
|
```
|
|
page_sections (junction collection)
|
|
- id
|
|
- pages_id (An M2O, stores foreign keys from pages.id)
|
|
- collection (An M2O, stores name of the related collection, for example headings, text_bodies, or images.)
|
|
- item (An M2O, stores foreign keys from headings.id, text.id, images.id, etc.)
|
|
```
|
|
|
|
```
|
|
headings
|
|
- id
|
|
- title
|
|
```
|
|
|
|
```
|
|
text_bodies
|
|
- id
|
|
- text
|
|
```
|
|
|
|
```
|
|
images
|
|
- id
|
|
- file
|
|
```
|
|
|
|
Note the following points from the schema above. When we create an M2A in Directus:
|
|
|
|
- Compared to the M2O and M2M relationships, there may be a lower likelihood that you will need to configure alias
|
|
fields on related collections, such as `headings`, `text_bodies` and `images`, as these collections may not be as
|
|
useful without the parent collection.
|
|
- Each collection has a unique collection name, so this serves as an adequate foreign key in the
|
|
`page_sections.collection` field.
|
|
|
|
:::tip Configure an M2A
|
|
|
|
<!--
|
|
<video title="Configure an M2A Relationship" autoplay playsinline muted loop controls>
|
|
<source src="" type="video/mp4" />
|
|
</video>
|
|
-->
|
|
|
|
The easiest way to configure an M2A is to follow the guide on how to
|
|
[create a field (standard)](/configuration/data-model/fields/#create-a-field-standard) and select the **Many to Any**
|
|
Interface from the template wizard.
|
|
|
|
:::
|
|
|
|
## Translations (O2M)
|
|
|
|
Directus provides this special relational Interface designed specifically to handle translations. When you create a
|
|
Translations O2M in the Data Studio, the following things happen within your data model. A Translations O2M alias field
|
|
is created. A junction collection and a `languages` collection are created. All your translations are stored within
|
|
context fields, configured by you, on the junction collection. Therefore, when you create a Translations O2M, you also
|
|
create an M2M relationship behind the scenes. So remember, it is called the Translations O2M because we interact with
|
|
the Translations O2M alias field. But behind the scenes, it is powered by an M2M.
|
|
|
|
To demonstrate, let's create a Translations O2M relationship for `articles`, a common content type that you may want to
|
|
translate.
|
|
|
|

|
|
|
|
Let's take a look at the schema.
|
|
|
|
```
|
|
articles
|
|
- id
|
|
- author (a field that is not translated)
|
|
- date_published (a field that is not translated)
|
|
- translations (A Translations O2M alias field, does not exist within the data table. Allows access to items from article_translations)
|
|
```
|
|
|
|
```
|
|
article_translations
|
|
- id
|
|
- article_id (an M2O, stores foreign key article.id)
|
|
- language_id (an M2O, stores foreign key languages.language_code)
|
|
- title (A context field, created by you. Stores a translation of the Article Title)
|
|
- text (A context field, created by you. Stores a translation of the Article Text)
|
|
```
|
|
|
|
```
|
|
languages
|
|
- language_code (A primary key. A manually typed language code, e.g., "en-US")
|
|
- name (Stores the language name, e.g., "English")
|
|
```
|
|
|
|
Note the following points from the schema above. When we create a Translations O2M:
|
|
|
|
- As demonstrated by `article_translations.title` and `article_translations.text`, any _translated_ fields should be
|
|
added as context fields on the junction collection.
|
|
- You are not bound to use this for translations. You can build your data model as desired. You could create individual
|
|
fields for each translation, such as `title_english`, `title_german`, `title_french`, and so on. However, this is not
|
|
easily extensible and it creates a sub-optimal experience to have every single translation of every field on the item
|
|
details page. The Translations O2M alias field is designed specifically to make the translation process easier.
|
|
- There may come a time when you want to make a pre-existing parent field translatable. To do this, you can
|
|
[duplicate a field](/configuration/data-model/fields/#duplicate-a-field), move it to the translation collection, and
|
|
then delete the parent field. However, be aware that duplicating a field does _not_ duplicate any existing field
|
|
values.
|
|
|
|
:::tip Configure a Translations O2M
|
|
|
|
<!--
|
|
<video title="Configure a Translations O2M" autoplay playsinline muted loop controls>
|
|
<source src="" type="video/mp4" />
|
|
</video>
|
|
-->
|
|
|
|
The easiest way to configure a Translations relationship is to follow the guide on how to
|
|
[create a field (standard)](/configuration/data-model/fields/#create-a-field-standard) and select the **Translations
|
|
O2M** Interface from the template wizard.
|
|
|
|
:::
|