ToolJet 数据库外键(Foreign Key)完整指南:约束、动作与引用完整性实战
【免费下载链接】ToolJetOpen-source foundation of ToolJet AI - the enterprise app generation platform for internal tools, dashboards, business applications, workflows and AI agents. Build visually, from a prompt, or from Claude Code, Codex and Cursor over MCP 🚀项目地址: https://gitcode.com/GitHub_Trending/to/ToolJet
导读
外键(Foreign Key)是 ToolJet 内置数据库(ToolJet Database)中用于连接两张表、保证数据一致性的核心约束机制。本文基于 ToolJet 官方文档,并结合前端表单实现与后端服务源码,系统讲解在 ToolJet 数据库编辑器中创建外键关系的完整流程、四类更新/删除动作(Restrict、Cascade、Set NULL、Set to Default)的行为差异、引用完整性的底层保障,以及一张表可同时建立多条外键等进阶用法。读完本文,你将能在 ToolJet 数据库 UI 中熟练设计多表关联,并理解外键约束背后的校验规则与源码实现。
什么是外键关系
外键关系(Foreign Key Relation)是指将当前表(Source,源表)中的一个或一组列,与已存在的其他表(Target,目标表)中的一个或一组列进行关联。这一关系在两张表之间建立连接,使源表可以引用目标表的数据。
在 ToolJet 数据库中,当你在源表中创建或编辑表结构时,可以添加一条或多条外键,每条外键都指向另一张已存在表中具备唯一约束的列。创建外键关系时,你还可以为源行选择当目标行被更新或删除时执行的动作(Action),相关选项的完整说明见下文"外键动作"一节。
从源码角度看,ToolJet 数据库服务层通过 tooljet-db-table-operations.service.ts 中的createForeignKey方法执行外键创建,内部最终调用 TypeORM 的createForeignKeys生成 PostgreSQL 的FOREIGN KEY ... REFERENCES约束,并在成功后通过NOTIFY pgrst, 'reload schema'通知 PostgREST 重新加载 schema,使新约束立即生效。
Constraints(约束条件)
在 ToolJet 数据库中创建外键关系时,必须满足以下三个约束条件:
- 数据类型匹配:目标表中被引用的列,必须与源表中建立外键的列具有相同的数据类型。
- 目标列必须具备 Unique 约束:目标表中被引用的列必须显式设置 Unique 约束(也包含主键列,因为主键天然具备唯一性)。
- 目标表必须已存在:添加外键关系之前,目标表必须先创建完成,即在源表上建立外键时,无法引用一张尚不存在的表。
在 UI 层,上述约束被编码为类型过滤逻辑:在 TableKeyRelations.jsx 中,目标列下拉列表会根据源列的数据类型动态过滤,仅展示数据类型匹配的候选列;源列为integer时可匹配目标表的integer或serial列,源列为bigint时可匹配integer或bigint,其余类型则要求完全一致。
Limitations(限制)
除了约束条件,ToolJet 数据库对外键还有以下限制:
- 不允许自引用:目标表和源表不能是同一张表。
- serial 类型列不能作为源表的外键列:无法用源表中数据类型为
serial的列创建外键。 - 不能引用复合主键的组成部分:目标表中作为其复合主键(Composite Primary Key)一部分的列,不能被外键引用。
前端源码与上述限制一一对应。在 TableKeyRelations.jsx 中,源列下拉列表将serial、boolean、timestamp with time zone、jsonb类型的列标记为禁用(isDisabled),无法选为外键源列。而后端在createForeignKey中会调用 checkIfForeignKeyReferencedColumnsAreFromCompositePrimaryKey,遍历目标表的列约束,若目标列属于由多个列组成的复合主键,则抛出ConflictException("Foreign key cannot be created as the referenced column is in the composite primary key."),从服务端再次拦截。
此外,创建外键时源表需至少有一列满足必填字段要求,且数据库中至少要有两张表(目标表加源表),否则 UI 上的+ Add relation按钮会处于禁用状态并给出对应的 tooltip 提示,相关判断见 ForeignKeyRelation.jsx。
Exception(例外)
有一条值得注意的例外规则:
- 源表中数据类型为integer的列,可以引用目标表中数据类型为serial的列。
这在前端源码中得到印证:在 TableKeyRelations.jsx 的过滤逻辑中,当sourceColumn.dataType === 'integer'时,允许的目标列类型为integer或serial。serial本质上是 PostgreSQL 基于序列(sequence)生成的整数自增列,因此从值域上看与integer兼容。
Creating Foreign Key(创建外键)
在创建或编辑表(此时该表即为源表)时,你可以添加一条或多条外键,分别引用其他已存在表(目标表)的列。创建外键关系的步骤如下:
- 创建或编辑一张表(该表将作为源表)。
- 在Foreign key relation区域点击+ Add relation按钮,打开右侧外键配置抽屉。
- Source(源):从下拉菜单中选择要建立外键的列。源表默认为当前正在创建/编辑的表,且已通过数据类型过滤禁用不可用的列。
- Target(目标):从下拉菜单中选择目标表,再从该表的下拉菜单中选择被引用的列。目标列列表会随目标表的切换而重新加载(调用
viewTable接口拉取目标表列信息),且同样经过数据类型匹配过滤。 - Actions(动作):选择目标行被更新或删除时,源表行应执行的动作。
- 点击Create按钮创建外键关系。
提示:在目标表的下拉菜单底部有一个进入 ToolJet 数据库页面的入口(源码中通过
getPrivateRoute('database')打开数据库主页面),方便你跳转到目标表确认列定义。
创建成功后,源表的外键关系列表会以源列 → 目标表.目标列的形式展示,每条关系旁都有编辑图标,点击可修改目标表、目标列或动作;编辑已有外键关系时,如果更改了目标表或目标列,界面会弹出确认对话框,提示"更新外键关系将先删除当前约束再添加新约束"(对应后端updateForeignKey先dropForeignKey再createForeignKeys的实现,见 tooljet-db-table-operations.service.ts),确认后才会提交变更。删除外键关系同样需要二次确认。
Foreign Key Actions(外键动作)
创建外键关系时,ToolJet 数据库允许你为"目标行被更新或删除时源行执行什么操作"选择动作。On Update 与 On Delete 各有四个选项,前端在 TableKeyRelations.jsx 中维护了onUpdateOptions与onDeleteOptions两个选项列表(RESTRICT、CASCADE、SET NULL、SET DEFAULT),且抽屉打开时会默认选中第一项RESTRICT。
On Update(目标行更新时)
| 选项 | 说明 |
|---|---|
| Restrict(默认) | 若目标表中的某行被其他源行引用,则禁止对该目标行执行更新。 |
| Cascade | 目标行中的引用值更新后,源表中引用该值的行会同步更新。 |
| Set NULL | 目标行中的引用值更新后,源表中引用该值的列会被置为 NULL。 |
| Set to Default | 目标行中的引用值更新后,源表中引用该值的列会被重置为源表外键列的默认值。 |
On Delete(目标行删除时)
| 选项 | 说明 |
|---|---|
| Restrict(默认) | 若目标表中的某行被其他源行引用,则禁止删除该目标行。 |
| Cascade | 目标行被删除后,源表中引用该值的行会一并被删除。 |
| Set NULL | 目标行被删除后,源表中引用该值的列会被置为 NULL。 |
| Set to Default | 目标行被删除后,源表中引用该值的列会被重置为源表外键列的默认值。 |
这些动作在创建外键时以 JSON 形式提交到后端,字段名为on_delete与on_update(见 ForeignKeyRelation.jsx 中handleCreateForeignKey构造的请求体),最终由createForeignKeys生成对应的 PostgreSQLON UPDATE/ON DELETE子句。实际使用中,最常见的组合是On Delete 选择 Restrict以防止误删被引用的主数据,同时配合On Update 选择 Cascade让主键值变更自动同步到所有关联子表。
Referential Integrity(引用完整性)
外键约束的核心价值在于保证源表与目标表之间的引用完整性(Referential Integrity):它强制源表中的外键列只能取目标表外键列中存在的唯一值之一。ToolJet 数据库在 UI 层面提供了三层保障:
- 新增行时:在源表新增一行、操作外键列时,该列会以下拉框形式展示目标表中存在的唯一值,确保新增数据与目标表保持一致。
- 编辑行时:在源表编辑已有行的外键单元格时,下拉框同样只展示目标表中的唯一值,保证更新后的数据始终与目标表一致。
- 快捷跳转:下拉框底部提供Open referenced table按钮,点击可直接跳转到目标表,便于核对引用数据。
需要注意的是,当插入或更新的值违反了外键约束(例如引用了目标表中不存在的值,或在 Restrict 动作下删除仍被引用的目标行)时,ToolJet 数据库会拒绝该操作并返回约束违规错误,前端会以 toast 形式提示具体失败原因。这一点与后端createForeignKey对 PostgreSQL 错误码(如42710表示外键约束已存在)的处理逻辑对应,见 tooljet-db-table-operations.service.ts。
完整示例:Orders 与 Customers 表
下面通过一个电商场景,演示如何为Orders(订单)与Customers(客户)表建立外键关系。
首先在 ToolJet 数据库中创建以下两张表:
Customers(目标表)
| 列名 | 数据类型 | 主键 | 非空 | 唯一 |
|---|---|---|---|---|
| customer_id | int | ✅ | ✅ | ✅ |
| name | varchar | ❌ | ✅ | ❌ |
| varchar | ❌ | ✅ | ✅ |
Orders(源表)
| 列名 | 数据类型 | 主键 | 非空 | 唯一 |
|---|---|---|---|---|
| order_id | int | ✅ | ✅ | ✅ |
| customer_id | int | ❌ | ✅ | ❌ |
| order_date | varchar | ❌ | ✅ | ❌ |
| total_amount | float | ❌ | ✅ | ❌ |
两张表的结构满足外键约束条件:Orders.customer_id与Customers.customer_id数据类型一致(均为int);目标列Customers.customer_id同时具备主键与唯一约束(主键天然唯一);目标表Customers先于外键创建存在。
现在我们希望在Orders.customer_id与Customers.customer_id之间建立外键关系:
- 定义外键关系
- 编辑
Orders表。 - 在Foreign key relation区域点击+ Add relation按钮。
- 在Source区域选择
customer_id列。 - 在Target区域选择
Customers表和它的customer_id列。 - 选择目标动作,例如RESTRICT,以防止删除仍有关联订单的客户。
- 编辑
- 保存变更:点击Save Changes按钮创建外键关系。
此后,每当向Orders表插入或更新记录时,customer_id的值必须对应Customers表中已存在的customer_id值;同时,由于 On Delete 选择了 Restrict,你无法删除仍被订单引用的客户记录。这样就能确保订单永远关联到一个有效的客户,从而维护数据的完整性与一致性。
外键的进阶用法与底层原理
一条外键关联多个目标列
外键并不局限于单列。ToolJet 数据库支持一个外键关系同时引用目标表的一组列:后端在 prepareForeignKeyDetailsJSON 及 TypeORMTableForeignKey构造中均以数组形式处理referenced_column_names,前端请求体也使用column_names/referenced_column_names数组结构(见 ForeignKeyRelation.jsx),为多列复合外键预留了能力。
一张表多个外键
创建/编辑表时,你可以为不同的源列分别添加多条外键关系,每条关系独立引用不同的目标表。前端foreignKeyDetails状态以数组维护多条关系(handleCreateForeignKey通过[...prevValues]追加新关系),后端createForeignKey同样接收foreign_keys数组并一次性批量创建,见 controller.ts。
外键操作的后端调用链
从一次完整的外键创建操作来看,其调用链为:
- 前端 ForeignKeyRelation.jsx 组装
column_names、referenced_table_name、referenced_column_names、on_delete、on_update并调用tooljetDatabaseService.createForeignKey; - 服务端 controller.ts 接收请求,将操作委托给
tableOperationsService.perform(organizationId, 'create_foreign_key', params); - tooljet-db-table-operations.service.ts 校验目标表存在、复合主键限制后,在事务中执行
createForeignKeys,随后NOTIFY pgrst, 'reload schema'刷新 schema 缓存。
其中create_foreign_key、update_foreign_key、delete_foreign_key等操作标识集中定义在 constants/index.ts,与前端表单的增、改、删三种操作一一对应。修改已有外键关系的本质是"先删旧约束、再建新约束",因此 UI 才会在变更目标列或目标表时弹出二次确认。
相关文档
- 主键(Primary Key)约束:外键通常引用主键列,建议先掌握主键的定义方式。
- 表操作(Table Operations):表的创建、编辑与字段类型定义。
- 数据库编辑器(Database Editor):ToolJet 数据库的整体界面与数据浏览方式。
- 查询 ToolJet 数据库:建立外键后,可在查询中利用关联关系进行 Join 查询。
【免费下载链接】ToolJetOpen-source foundation of ToolJet AI - the enterprise app generation platform for internal tools, dashboards, business applications, workflows and AI agents. Build visually, from a prompt, or from Claude Code, Codex and Cursor over MCP 🚀项目地址: https://gitcode.com/GitHub_Trending/to/ToolJet
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考