连接字段

Join 字段用于在相反方向展示 Relationship 和 Upload 字段的关系。通过 Join 字段,你可以:

  • 编辑和查看引用了特定集合文档的其他集合
  • 该字段本身是一个虚拟字段,不会在集合中存储新数据
  • 在 Admin UI 中展示相关文档以提供更好的编辑体验
  • 通过 Payload 的 API 暴露这些关系

Join 字段在以下场景中非常有用:

  • 展示某个 Product 的所有 Orders
  • 查看和编辑属于某个 CategoryPosts
  • 处理任何双向关系数据
  • 显示文档或上传文件在其他文档中的使用情况
展示 Payload 管理面板中的 Join 字段
Join 字段在管理面板中的截图

要使 Join 字段正常工作,你必须在要关联的集合中已存在 relationshipupload 字段。这将引用相关文档的集合和字段路径。

要添加 Join 字段,在你的 Field Config 中将 type 设置为 join

import type { Field } from 'payload'

export const MyJoinField: Field = {
  // highlight-start
  name: 'relatedPosts',
  type: 'join',
  collection: 'posts',
  on: 'category',
  // highlight-end
}

// 另一个集合中的 relationship 字段:
export const MyRelationshipField: Field = {
  name: 'category',
  type: 'relationship',
  relationTo: 'categories',
}

在这个例子中,该字段被定义为当添加到 category 集合时显示相关的 postson 属性用于指定与集合文档关联的字段名称。

使用这个例子,如果你在 Admin UI 或 API 响应中导航到一个 Category,现在你会看到与该 Category 相关的 Posts 被自动填充。这非常强大,可以用来以简单的方式定义各种关系类型。

Join 字段性能极高,在添加深度为 1 或更高之前不会给 API 响应增加额外的查询开销。它适用于所有数据库适配器。在 MongoDB 中,我们使用 聚合 来自动关联相关文档,而在关系型数据库中,我们使用 join 操作。

Join 字段在 DocumentDBAzure Cosmos DB 中不受支持,因为我们内部使用 MongoDB 聚合来查询该字段的数据,而这些数据库对此有限制。这一点未来可能会改变。

数据库建模建议

在设计数据库结构时,你可能会遇到许多需要建立双向关联的场景。但这里有一个重要原则——通常你只需要在一个地方存储关联关系信息。

以文章(Posts)和分类(Categories)为例,在编辑文章时定义它所属的分类是合理的做法。

但通常没有必要同时在分类中存储文章ID列表,原因如下:

  • 你希望关联关系有"单一数据源",而不需要担心保持两个数据源的同步
  • 如果文章数量达到数百、数千甚至数百万条,你不会希望在一个分类中存储所有这些文章的ID
  • 等等

这正是 join 字段的强大之处。通过它,你只需要在 post 上存储 category_id,当查询分类时,Payload 会自动为你关联相关文章。关联的分类信息仅存储在文章本身,而不会在两边重复存储。然而,join 字段为你提供了双向API和UI的能力。

使用 Join 字段完全掌控数据库架构

对于典型的多态/多对多关系,如果你使用 Postgres 或 SQLite,Payload 会自动创建一个 posts_rels 表作为连接表来存储文档的所有关系。

但如果你希望对数据库架构有更多控制权,这种默认方式可能并不适合你的使用场景。你可能不希望使用 _rels 表,而是更倾向于维护和控制自己的连接表设计。

通过 Join 字段,你可以控制自己的连接表设计,避免 Payload 自动创建 _rels 表。

join 字段可以与_任何_ collection 结合使用。如果你想定义自己的"连接" collection(例如名为 categories_posts,包含 post_idcategory_id 列),你可以完全控制该连接表的结构。

你还可以更进一步,利用 categories_posts collection 的 admin.hidden 属性,将该 collection 从 Admin UI 导航中隐藏。

在关系字段上指定额外字段

join 字段另一个非常强大的用途是能够在关系上定义"上下文"字段。假设你有 Posts(文章)和 Categories(分类),并且在 Posts 和 Categories 集合中都使用了 join 字段来关联一个新的伪连接集合 categories_posts。现在,这些关系存储在这个第三方连接集合中,并且可以同时在 Posts 和 Categories 上展示。但更重要的是,你可以为这个共享的连接集合添加额外的"上下文"字段。

例如,在这个 categories_posts 集合中,除了拥有 categorypost 字段外,我们还可以添加自定义的"上下文"字段,如 featured(精选)或 spotlight(焦点),这允许你直接在关系上存储额外信息。join 字段让你能够完全控制 Payload 中的任何类型的关系架构,所有这些都封装在一个强大的 Admin UI 中。

配置选项

