触发器用于在新建、更新、删除对象数据后,自动执行一段 DeepQL 语句,实现数据校验、日志记录、联动更新等后置逻辑。
注意:触发器不能修改发起操作的对象本身(如需修改,请使用数据重写功能)。触发器会使保存数据增加额外开销,请合理使用。
触发器的执行语句与触发它的原始操作(insert / update / delete)在同一个事务中执行。如果触发器中的断言失败或语句报错,整个操作会被回滚——这是数据校验能生效的基础。
触发器在对象类型的 SDL 定义中声明,完整格式如下:
type 对象类型 {
# ... 属性和链接定义 ...
trigger 触发器名称
after {insert | update | delete} [, ...]
for {each | all}
do (表达式);
};
|
关键字 |
说明 |
|---|---|
|
|
触发器编码,同一对象内唯一 |
|
|
触发时机,在操作之后执行 |
|
|
运行时机,可多选,逗号分隔 |
|
|
单条模式:对每条受影响的数据执行一次, |
|
|
批量模式:只执行一次, |
|
|
触发时执行的 DeepQL 表达式 |
|
变量 |
可用时机 |
含义 |
|---|---|---|
|
|
insert、update |
操作后的数据 |
|
|
update、delete |
操作前的数据 |
在 for each 模式下,__new__ / __old__ 是单个对象实例
在 for all 模式下,__new__ / __old__ 是对象的集合(可能包含多条数据)
触发器中执行的任何查询,看到的都是触发操作完成后的数据库状态。例如,在 insert 触发器中查询 detached Order,结果会包含刚刚插入的那条记录。
在 DeepModel 中,通过可视化界面配置触发器:对象属性栏 → 高级 → 新建触发器。

|
配置项 |
说明 |
|---|---|
|
编码 |
自动生成,无需手动填写 |
|
名称 |
触发器的描述文字 |
|
运行时机 |
新建(insert)/ 更新(update)/ 删除(delete),可多选 |
|
执行方式 |
单条(for each)或 批量(for all) |
|
执行语句 |
格式为 |
运行时机针对最终生成的 DeepQL 语句。例如:
在数据管理或 UX 页面新建包含自我链接的数据时,底层实际为「先 insert,再 update 自我链接」,会分别触发 insert 和 update 的触发器
批量更新时如果底层是逐条执行的 DeepQL,即使触发器配置为「批量」,每次的 __new__ 也只有单条数据
同一对象可以配置多个触发器,它们在同一事务中依次执行
触发器可以引起其他对象的触发器被触发(级联触发)。系统会先完成第一阶段的所有触发器,再启动第二阶段,依此类推
但如果触发器之间形成循环(A 触发 B,B 又触发 A),系统会报错
所有触发器的语句和原始操作最终会被编译为一条组合 SQL。如果多个触发器中有对同一对象的多次 update,只有一次会生效。因此,对同一对象的更新应尽量合并到一个触发器的一次 update 中。
目标:记录对象的新建、更新、删除操作到日志表,便于审计追溯。
步骤 1:新建日志对象 Log
|
编码 |
名称 |
分类 |
类型 |
说明 |
|---|---|---|---|---|
|
code |
编码 |
非计算属性 |
文本 |
业务主键,可配置数据重写 |
|
action |
动作 |
非计算属性 |
枚举值 |
insert / update / delete |
|
operation_time |
操作时间 |
非计算属性 |
日期时间 |
数据重写 |
|
operate_user_id |
操作用户ID |
非计算属性 |
文本 |
数据重写 |
|
object_code |
对象编码 |
非计算属性 |
文本 |
记录操作对象,建议加索引 |
|
business_key |
业务主键 |
非计算属性 |
文本 |
记录操作数据的主键,建议加索引 |
|
data |
数据 |
非计算属性 |
文本 |
记录操作后(或删除前)的对象数据快照 |
|
operator |
操作用户 |
计算链接 |
|
根据 operate_user_id 联查用户信息 |
步骤 2:为需要记录日志的对象配置触发器
以 Requirement 对象为例,配置三个单条触发器:
记录新建日志(运行时机:新建,执行方式:单条):
do (
insert Log {
action := 'insert',
object_code := 'Requirement',
business_key := __new__.code,
data := to_str(<json>(
select __new__ {
name,
req_type,
component_code := .component.code
}
))
}
)
记录更新日志(运行时机:更新,执行方式:单条):
do (
insert Log {
action := 'update',
object_code := 'Requirement',
business_key := __new__.code,
data := to_str(<json>(
select __new__ {
name,
req_type,
component_code := .component.code
}
))
}
)
记录删除日志(运行时机:删除,执行方式:单条):
do (
insert Log {
action := 'delete',
object_code := 'Requirement',
business_key := __old__.code,
data := to_str(<json>(
select __old__ {
name,
req_type,
component_code := .component.code
}
))
}
)
注意:删除时使用 __old__(操作前的数据),新建和更新时使用 __new__(操作后的数据)。
技巧:将操作数据通过 <json>(select __new__ { ... }) 转为 JSON 再 to_str,可以灵活选择要记录的字段,也支持通过链接带出关联数据(如 .component.code)。
配置完成后,可新建 UX 页面展示操作日志:

