Supabase 数据关联查询
前面学的都是单表操作,真实产品几乎都是多张表配合使用。这一篇学习用外键建模数据关系,再用一行代码完成多表联查。
为什么要多张表
设想一个博客,如果所有数据放一张表,文章表里塞作者昵称和头像,会出现两个问题
- 作者改昵称后,历史文章里的作者信息不会跟着变
- 数据重复存储,白白浪费存储空间
正确做法是分开两张表,文章只存作者 ID,查文章时再根据 ID 关联出作者信息。这就是关系型数据库最擅长的场景。
外键
外键(Foreign Key)是连接两张表的桥梁。给 posts 表加一个 user_id 列,声明它引用 profiles 表的 id 列
create table public.posts (
id bigint generated always as identity primary key,
title text not null,
content text,
user_id uuid references public.profiles (id)
);
| 列 | 说明 |
|---|---|
| id | 自增主键,bigint 类型适合业务表 |
| title | 文章标题 |
| content | 文章内容 |
| user_id | 作者,外键指向 profiles.id |
references 关键字就是外键声明,它告诉数据库,这一列的值必须来自另一张表里真实存在的 ID。传一个不存在的作者 ID 会被拒绝,数据库从源头保证数据不乱。
插入测试数据
插入两篇文章,分别指向两个测试用户
insert into posts (title, content, user_id)
values
('第一篇文章', '内容一', '11111111-1111-1111-1111-111111111111'),
('第二篇文章', '内容二', '22222222-2222-2222-2222-222222222222');
一次请求拿全部信息
关键操作来了。查询文章时,在 select 里用括号写上关联表,Supabase 会把作者信息一起返回
const { data, error } = await supabase
.from('posts')
.select('*, author:profiles(*)')
console.log(data)
返回结果里,每篇文章多了一个 author 字段,里面是完整的作者对象
[
{
id: 1,
title: '第一篇文章',
user_id: '11111111-1111-1111-1111-111111111111',
author: {
id: '11111111-1111-1111-1111-111111111111',
username: '小明',
age: 18
}
}
]
语法拆解
‘, author:profiles()’ 由三部分组成
| 部分 | 含义 |
|---|---|
| * | 取文章全部字段 |
| author: | 给关联结果起个别名,返回里叫 author 而不是 profiles |
| profiles(*) | 关联 profiles 表并取全部字段 |
不写别名也可以,直接写 profiles(*),返回的字段名就是表名。起别名是为了让数据结构更符合业务直觉。
Supabase 是通过外键自动发现关联关系的,所以建表时把外键写对,前端联查就不用额外配置。
只取关联表的某些字段
// 只要作者昵称
const { data } = await supabase
.from('posts')
.select('title, author:profiles(username)')
一对多和多对一
| 关系 | 例子 | 建模方式 |
|---|---|---|
| 多对一 | 多篇文章属于一个作者 | 文章表存作者 ID |
| 一对多 | 一个作者有多篇文章 | 同一张表反着查 |
| 多对多 | 一篇文章有多个标签 | 中间表存两个 ID |
本教程的场景是典型的多对一。一对多查询,比如查某作者的全部文章,本质就是文章表按作者过滤
const { data } = await supabase
.from('posts')
.select('*')
.eq('user_id', '11111111-1111-1111-1111-111111111111')
关联查询与 RLS
关联查询同样受 RLS 约束,联合策略对两张表各自生效。如果用户没有权限读作者那行,author 字段会是 null。设计公开文章时,记得给作者资料表也配置公开读策略,否则页面会缺作者信息。
下一篇学习文件存储,把用户的头像图片真正存进 Supabase。