选项描述
name *作为从数据库检索时使用的属性名称。了解更多
collection *包含关系字段的集合 slug 或集合 slug 数组。
on *与集合文档相关联的关系或上传字段名称。对于嵌套路径使用点表示法,如 'myGroup.relationName'。如果 collection 是数组,则此字段必须存在于所有指定集合中
orderable如果为 true,启用自定义排序,可以通过拖放重新排序关联文档。使用分数索引实现高效重新排序。
where用于隐藏相关文档的 Where 查询条件。将与请求中指定的任何 where 条件合并。
maxDepth默认为 1,设置此字段的最大填充深度,无论到达此字段时剩余的深度如何。最大深度
label在管理面板中用作字段标签的文本,或包含每种语言键的对象。
hooks提供字段钩子来控制此字段的逻辑。更多详情
access提供字段访问控制,指定用户可以对此字段数据执行的操作。更多详情
defaultLimit返回的文档数量。设置为 0 可返回所有相关文档。
defaultSort用于指定返回关联文档顺序的字段名称。
admin管理界面特定配置。更多详情
custom用于添加自定义数据(例如插件)的扩展点。
typescriptSchema通过提供 JSON schema 来覆盖字段类型生成。
graphQL字段的自定义 graphQL 配置。更多详情

* 星号表示该属性为必填项。

管理配置选项

你可以使用 admin 配置属性来控制 join 字段的用户体验。支持以下选项:

选项描述
defaultColumns字段名称数组,对应关系表中要显示的列。默认为 collection 配置。
allowCreate设置为 false 可移除从此字段创建新相关文档的控件。
components.Label覆盖 Field 组件的默认 Label。更多详情

Join 字段数据

当返回包含 join 字段且已填充相关文档的文档时,返回的结构是一个包含以下属性的对象:

  • docs 相关文档数组,如果达到深度限制则仅包含 ID
  • hasNextPage 布尔值,表示是否有更多文档
  • totalDocs 文档总数,仅在 join 查询中传递 count: true 时存在
{
  "id": "66e3431a3f23e684075aae9c",
  "relatedPosts": {
    "docs": [
      {
        "id": "66e3431a3f23e684075aaeb9",
        // 其他字段...
        "category": "66e3431a3f23e684075aae9c"
      }
      // { ... }
    ],
    "hasNextPage": false,
    "totalDocs": 10 // 如果传递了 count: true
  }
  // 其他字段...
}

Join 字段数据(多态)

当一个文档返回时,对于多态 Join 字段(collection 为数组的情况),会填充相关文档。返回的结构是一个包含以下属性的对象:

  • docs 一个数组,包含 relationTo(文档所属的 collection slug)和 value(文档本身,如果达到深度限制则返回 ID)
  • hasNextPage 布尔值,表示是否有更多文档
  • totalDocs 文档总数,仅在 join 查询中传递了 count: true 时存在
{
  "id": "66e3431a3f23e684075aae9c",
  "relatedPosts": {
    "docs": [
      {
        "relationTo": "posts",
        "value": {
          "id": "66e3431a3f23e684075aaeb9",
          // 其他字段...
          "category": "66e3431a3f23e684075aae9c"
        }
      }
      // { ... }
    ],
    "hasNextPage": false,
    "totalDocs": 10 // 如果传递了 count: true
  }
  // 其他字段...
}

查询选项

Join Field 支持自定义查询来筛选、排序和限制返回的相关文档。除了每个 Join Field 特有的查询选项外,你还可以传递 joins: false 来禁用所有 Join Field 的返回。这在不需要相关文档时有助于提升性能。

支持以下查询选项:

PropertyDescription
limit返回的相关文档最大数量,默认为 10。
where可选的 Where 查询,用于筛选关联文档。将与字段的 where 对象合并。
sort用于排序相关结果的字符串
count是否包含相关文档的计数。默认不包含

这些选项可应用于 Local API、GraphQL 和 REST API。

Local API

通过在 Local API 中添加 joins,你可以按字段的 name 为每个 join field 自定义请求。

const result = await payload.find({
  collection: 'categories',
  where: {
    title: {
      equals: 'My Category',
    },
  },
  joins: {
    relatedPosts: {
      limit: 5,
      where: {
        title: {
          equals: 'My Post',
        },
      },
      sort: 'title',
    },
  },
})

目前,对于包含 collection 数组的 join fields,其关联文档的 Where 查询支持有限,且不支持数组和 blocks 内部的字段。

REST API

REST API 支持与 Local API 相同的查询选项。你可以使用 joins 查询参数,通过字段的 name 为每个关联字段自定义请求。例如,一个获取文档并限制关联文章为5条且按标题排序的 API 调用:

/api/categories/${id}?joins[relatedPosts][limit]=5&joins[relatedPosts][sort]=title

你可以在单个请求中为相同或不同的关联字段指定任意数量的 joins 参数。

GraphQL

GraphQL API 支持与本地 API 和 REST API 相同的查询选项。你可以在查询中为每个关联字段指定查询选项。

示例:

query {
  Categories {
    docs {
      relatedPosts(
        sort: "createdAt"
        limit: 5
        where: { author: { equals: "66e3431a3f23e684075aaeb9" } }
      ) {
        docs {
          title
        }
        hasNextPage
      }
    }
  }
}