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 Database 中建立表与表之间引用关系、保障数据一致性的核心手段。本文以 ToolJet 3.0.0-LTS 官方文档为骨架,结合前端表单实现与后端服务源码,完整讲解外键的创建前提、适用限制、On Update / On Delete 动作语义以及引用完整性在界面上的实际体现,帮助你基于 ToolJet 内置数据库设计出符合第三范式、数据自洽的多表业务模型。
什么是外键关系
外键关系用于将当前表(源表 Source Table)的一个或多个列,与另一张已存在表(目标表 Target Table)的一个或多个列进行关联。这种关系在两张表之间建立起连接,使源表能够引用目标表中已有的数据。在创建外键关系时,你还可以为源行选择当被引用的(目标表)行发生更新或删除时所要执行的动作(On Update / On Delete)。
在 ToolJet Database 的界面中,外键关系在"Foreign key relation"区域统一管理。前端组件 ForeignKeyRelation.jsx 展示了该区域的完整实现:它列出当前表已配置的外键(显示为源列 → 目标表.目标列),并提供了Add relation按钮打开右侧抽屉进行新建与编辑。
创建外键的前提约束(Constraints)
在 ToolJet Database 中建立外键关系,必须同时满足以下三条硬性条件:
- 数据类型匹配:目标表中被引用的列,其数据类型必须与源表中的外键列数据类型一致。
- 唯一约束:目标表中被引用的列必须显式声明Unique约束(即该列上的值在目标表中唯一,例如主键列天然满足,但普通列需单独开启 Unique)。
- 目标表先行存在:在源表中添加外键关系之前,目标表必须已经创建完成。
这些规则与标准关系型数据库(ToolJet Database 底层基于 PostgreSQL)的外键语义一致:外键列中的每个值,都必须能在目标表被引用列中找到对应的唯一值。
限制与例外(Limitations & Exception)
ToolJet Database 对外键关系做了以下明确的限制:
- 不允许自引用:目标表与源表不能是同一张表。
- 序列类型不能作为源表外键列:源表中数据类型为
serial(自动递增整数)的列不能用于创建外键。 - 不能引用复合主键的组成列:目标表中作为其复合主键(Composite Primary Key)组成部分的列,不能被外键引用。
同时存在一条例外:
- 源表中数据类型为
integer的列,可以引用目标表中serial数据类型的列(因为serial在底层本质上就是带默认值的integer)。
从后端实现看,元数据层通过查询 PostgreSQL 的pg_constraint系统表来获取外键明细(约束名、引用的表与列),见 tooljet-db-table-operations.service.ts;而唯一约束与主键的判断同样来自对constraint_type的查询(PRIMARY KEY、UNIQUE),这正对应了上文"目标列必须 Unique"的校验来源。
创建外键关系的操作步骤
在创建或编辑表时,可以为该表(源表)添加一个或多个指向其他已存在表(目标表)列的外键。具体步骤如下:
- 创建一张新表,或编辑一张已存在的表。
- 在Foreign key relation区域点击+ Add relation按钮。
- 当前正在创建/编辑的表即为源表(Source)。
- 在 Source 区域的下拉菜单中选择要建立外键的列。
- 在 Target 区域的下拉菜单中,先选择目标表,再选择目标表中的被引用列。
- 在 Actions 区域选择当被引用行更新或删除时希望执行的动作。
- 点击Create按钮完成外键关系的创建。
前端交互逻辑中的细节
结合源码可以看到,创建外键时前端会组装如下结构(见 ForeignKeyRelation.jsx):
{ column_names: [sourceColumn], // 源表外键列 referenced_table_name: targetTable, // 目标表 referenced_table_id: targetTable.id, referenced_column_names: [targetColumn], // 目标表被引用列 on_delete: onDelete, // 删除动作 on_update: onUpdate, // 更新动作 }同时,Add relation按钮并非始终可用,它会根据以下条件自动禁用(并给出 Tooltip 提示):
- 已存在的表少于 2 张(外键必须存在源表与目标表两张表);
- 源表尚未命名;
- 源表没有任何列;
- 至少有一个列的必要字段尚未填写完整。
在 ForeignKeyTableForm.jsx 中,Create按钮还会在 Source 列、Target 表、Target 列、On Delete、On Update 任一为空时禁用,确保提交到后端的数据始终完整。
外键动作(Foreign Key Actions)
创建外键关系时,ToolJet Database 允许你为"目标表被引用行更新/删除时,源表行如何处理"选择动作。前端动作选项定义在 TableKeyRelations.jsx 中,共四种: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 | 目标表被引用行被删除后,源表中引用该行的实例会被设置为源表外键列的默认值。 |
在编辑已存在的外键时,如果修改了引用的目标表或目标列,前端会弹窗提示"更新外键关系将删除当前约束并添加新约束,同时会用源表的默认值替换目标表列中设置的默认值",确认后才会提交。
后端如何执行这些动作
后端将前端提交的外键描述转换为数据库约束并执行。在 tooljet-db-table-operations.service.ts 中可以看到三类核心操作:
- 创建外键(
create_foreign_key,对应 L1415-L1494):将on_delete/on_update等参数封装为TableForeignKey后调用createForeignKeys,并执行NOTIFY pgrst, 'reload schema'让 PostgREST 重新加载 schema,使新建约束立即生效;若违反唯一性(PostgreSQL 错误码 42710,外键约束已存在)会被捕获并返回友好错误。 - 更新外键(
update_foreign_key,对应 L1496-L1560):先dropForeignKey删除旧约束,再createForeignKeys重建新约束,整个过程在事务中完成。 - 删除外键(
delete_foreign_key,对应 L1568-L1590):通过约束名直接dropForeignKey移除关系。
此外,在创建/编辑表结构时,如果表中已配置外键,后端也会在提交列变更后同步执行createForeignKeys(见 L776-L780),保证表结构变更与外键关系始终一致。
引用完整性(Referential Integrity)
外键约束保证了源表与目标表之间的引用完整性:源表外键列中的每一个值,都必须是目标表被引用列中已存在的唯一值之一。ToolJet Database 通过界面交互让这一约束"所见即所得":
- 新增行时:在源表新增一行时,外键列会以下拉框的形式展示目标表中现有的唯一值,从源头杜绝无效引用;下拉框底部还提供Open referenced table按钮,点击即可直接跳转到目标表查看数据。
- 编辑行时:编辑源表中已有行的外键单元格时,下拉框同样只展示目标表中的唯一值,确保更新后的数据依然与目标表保持一致。
这种"下拉框即约束"的设计,将数据库层的完整性校验前置到了录入阶段,让非专业的业务用户也能在无 SQL 的情况下维护严格一致的数据关系。
实战示例:订单表与客户表的外键关系
以电商场景为例,我们需要在Orders(订单)表与Customers(客户)表之间建立外键关系,保证每条订单都关联到一个真实存在的客户。
首先,在 ToolJet Database 中创建以下两张表:
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列之间创建外键关系:
- 定义外键关系
- 编辑
Orders表; - 在 Foreign Key Relation 区域点击+ Add Relation;
- 在Source区域选择
customer_id列; - 在Target区域选择
Customers表与其中的customer_id列; - 选择期望的动作,例如RESTRICT,从而阻止删除仍存在关联订单的客户。
- 编辑
- 保存变更:点击Save Changes按钮完成外键关系的创建。
关系建立后,向Orders表插入或更新记录时,customer_id的值必须对应Customers表中已存在的customer_id;同时,由于选择了 RESTRICT,任何仍有关联订单的客户都无法被删除。由此,订单永远关联到有效客户,数据的一致性与完整性得到了持续保障。
小结
ToolJet Database 的外键功能将传统关系型数据库的引用完整性封装为可视化的低代码交互:创建时严格校验数据类型、唯一约束与目标表存在性,操作动作覆盖 Restrict / Cascade / Set NULL / Set to Default 四种标准语义,录入与编辑阶段又以"唯一值下拉框"的形式将约束前置。无论是构建电商订单模型、多对一字典表还是关联明细表,理解并善用外键关系,都是保证 ToolJet 应用数据长期可靠的基础。
【免费下载链接】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),仅供参考