关系基础:一对多与一对一
本教程共 54 篇 · 第 11 篇 · 更新于 2026-08-11 · 约 5 分钟阅读
本节目标:区分关系字段与外键标量字段,用
@relation定义一对多和一对一关系。
应用里的数据几乎从不孤立存在:用户发布文章,文章有作者。关系(relation)就是模型之间的这种连接。在数据库层面,关系靠外键实现;在 Prisma 层面,关系由成对出现的关系字段描述。
关系字段 vs 外键
看这个一对多例子,User 和 Post 各有一个关系字段:
model User {
id Int @id @default(autoincrement())
posts Post[] // 关系字段(列表侧)
}
model Post {
id Int @id @default(autoincrement())
author User @relation(fields: [authorId], references: [id]) // 关系字段(带注解)
authorId Int // 关系标量字段:真正的外键
title String
}
两个概念要分清:
- 关系字段(
posts、author):类型是另一个模型,只存在于 Prisma 层,数据库里没有对应列。 - 关系标量字段(
authorId):真正的外键列,存在数据库里。
@relation(fields: [authorId], references: [id]) 把两者串起来:fields 是本模型的外键字段,references 是被引用模型上的字段(通常是主键)。命名约定是「关系字段名 + Id」:author 配 authorId。外键字段的类型必须和被引用字段一致,Int 对 Int,String 对 String。
一条关系恰好由两个关系字段组成,两边各一个。每个关系必须成对出现,漏了任何一侧,prisma validate 都会报错。
一对多:列表侧 + 外键侧
一对多表达「一个用户有多篇文章」。判断方法:哪一侧能有多条记录,哪一侧就是列表。posts Post[] 在 User 上,所以是「一」;author 在 Post 上,所以是「多」。
一对多可以必填,也可以可选。可选的 1-n 允许文章没有作者:
model Post {
id Int @id @default(autoincrement())
author User? @relation(fields: [authorId], references: [id])
authorId Int? // 外键可空
}
关系字段和外键标量必须同时加 ? 或同时不加,否则校验报错。必填版(User + authorId Int)则强制每篇文章都有作者,创建时要么嵌套创建作者,要么用 connect 接上已有作者。
在数据库层面,外键就是普通列加约束。迁移生成的 SQL 大致长这样:
ALTER TABLE "Post" ADD FOREIGN KEY ("authorId") REFERENCES "User"("id");
理解这张底牌,后面看迁移文件就不慌。
一对一:@unique 决定一切
一对一表达「一个用户只有一个资料页」。关键差异在数据库层:外键加 @unique 就是 1-1,不加就是 1-n。唯一约束保证同一个用户不会被两个资料页引用。
model User {
id Int @id @default(autoincrement())
profile Profile?
}
model Profile {
id Int @id @default(autoincrement())
bio String
user User @relation(fields: [userId], references: [id])
userId Int @unique // 外键 + 唯一约束 → 一对一
}
两条规则要记住:
- 没有外键的那一侧必须可选。User 侧的
profile Profile?写死不能去掉——User 表里没有外键列,无法强制「必须有资料页」。 - 外键放哪边都可以。上例放 Profile 侧;反过来放 User 侧(
profileId Int? @unique)同样合法。选择标准:哪个实体「必须依附」另一个,就把外键放哪边。
必填的 1-1(创建 User 必须同时给 Profile)也能建模,把外键侧写成 profile Profile @relation(...) + profileId Int @unique 即可,代价是创建 User 时 profile 不能缺席。
Note
@relation不只是 1-1/1-n 的必需品。同一对模型之间有多条关系(比如「作者」和「置顶」都是 User 与 Post 的关系)、自关联(用户关注用户)时,都需要@relation("名字")来区分。这些进阶形态在第 13 章展开;引用操作的默认值见第 14 章。
Note也可以引用非主键字段,只要被引用字段带
@unique。比如用id做关系锚点:@relation(fields: [authorEmail], references: [email])。少见但合法。
多字段关系
被引用方是复合主键时,fields 和 references 都要写成数组,一一对应:
model User {
firstName String
lastName String
posts Post[]
@@id([firstName, lastName])
}
model Post {
id Int @id @default(autoincrement())
author User @relation(fields: [authorFirstName, authorLastName], references: [firstName, lastName])
authorFirstName String
authorLastName String
}
字段数量、顺序、类型必须完全匹配。多字段关系仅关系型数据库支持。
关系能做什么
定义好关系,Prisma Client 立刻获得三种能力:创建时嵌套写入(posts: { create: [...] })、查询时用 include 带上关联数据、以及按关系条件过滤(where: { posts: { some: { published: true } } })。先看 include 的直观效果:
const user = await prisma.user.findUnique({
where: { id: 1 },
include: { posts: true }, // 把关联文章一起查出来
});
返回结果里多出 posts 数组,一次请求拿到用户和全部文章。嵌套写入、关系过滤的完整用法,本教程第三部分会逐个展开,这里先记住:关系字段就是通往关联数据的入口。
参考来源
- Prisma 官方文档:Relations、One-to-many relations、One-to-one relations