Many-to-many relations

Many-to-many (m-n) relations refer to relations where zero or more records on one side of the relation can be connected to zero or more records on the other side.

Prisma schema syntax and the implementation in the underlying database differs between relational databases and MongoDB.

Relational databases

In relational databases, m-n-relations are typically modelled via relation tables. m-n-relations can be either explicit or implicit in the Prisma schema.

Explicit many-to-many relations

In an explicit many-to-many relation, the relation table is represented as a model in the Prisma schema and can be used in queries. Explicit many-to-many relations define three models:

  • Two models that have a many-to-many relation, such as Category and Post
  • One model that represents the relation table, such as CategoriesOnPosts (also sometimes called JOIN, link or pivot table) in the underlying database

In this example, the model representing the relation table defines additional fields that describe the Post/Category relationship - who assigned the category (assignedBy), and when the category was assigned (assignedAt):

model Post {
id Int @id @default(autoincrement())
title String
categories CategoriesOnPosts[]
}
model Category {
id Int @id @default(autoincrement())
name String
posts CategoriesOnPosts[]
}
model CategoriesOnPosts {
post Post @relation(fields: [postId], references: [id])
postId Int // relation scalar field (used in the `@relation` attribute above)
category Category @relation(fields: [categoryId], references: [id])
categoryId Int // relation scalar field (used in the `@relation` attribute above)
assignedAt DateTime @default(now())
assignedBy String
@@id([postId, categoryId])
}

The underlying SQL looks like this:

CREATE TABLE "Category" (
id SERIAL PRIMARY KEY
);
CREATE TABLE "Post" (
id SERIAL PRIMARY KEY
);
-- Relation table + indexes -------------------------------------------------------
CREATE TABLE "CategoryToPost" (
"categoryId" integer NOT NULL,
"postId" integer NOT NULL,
"assignedBy" text NOT NULL
"assignedAt" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY ("categoryId") REFERENCES "Category"(id),
FOREIGN KEY ("postId") REFERENCES "Post"(id)
);
CREATE UNIQUE INDEX "CategoryToPost_category_post_unique" ON "CategoryToPost"("categoryId" int4_ops,"postId" int4_ops);

Querying an explicit many-to-many

The following section demonstrates how to query an explicit many-to-many relation. You can query the relation model directly (prisma.categoryOnPost(...)), or use nested queries to go from Post -> CategoryOnPost -> Category or the other way.

The following query:

  1. Creates a Post
  2. Creates a category assigment, or CategoryToPost (assigned by Bob, assigned today)
  3. Creates a new Category
const createCategory = await prisma.post.create({
data: {
title: 'How to be Bob',
categories: {
create: [
{
assignedBy: 'Bob',
assignedAt: new Date(),
category: {
create: {
name: 'New category',
},
},
},
],
},
},
})

The following query:

  • Creates a new Post
  • Creates a new category assignment, or CategoryToPost
  • Connects the category assignment to existing categories (with IDs 9 and 22)
const assignCategories = await prisma.post.create({
data: {
title: 'How to be Bob',
categories: {
create: [
{
assignedBy: 'Bob',
assignedAt: new Date(),
category: {
connect: {
id: 9,
},
},
},
{
assignedBy: 'Bob',
assignedAt: new Date(),
category: {
connect: {
id: 22,
},
},
},
],
},
},
})

The following query returns all Post records where at least one (some) category assignment (categories) refers to a category named "New category":

const getPosts = await prisma.post.findMany({
where: {
categories: {
some: {
category: {
name: 'New Category',
},
},
},
},
})

The following query returns all categories where at last one (some) related Post record titles contain the words "Cool stuff" and the category was assigned by Bob.

const getAssignments = await prisma.category.findMany({
where: {
posts: {
some: {
assignedBy: 'Bob',
post: {
title: {
contains: 'Cool stuff',
},
},
},
},
},
})

The following query gets all category assignments (CategoryOnPost) records that were assigned by "Bob" to one of 5 posts:

const getAssignments = await prisma.categoriesOnPosts.findMany({
where: {
assignedBy: 'Bob',
post: {
id: {
in: [9, 4, 10, 12, 22],
},
},
},
})

Implicit many-to-many relations

Implicit many-to-many relations define relation fields as lists on both sides of the relation. Although the relation table exists in the underlying database, it is managed by Prisma and does not manifest in the Prisma schema. Implicit relation tables follow a specific convention.

Implicit m-n relations makes the Prisma Client API for many-to-many relations a bit simpler (since you have one fewer level of nesting inside of nested writes).

In the example below, there's one implicit m-n-relation between Post and Category:

Prisma schema
SQL
model Post {
id Int @id @default(autoincrement())
categories Category[]
}
model Category {
id Int @id @default(autoincrement())
posts Post[]
}

Querying an implicit many-to-many

The following section demonstrates how to query an implicit many-to-many relation. The queries require less nesting than explicit many-to-many queries.

The following query creates a single Post and multiple Category records:

const createPostAndCategory = await prisma.post.create({
data: {
title: 'How to become a butterfly',
categories: {
create: [{ name: 'Magic' }, { name: 'Butterflies' }],
},
},
})

The following query creates a single Category and multiple Post records:

const createCategoryAndPosts = await prisma.category.create({
data: {
name: 'Stories',
posts: {
create: [
{ title: 'That one time with the stuff' },
{ title: 'The story of planet Earth' },
],
},
},
})

