Install
openclaw skills install @23396599/report-sqlDefine and validate SQL template variables and blocks for report-service with zero tolerance, supporting safe injection, conditional fragments, loops, and da...
openclaw skills install @23396599/report-sql本技能由老板亲授,定义 report-service 的 SQL 模板协议与变量规范。
report-service 变量遵循唯一、精确、零容错原则:
#@option.where.<field>#
| 字段名 | 类型 | 示例值 | 典型 SQL 用法 |
|---|---|---|---|
name | string | "张三" | WHERE s.name = #@option.where.name# |
status_list | array | ["draft","published"] | WHERE b.status IN (#@option.where.status_list#) |
start_date | date | "2026-03-01" | AND u.created_at >= #@option.where.start_date# |
limit | number | 100 | LIMIT #@option.where.limit# |
#@name#, #name#, @name@ → 无命名空间;#@option.name#(缺 .where)→ 路径错误;s.name, b.status → 别名+字段,非变量;变量只提供值,表别名由 FROM 子句决定:
FROM sale s → 写 s.name = #@option.where.name#;FROM blog b → 写 b.title LIKE CONCAT('%', #@option.where.name#, '%');s. 或 b. 前缀。${...} 块级变量语法(2026-03-23 补充)用于包裹条件片段,典型形式:
${
AND `corpId` = '#@option.where.corpId#' }
${
AND `creatorId` = '#@option.where.creatorId#' }
${...} 是完整 SQL 片段容器,内部可含任意合法 SQL(含换行、缩进、注释);${...} 内部仍使用 #@option.where.xxx# 注入具体值;${...} 块整体按需渲染:若块内所有 #@...# 均有值,则整个块保留;若任一 #@...# 为空/未传,则整块被剔除(空安全);${...};${...} 外不可加额外空格或符号(如 ${...} 或 AND ${...} —— AND 必须写在块内)。用于 LIKE 模糊查询,典型形式:
${
AND `name` LIKE '%#@option.where.name#%' }
%...% 包裹确保前后通配;${...} 空安全规则:若 #@option.where.name# 未传值,整块自动剔除;xxx%)或右匹配(%xxx),请显式写出对应 % 符号。用于 createdAt 等时间字段的开闭区间查询,典型形式:
${
AND `createdAt` >= '#@option.where.createdAt[0]#'
AND `createdAt` < '#@option.where.createdAt[1]#' }
[0] / [1] 表示数组索引,要求传入长度为 2 的时间字符串数组(如 ["2026-03-01 00:00:00", "2026-03-02 00:00:00"]);>= start AND < end,实现标准左闭右开区间;${...} 空安全:任一索引值缺失 → 整块剔除。用于生成 IN 或多条件 OR 逻辑,典型形式:
$@option.where.state ->
OR {
AND [state = '#@item#'] }
$@option.where.state -> 声明待遍历的数组变量(如 ['draft','published']);#@item# 在循环中自动替换为当前数组元素;OR { ... } 是协议级语法糖:引擎会将每个 { ... } 块内容作为原子条件,用 OR 连接,并自动包裹外层括号 (... OR ...),最终与块外固定条件组合(如 AND (...)),确保 SQL 语义清晰、结构合法;AND/OR 优先级错误);@option.where.state 为空数组或未传,整段被剔除。$@... -> OR { ... } 生成的 SQL 自动为每个 { ... } 块添加括号,例如:
$@option.where.types ->
OR {
AND state = 'finished' AND [type = '#@item#'] }
解析为:
( state = 'finished' AND type = 'sale' )
OR ( state = 'finished' AND type = 'refund' )
此形式在逻辑上 完全等价于:
state = 'finished' AND type IN ('sale', 'refund')
或:
AND state = 'finished' AND ( (type = 'sale') OR (type = 'refund') )
✅ 优势:括号强制分组,杜绝 AND/OR 优先级陷阱;
✅ 优势:可轻松扩展为多字段组合(如 AND [type = '#@item#'] AND [status = '#@item2#']);
⚠️ 注意:若需外层统一 AND(如所有 OR 结果再 AND 一个固定条件),请将该固定条件写在 OR { ... } 块之外(如 WHERE active = 1 AND (...)),而非塞进块内。
$@... -> OR { ... } 支持遍历对象数组,并用点语法 #@item.xxx# 访问嵌套字段。
典型形式:
$@searchOrder -> OR { AND [id = '#@item.corpId#'] }
解析逻辑:
searchOrder = [{"id":1,"corpId":"wx123"},{"id":2,"corpId":"wx456"}],#@item.corpId# 分别取值 "wx123" 和 "wx456",( id = 'wx123' ) OR ( id = 'wx456' )
✅ 优势:直接复用上一步查询结果,实现“查完即用”的流水线;
⚠️ 注意:#@item.xxx# 中的 xxx 必须是对象存在的字段名,否则该元素被跳过(空安全)。
用于封装高复用业务逻辑(如分发状态判定),典型形式:
${
AND NOT (
EXISTS(
SELECT #@option.where.distributeYes# FROM wq.`joblist` j WHERE j.`JobId` = b.id AND j.active = 1 AND (j.`outUserID` = '' OR j.outUserID IS NULL)) OR (NOT EXISTS(SELECT 1 FROM wq.`joblist` j WHERE j.`JobId` = b.id AND j.active = 1 AND (j.`outUserID` = '' OR j.outUserID IS NULL)) and (b.outUserID is null or b.outUserID = '')))}
${ AND (EXISTS(SELECT #@option.where.distributeNo# FROM wq.`joblist` j WHERE j.`JobId` = b.id AND j.active = 1 AND (j.`outUserID` = '' OR j.outUserID IS NULL)) OR (NOT EXISTS(SELECT 1 FROM wq.`joblist` j WHERE j.`JobId` = b.id AND j.active = 1 AND (j.`outUserID` = '' OR j.outUserID IS NULL)) and (b.outUserID is null or b.outUserID = '')))}
#@option.where.distributeYes# / #@option.where.distributeNo# 仍为原子值注入(如 1 或 'true'),子查询结构由块体保证;${...} 空安全:任一 #@...# 缺失 → 整块剔除;j./b.)由块内 SQL 自行定义,与外部 FROM 无关。${...} 块不是“值占位符”,而是自洽 SQL 片段容器。其边界必须包裹完整语法单元。
-- 错!and 在块外 → 空时生成语法错误:`WHERE active = 1 and `
select id, parent, name from department where active = 1 and ${ corpId = '#@option.where.corpId#' }
-- 对!and 在块内 → 空时整块剔除,SQL 依然合法
select id, parent, name from department where active = 1 ${ and corpId = '#@option.where.corpId#' }
AND/OR/IN/BETWEEN 等)必须写在 ${...} 块内;EXPLAIN);SELECT ... FROM ... WHERE),绝不拼接动态逻辑。这是
report-service空安全的最后防线 —— 宁可多写一个and,不可少包一行 SQL。
report-service 支持链式数据变换操作,用于处理 SQL 查询结果,支持初始化、条件赋值、字段映射、关联补全与结果组装。
所有变换以 JSON 数组形式声明,每个对象为一个操作步骤,按顺序执行。
| 字段 | 类型 | 说明 |
|---|---|---|
method | string | 操作类型(setData / copyObject / leftJoin) |
target | string | 目标变量名(如 @result, @rows),以 @ 开头 |
source | any | 源数据(可为字面值、数组、对象、或另一变量如 @deptList) |
precondition | string | 执行前置条件(如 #@deptList#,当该变量有值时才执行此步) |
attributes / fields | array | 字段映射规则(from → to,可设 default) |
targetKey / sourceKey | string | 关联键名(用于 leftJoin) |
[
{
"method": "setData",
"target": "@result",
"source": []
},
{
"method": "setData",
"target": "@result",
"source": "@deptList",
"precondition": "#@deptList#"
}
]
@result 为空数组;若 @deptList 存在,则用其覆盖 @result。precondition 实现空安全分支。💡 权威说明(老板亲授):
precondition: "#@deptList#"的语义是——判断上一步产生的中间变量@deptList是否存在且非空(非null/undefined/[]/{}); 仅当该条件为真时,第二步setData才执行; 第一步source: []确保@result始终有默认值,避免未定义状态。
[
{
"method": "setData",
"target": "@result",
"source": {}
},
{
"method": "copyObject",
"target": "@result",
"source": "@countOrder[0]",
"attributes": [
{
"from": "totalCount",
"to": "count",
"default": 0
},
{
"from": "totalMoney",
"to": "money",
"default": 0
}
]
}
]
@result 为空对象 {};再从 @countOrder[0](数组首项,必须是对象)中提取 totalCount → count、totalMoney → money,缺失时用 default 填充。copyObject 支持字段级映射、类型安全兜底,且目标必须是对象(非数组)。[
{
"method": "setData",
"target": "@rows",
"source": []
},
{
"method": "setData",
"target": "@rows",
"source": "@searchOrder",
"precondition": "#@searchOrder#"
},
{
"method": "leftJoin",
"target": "@rows",
"source": "@searchCorp",
"targetKey": "corpId",
"sourceKey": "id",
"fields": [
{
"from": "name",
"to": "corpName",
"default": ""
},
{
"from": "nickname",
"to": "corpNickname",
"default": ""
}
]
},
{
"method": "copyObject",
"target": "@result",
"source": "@rows",
"to": "rows",
"default": []
}
]
@rows 为空数组;@searchOrder 存在,则赋值;@searchCorp 左关联 @rows(corpId ↔ id),补全 corpName/corpNickname;@rows 数组整体写入 @result.rows(非覆盖 @result)。leftJoin 保障跨源数据一致性,copyObject 的 to: "rows" 实现标准响应结构({ "rows": [...] })。