Relations
To define a relationship between two collections, you define a subquery that describes the relationship with RelationMany
, RelationOne
or RelationById
. while RelationOne
and RelationById
are designed for singleton relations and will be directly nested or a sub-object or null
if an applicable entity doesn't exist. Within a relation, either in a where clause or the RelationById
id, parameter, you can reference the current collection's attributes with $
.
RelationMany
A RelationMany
attribute will be in the shape Array<Entity>
. It's designed to model a one-to-many relationship between two collections. If no related entities are found, the attribute will be an empty array.
In this example schema, we are modeling a school, where departments have many classes. The departments
collection has a classes
attribute that is a RelationMany
to the classes
collection.
import { Schema as S } from '@triplit/client';
const schema = S.Collections({
departments: {
schema: S.Schema({
id: S.Id(),
name: S.String(),
}),
relationships: {
classes: S.RelationMany('classes', {
where: [['department_id', '=', '$id']],
}),
},
},
classes: {
schema: S.Schema({
id: S.Id(),
name: S.String(),
level: S.Number(),
building: S.String(),
department_id: S.String(),
}),
},
});
RelationOne
A RelationOne
attribute will be an Entity
or null
. It's designed to model a one-to-one relationship between two collections. The RelationOne
attribute will be the related entity or null
if no related entity is found.
We can update our model of a school, so that a class has a relation to its department.
import { Schema as S } from '@triplit/client';
const schema = S.Collections({
departments: {
schema: S.Schema({
id: S.Id(),
name: S.String(),
}),
relationships: {
classes: S.RelationMany('classes', {
where: [['department_id', '=', '$id']],
}),
},
},
classes: {
schema: S.Schema({
id: S.Id(),
name: S.String(),
level: S.Number(),
building: S.String(),
department_id: S.String(),
}),
relationships: {
department: S.RelationOne('departments', {
where: [['id', '=', '$department_id']],
}),
},
},
});
RelationById
RelationById is a special case of RelationOne
that is used to define a relationship by a foreign key. The RelationById
attribute will be the related entity or null
if no related entity is found.
We can update the previous example to use RelationById
instead of RelationOne
.
import { Schema as S } from '@triplit/client';
const schema = S.Collections({
departments: {
schema: S.Schema({
id: S.Id(),
name: S.String(),
}),
relationships: {
classes: S.RelationMany('classes', {
where: [['department_id', '=', '$id']],
}),
},
},
classes: {
schema: S.Schema({
id: S.Id(),
name: S.String(),
level: S.Number(),
building: S.String(),
department_id: S.String(),
}),
relationships: {
department: S.RelationById('departments', '$department_id'),
},
},
});
Querying collections with relations
By default, queries on collections with relations will not return related data. You can use the include
method to specify which relations you want to include in the query.
const classesQuery = client.query('classes').Include('department');
const departmentsQuery = client.query('departments').Include('classes');
Defining relations with referential variables
You can also define relations ad-hoc in a query using referential variables. This allows you to define relations that are not part of the schema and is equivalent to a JOIN
you might see in SQL. Under the hood, this is actually what your RelationMany
, RelationOne
, and RelationById
attributes are doing. See usage of referential variables in the Variables documentation. For example:
// Fetch all 747 planes that have a flight to an airport newer than the plane
const query = client.query('planes').Where([
['model', '=', '747']
{
exists: client.query('flights').Where([
['plane_id', '=', '$1.id'],
{
exists: client.query('airports').Where([
['id', '=', '$1.destination_id']
['created_at', '>', '$2.created_at'],
]),
},
]),
},
]);