The following query returns all Post records with a list of that post's assigned categories:

const getPostsAndCategories = await prisma.post.findMany({
include: {
categories: true,
},
})

Rules for defining an implicit m-n relation

Implicit m-n relations:

Conventions for relation tables in implicit m-n-relations

If you obtain your data model from introspection, you can still use implicit many-to-many relations by following Prisma's conventions for relation tables. The following example assumes you want to create a relation table to get an implicit many-to-many relation for two models called Post and Category.

Table name

The name of the relation table must be prefixed with an underscore:

  • Valid: _CategoryToPost, _MyRelation
  • Invalid: CategoryToPost, MyRelation
Columns

A relation table for an implicit-many-to-many relation must have exactly two columns:

  • A foreign key column that points to Category called A
  • A foreign key column that points to Post called B

The columns must be called A and B where A points to the model that comes first in the alphabet and B points to the model which comes last in the alphabet.

Indexes

There further must be:

  • A unique index defined on both foreign key columns:
CREATE UNIQUE INDEX "_CategoryToPost_AB_unique" ON "_CategoryToPost"("A" int4_ops,"B" int4_ops);
  • A non-unique index defined on B:
CREATE INDEX "_CategoryToPost_B_index" ON "_CategoryToPost"("B" int4_ops);
Example

This is a sample SQL statement that would create the three tables including indexes (in PostgreSQL dialect):

CREATE TABLE "_CategoryToPost" (
"A" integer NOT NULL REFERENCES "Category"(id) ,
"B" integer NOT NULL REFERENCES "Post"(id)
);
CREATE UNIQUE INDEX "_CategoryToPost_AB_unique" ON "_CategoryToPost"("A" int4_ops,"B" int4_ops);
CREATE INDEX "_CategoryToPost_B_index" ON "_CategoryToPost"("B" int4_ops);
CREATE TABLE "Category" (
id integer SERIAL PRIMARY KEY
);
CREATE TABLE "Post" (
id integer SERIAL PRIMARY KEY
);

Configuring the name of the relation table in implicit many-to-many relations

When using Prisma Migrate, you can configure the name of the relation table that's managed by Prisma using the @relation attribute. The only requirement is that it starts with an underscore. For example, if you want the relation table to be called _MyRelationTable instead of the default name _CategoryToPost, you can specify it as follows:

model Post {
id Int @id @default(autoincrement())
categories Category[] @relation("_MyRelationTable")
}
model Category {
id Int @id @default(autoincrement())
posts Post[] @relation("_MyRelationTable")
}

Relation tables

A relation table (also sometimes called JOIN, link or pivot table) connects two or more other tables and therefore creates a relation between them. Creating relation tables is a common data modeling practice in SQL to represent relationships between different entities. In essence it means that "one m-n relation is modeled as two 1-n relations in the database".

When using Prisma, you can create relation tables by defining models similar to how you would model them as tables. The main difference is that the fields of the relation table are both annotated relation fields with a corresponding relation scalar field.

Relation tables are also often used to add "meta-information" to a relation. For example, to store when the relation was created.

Here is an example for a relation table called CategoriesOnPosts:

model Post {
id Int @id @default(autoincrement())
title String
categories CategoriesOnPosts[]
}
model Category {
id Int @id @default(autoincrement())
name String
posts CategoriesOnPosts[]
}
model CategoriesOnPosts {
post Post @relation(fields: [postId], references: [id])
postId Int
category Category @relation(fields: [categoryId], references: [id])
categoryId Int
assignedAt DateTime @default(now())
@@id([postId, categoryId])
}

In this example, the assignedAt field stores additional information about the relation between Post and Category:

  • assignedAt stores when the post was added to the category
  • assignedBy store who added the post to the category

Note that the same rules as for 1-n-relations apply (because PostCategoriesOnPosts and CategoryCategoriesOnPosts are both in fact 1-n-relations), which means one side of the relation needs to be annotated with the @relation attribute.

When you don't need to attach additional information to the relation, you can model m-n-relations as implicit many-to-many relations. If you're not using Prisma Migrate but obtain your data model from introspection, you can still make use of implicit many-to-many relations by following Prisma's conventions for relation tables.

MongoDB

In MongoDB, m-n-relations are represented by:

  • Relation fields on both sides, include a @relation attribute on each side
  • A scalar list of referenced IDs on each side, including the @db.Array(X) attribute, where X is the ID type (ObjectId, String)

The following example demonstrate an m-n relation between posts and categories:

model Post {
id String @id @default(dbgenerated()) @map("_id") @db.ObjectId
categoryIDs String[] @db.Array(ObjectId)
categories Category[] @relation(fields: [categoryIDs])
}
model Category {
id String @id @default(dbgenerated()) @map("_id") @db.ObjectId
postIDs String[] @db.Array(ObjectId)
posts Post[] @relation(fields: [postIDs])
}

Querying MongoDB m-n relations

const newId1 = new ObjectId()
const newId2 = new ObjectId()
const posts = await prisma.post.findMany({
where: {
categoryIDs: {
hasSome: [newId1.toHexString(), newId2.toHexString()],
},
},
})
const posts = await prisma.post.findMany({
where: {
categories: {
some: {
name: {
contains: 'Servers',
},
},
},
},
})
Edit this page on GitHub