场景:头表 Head 和行表 Detail 各有 status 属性。当头表状态从 true 改为 false 时,自动将关联的行表数据状态也置为 false。
在头表对象上配置触发器(运行时机:更新,执行方式:单条):
do (
update Detail
filter .head.code = __new__.code
set {
status := (
false if (__old__.status and not __new__.status) else .status
)
}
)
解读:
__old__.status and not __new__.status 表示「原来是 true、现在改成了 false」
满足条件时将行表的 status 置为 false,否则用 .status 保持原值不变
filter .head.code = __new__.code 限定只更新当前头表数据关联的行表
注意:联动 update 要确保不会形成触发器循环(A 改 B → B 的触发器又改 A)。
通过 assert 函数进行校验:assert(条件, message := '错误信息')。条件为 false 时,保存失败并弹出错误信息,整个事务回滚。
触发器校验主要用于涉及多个属性/链接的复合条件。单个属性的简单约束(如必填、唯一、正则等)建议直接在属性的约束配置中设置,无需触发器。
示例 1:计划开始时间必须小于结束时间
对象 Task,运行时机:新建 + 更新,执行方式:单条:
do (
assert(
__new__.p_start_date < __new__.p_end_date,
message := '任务【' ++ __new__.name ++ '】计划开始时间需小于计划结束时间'
)
)

示例 2:重复需求的关联需求必填
对象 Requirement,运行时机:新建 + 更新,执行方式:单条:
do (
assert(
not (__new__.is_duplicated and not exists __new__.related_req),
message := '需求【' ++ __new__.name ++ '】为重复需求,关联需求必填'
)
)

当一个对象有多条校验规则时,可以把它们写在同一个触发器里,用 with 组织多条校验逻辑,最后用 assert 统一判断。好处是减少触发器数量,且多条规则的错误信息可以合并输出。
场景:对象 ValDemo 有属性 code(编码)、str1(文本)、num1(数字)、num2(数字)、status(枚举:s1/s2/s3)。需要校验:
规则 1:状态为 s1 时,str1 必填
规则 2:状态为 s2 时,num1 必须大于 num2
对应的 SDL 定义(运行时机:新建 + 更新,执行方式:批量):
trigger trigger1_7S
after update, insert
for all do (with
-- 规则1:状态为 s1 时,str1 必填
val1 :=
(select __new__
filter (.status = 's1') and not (exists (.str1))
),
val1_msg :=
(('状态为s1时, str1必填 | 具体: '
++ array_join(array_agg(val1.code), ', ')
) if exists (val1) else ''),
-- 规则2:状态为 s2 时,num1 必须大于 num2
val2 :=
(select __new__
filter (.status = 's2') and (.num1 < .num2)
),
val2_msg :=
(('状态为s2时, num1 应大于 num2 | 具体: '
++ array_join(array_agg(val2.code), ', ')
) if exists (val2) else ''),
-- 统一断言:所有规则都通过才放行
select
assert(
not (exists (val1)) and not (exists (val2)),
message := (val1_msg ++ '\n') ++ val2_msg
)
);
逐段解读:
|
部分 |
作用 |
|---|---|
|
|
批量模式, |
|
|
从中筛出违反规则 1 的记录(状态为 s1 但 str1 为空) |
|
|
有违规时拼接错误信息 + 违规编码列表;无违规则为空串 |
|
|
同理处理规则 2 |
|
|
任一规则违反就 assert 失败,回滚事务 |
关键技巧:
array_join + array_agg:把违规记录的编码收集为数组再拼成字符串,方便定位是哪些数据出了问题
多规则合并:每条规则独立检查,错误信息用 '\n' 换行拼接,用户看到完整的错误描述
批量 + select filter 模式:比为每条规则单独建触发器更简洁,且所有规则在同一个事务中完成
触发效果:
导入或编辑数据时,违反规则 1 的提示「状态为s1时, str1必填」并列出违规编码;违反规则 2 的提示「状态为s2时, num1 应大于 num2」:

场景:头表 Head 和行表 Detail,删除行表数据后,每条头表至少保留一条关联的行表数据。
行表对象设置触发器(运行时机:删除,执行方式:批量):
do (
assert(
(for h in __old__.head union (
select count((
select detached Detail filter .head = h
)) > 0
)),
message := '至少有一条关联的行表数据'
)
)
解读:
__old__.head 取出被删除的行表数据关联的所有头表
for h in ... union (...) 遍历每个头表,检查在删除后(detached Detail,即数据库当前状态)是否还有关联的行表
使用 detached 确保查询的是全局的 Detail,而非受触发器上下文限制的集合

注意:在 UX 页面新建头行数据时,头表和行表是同时提交的,无法通过触发器校验行表是否有数据。此时需要在 UX 页面配置前端控制——保存时检查行表有数据才提交。


|
能力 |
说明 |
|---|---|
|
SDL 语法 |
|
|
运行时机 |
insert / update / delete,可多选 |
|
执行方式 |
|
|
|
操作后 / 操作前的数据,单条为对象,批量为集合 |
|
事务一致性 |
触发器与原始操作在同一事务,assert 失败则整体回滚 |
|
日志记录 |
触发器中 insert 日志对象,记录操作快照 |
|
联动更新 |
触发器中 update 其他对象,实现级联状态同步 |
|
单条校验 |
|
|
复合校验 |
with 中定义多条规则,分别 select 违规数据,统一 assert |
|
批量校验 |
for … union 遍历集合逐条检查 |
|
级联触发 |
触发器可引发其他触发器,但不能形成循环 |
|
多 update 合并 |
同一对象的多次 update 只生效一次,需合并到一个触发器中 |
|
不能做的事 |
不能修改发起对象本身(需用数据重写) |
回到顶部
咨询热线
