Skip to content
Database

查询连接和嵌套表

数据 API 会自动检测 Postgres 表之间的关系。因为 Postgres 是关系型数据库,这种情况很常见。

🌐 The data APIs automatically detect relationships between Postgres tables. Since Postgres is a relational database, this is a very common scenario.

一对多连接 #

🌐 One-to-many joins

使用一个存储 orchestral_sections 和 instruments 的示例数据库:

🌐 Use an example database that stores orchestral_sections and instruments:

管字符串部分

idname
1字符串
2木管乐器

乐器

idnamesection_id
1小提琴1
2中提琴1
3长笛2
4双簧管2

这些 API 会根据外键自动检测关系:

🌐 The APIs will automatically detect relationships based on the foreign keys:

const { data, error } = await supabase.from('orchestral_sections').select(`
id,
name,
instruments ( id, name )
`)

TypeScript 的连接类型 #

🌐 TypeScript types for joins

supabase-js 总是返回一个 data 对象(用于成功),以及一个 error 对象(用于请求失败)。

这些辅助类型提供了任何查询的结果类型,包括数据库连接的嵌套类型。

🌐 These helper types provide the result types from any query, including nested types for database joins.

给定以下关于管字符串各乐器组与乐器之间关系的架构:

🌐 Given the following schema with a relation between orchestral sections and instruments:

create table orchestral_sections (
"id" serial primary key,
"name" text
);
create table instruments (
"id" serial primary key,
"name" text,
"section_id" int references "orchestral_sections"
);

我们可以这样获取嵌套的 SectionsWithInstruments 类型:

🌐 We can get the nested SectionsWithInstruments type like this:

import { QueryData, QueryError, QueryResult } from '@supabase/supabase-js'
const sectionsWithInstrumentsQuery = supabase.from('orchestral_sections').select(`
id,
name,
instruments (
id,
name
)
`)
type SectionsWithInstruments = QueryData<typeof sectionsWithInstrumentsQuery>
const { data, error } = await sectionsWithInstrumentsQuery
if (error) throw error
const sectionsWithInstruments: SectionsWithInstruments = data

连接类型和连接修饰符 #

🌐 Join types and join modifiers

默认情况下,嵌入关系使用父表的左连接语义:

🌐 By default, embedded relations use left join semantics from the parent table:

  • 即使没有相关的行匹配,也会返回父行。
  • 当没有匹配时,嵌入关系在一对多连接中是 [],在多对一连接中是 null。

要过滤掉与相关表不匹配的父行,可以在嵌入的关联上使用 !inner。

🌐 To filter out parent rows that do not match the related table, use !inner on the embedded relation.

: 和 !#

🌐 What : and ! mean in join syntax

语法含义示例
alias:relation(columns)重命名响应中的嵌入关系。start_scan:scans(id, badge_scan_time)
relation!inner(columns)对该嵌入关系使用 inner join 行为。instruments!inner(id, name)
relation!foreign_key(columns)当多个外键匹配连接时,选择要使用的外键关系。scans!scan_id_start(id)

连接类型的示例数据 #

🌐 Example data for join types

管字符串部分 #

🌐 Orchestral sections

idname
1字符串
2管乐
3打击乐

乐器 #

🌐 Instruments

idnamesection_id
1小提琴1
2中提琴1
3长笛2
4双簧管2

左连接(默认) #

🌐 Left join (default)

这个查询会对一个关联字段(instruments.name)进行过滤,但仍然返回所有父行:

🌐 This query filters on a joined field (instruments.name) but still returns all parent rows:

const { data, error } = await supabase
.from('orchestral_sections')
.select(
`
id,
name,
instruments ( id, name )
`
)
.eq('instruments.name', 'flute')

结果 #

🌐 Result

[
{
"id": 1,
"name": "strings",
"instruments": []
},
{
"id": 2,
"name": "woodwinds",
"instruments": [{ "id": 3, "name": "flute" }]
},
{
"id": 3,
"name": "percussion",
"instruments": []
}
]

内连接 (!inner#

🌐 Inner join (!inner)

添加 !inner 会过滤掉不匹配连接过滤器的父行:

🌐 Adding !inner filters out parent rows that don't match the joined filter:

const { data, error } = await supabase
.from('orchestral_sections')
.select(
`
id,
name,
instruments!inner ( id, name )
`
)
.eq('instruments.name', 'flute')

结果 #

🌐 Result

[
{
"id": 2,
"name": "woodwinds",
"instruments": [{ "id": 3, "name": "flute" }]
}
]

使用连接字段进行过滤 #

🌐 Filtering using joined fields

在过滤器中使用 joined_table.column(例如 eq、neq 和 in):

🌐 Use joined_table.column in filters (for example eq, neq, and in):

const { data, error } = await supabase
.from('instruments')
.select(
`
id,
name,
orchestral_sections!inner ( id, name )
`
)
.eq('orchestral_sections.name', 'woodwinds')

结果 #

🌐 Result

[
{
"id": 3,
"name": "flute",
"orchestral_sections": {
"id": 2,
"name": "woodwinds"
}
},
{
"id": 4,
"name": "oboe",
"orchestral_sections": {
"id": 2,
"name": "woodwinds"
}
}
]

多对多连接 #

🌐 Many-to-many joins

数据 API 会检测多对多的连接。例如,如果你有一个数据库存储了用户的团队(每个用户可以属于多个团队):

🌐 The data APIs will detect many-to-many joins. For example, if you have a database which stored teams of users (where each user could belong to many teams):

create table users (
"id" serial primary key,
"name" text
);
create table teams (
"id" serial primary key,
"team_name" text
);
create table members (
"user_id" int references users,
"team_id" int references teams,
primary key (user_id, team_id)
);

在这些情况下,你不需要显式地定义连接表(members)。如果我们想获取所有的团队以及每个团队的成员:

🌐 In these cases you don't need to explicitly define the joining table (members). If we wanted to fetch all the teams and the members in each team:

const { data, error } = await supabase.from('teams').select(`
id,
team_name,
users ( id, name )
`)

为有多个外键的连接指定 ON#

🌐 Specifying the ON clause for joins with multiple foreign keys

例如,如果你有一个项目用于跟踪员工上下班打卡时间:

🌐 For example, if you have a project that tracks when employees check in and out of work shifts:

-- Employees
create table users (
"id" serial primary key,
"name" text
);
-- Badge scans
create table scans (
"id" serial primary key,
"user_id" int references users,
"badge_scan_time" timestamp
);
-- Work shifts
create table shifts (
"id" serial primary key,
"user_id" int references users,
"scan_id_start" int references scans, -- clocking in
"scan_id_end" int references scans, -- clocking out
"attendance_status" text
);

在这种情况下,你需要明确指定连接,因为 shifts 上的连接列有歧义,两个列都引用了 scans 表。

🌐 In this case, you need to explicitly define the join because the joining column on shifts is ambiguous as they are both referencing the scans table.

要获取与特定 scan 相关的所有带有 scan_id_start 和 scan_id_end 的 shifts,可以使用以下语法:

🌐 To fetch all the shifts with scan_id_start and scan_id_end related to a specific scan, use the following syntax:

const { data, error } = await supabase.from('shifts').select(
`
*,
start_scan:scans!scan_id_start (
id,
user_id,
badge_scan_time
),
end_scan:scans!scan_id_end (
id,
user_id,
badge_scan_time
)
`
)