查询连接和嵌套表
数据 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:
管字符串部分
id | name |
|---|---|
| 1 | 字符串 |
| 2 | 木管乐器 |
乐器
id | name | section_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 sectionsWithInstrumentsQueryif (error) throw errorconst 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
左连接(默认) #
🌐 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:
-- Employeescreate table users ( "id" serial primary key, "name" text);-- Badge scanscreate table scans ( "id" serial primary key, "user_id" int references users, "badge_scan_time" timestamp);-- Work shiftscreate 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 ) `)