SQL 参数与动态语法
SQL 数据集中 $参数、${表达式}、#{脚本}、整条 SQL 脚本、集合参数、注释与分号处理、注入校验规则。
概述
SQL 数据集的 SQL 文本在执行前会经过模板处理。共有四种写法,遵循一条原则:值走 ${},结构走 #{}。
| 写法 | 处理结果 | 用途 | 示例 |
|---|---|---|---|
$参数名 |
绑定为预编译占位符 ? |
值 | WHERE id = $userId |
${表达式} |
表达式求值后绑定为 ? |
值 | WHERE d >= ${addDays(now(), -30)} |
#{脚本} |
结果直接拼进 SQL 文本 | 结构(子句、表名、排序) | #{isEmpty($s) ? '' : 'AND status = $s'} |
整条 SQL 被 ${...} 包裹 |
脚本生成整段 SQL | 结构 | 见下文 |
$参数名 等价于 ${$参数名},单个参数直接写前者即可;只有需要拼接、函数或四则运算时才写 ${...}。
功能入口
在 SQL 数据集、公共数据集与数据大屏的 SQL 编辑器中书写。编辑器右侧「语法」「函数」「代码片段」标签页有速查。
操作步骤
用 $参数名 传值
SELECT visit_id, dept_code, fee
FROM outpatient_visit
WHERE dept_code = $deptCode
AND visit_date >= $startDate
$ 后的名字须是已声明的报表参数。${库房} 这种没有内层 $ 的写法不是参数引用:库房 是未定义标识符,求值为空(null),绑定为 NULL;取参数值须写 $库房 或 ${$库房}。裸写的 $名 不会在引号内、${}/{} 块内、或 $$ 内置变量处被识别。
用 ${表达式} 计算值
表达式可调用内置函数,结果作为一个绑定值:
WHERE visit_date >= ${addDays(now(), -$days)}
AND visit_date < ${now()}
${} 正文遇到第一个 } 即结束,所以正文里不能出现 }。函数清单见 内置函数。
LIKE 模糊查询
占位符不能写在引号里,'${$kw}' 会被判为定义错误。正确写法是把通配符拼进表达式:
WHERE patient_name LIKE ${'%' + $kw + '%'}
用 #{} 拼结构
#{} 的结果原样进入 SQL,正文按花括号配平,可写块和映射字面量。正文只有一个表达式时自动返回;含 var、if/else 等多条语句时必须显式 return,否则拼出空串。
可选条件(值仍写成 $名,拼入后再被绑定):
SELECT visit_id, dept_code, fee
FROM outpatient_visit
WHERE 1 = 1
#{isEmpty($deptCode) ? '' : 'AND dept_code = $deptCode'}
#{$minFee != null ? 'AND fee >= $minFee' : ''}
带白名单的动态排序(多语句,必须 return):
SELECT visit_id, dept_code, fee, visit_date
FROM outpatient_visit
ORDER BY #{
var allowed = ['fee', 'visit_date', 'dept_code'];
return allowed.contains($sortField) ? $sortField : 'visit_date'
} #{$sortDir == 'ASC' ? 'ASC' : 'DESC'}
表名、列名只能用 #{} 拼入,必须做白名单,不要直接拼接参数。
整条 SQL 由脚本生成
整条 SQL 被 ${...} 包裹时,内部作为脚本执行,返回值就是最终 SQL;返回文本里的 #{}、${} 会再被处理一次:
${
if ($reportType == 'detail') {
return "SELECT visit_id, dept_code, fee FROM outpatient_visit ORDER BY visit_date DESC"
} else {
return "SELECT dept_code, SUM(fee) AS total_fee FROM outpatient_visit GROUP BY dept_code"
}
}
安全限制:结果里出现的 ${、#{ 必须原样出现在脚本源码里,否则被拒绝,防止参数值携带模板片段。
集合参数
把报表参数类型设为「集合」,用 IN (${$名}):
WHERE dept_code IN (${$deptCodes})
引擎按元素个数把一个 ? 展开为 ?,?,?。参数值可以是多选控件传入的集合,也可以是字符串 1,2,3、全角逗号或顿号分隔的字符串、JSON 数组字符串。空集合绑成 IN (NULL),结果为空而不报错。
「没选就不过滤」:
WHERE 1 = 1
#{isEmpty($deptCodes) ? '' : 'AND dept_code IN (${$deptCodes})'}
绑定时日期带时间的用 setTimestamp,仅日期的用 setDate。不要用 #{$ids} 拼集合:集合被转成 [1, 2, 3],产生 IN ([1, 2, 3]) 语法错误,空集合则是 IN ()。
系统参数与当前用户
| 写法 | 含义 |
|---|---|
${$$config.名称} |
「系统 > 自定义参数」中的配置值,SQL 里必须写成 ${$$config.名称},裸写 $$config.名称 不会被转换 |
${userAccount()} |
当前登录账号,仅在有登录上下文时有值 |
${username()} |
当前登录用户姓名,仅在有登录上下文时有值 |
行级权限示例:
SELECT visit_id, patient_name, fee
FROM outpatient_visit
WHERE doctor_account = ${userAccount()}
属性说明
处理顺序
| 步骤 | 处理 |
|---|---|
| 1 | 整条 SQL 是 ${...} 时先执行脚本 |
| 2 | 展开 #{} |
| 3 | 去除注释:-- 行注释、/* */ 块注释 |
| 4 | 裸 $变量 转为 ${$变量} |
| 5 | 提取 ${} |
| 6 | ${} 替换为 ? 并记录绑定值 |
| 7 | 识别存储过程的输出参数声明 |
| 8 | 去除末尾分号 |
同一条 SQL 中相同的表达式只求值一次。
尾部分号
末尾的 ;(含前后空白、连续多个)会被自动去除,字符串字面量中和语句中间的分号不动。语句中间的分号会被注入校验拒绝。
注入校验规则
对去掉字符串字面量后的 SQL 结构做校验,拒绝下列内容:
- 整词
drop、delete、insert、update、create、alter、truncate、exec、execute、script、declare。 - 十六进制字面量
0x…,xp_扩展存储过程,以及sp_executesql、sp_cmdshell等系统过程(自建的sp_xxx过程允许)。 - 语句中间的
;,OR x=x形式,非开头位置的UNION SELECT,单引号个数为奇数。
字段名 create_time、update_time 与 WHERE 1=1 不受影响。
注意:
${}只会绑定为预编译参数?,不能生成AND、IN、ORDER BY等 SQL 结构。表达式的值看起来是 SQL 片段时,执行报错「SQL 中第N个 ${...} 表达式看起来在生成动态 SQL 片段……请改用 #{}」。
示例
综合示例:按时间范围、可选科室、动态排序查询门诊费用。报表参数:startDate、endDate(日期),deptCodes(集合,可空),sortField、sortDir(字符串)。
SELECT visit_id, dept_code, fee, visit_date
FROM outpatient_visit
WHERE visit_date >= $startDate
AND visit_date < $endDate
#{isEmpty($deptCodes) ? '' : 'AND dept_code IN (${$deptCodes})'}
ORDER BY #{
var allowed = ['fee', 'visit_date', 'dept_code'];
return allowed.contains($sortField) ? $sortField : 'visit_date'
} #{$sortDir == 'ASC' ? 'ASC' : 'DESC'}
注意事项
注意:不要把值拼成字面量,例如
'AND dept_code = ' + "'" + $deptCode + "'"。这样值进入了 SQL 文本,有注入风险,也可能被单引号配对校验拦下。
注意:数值参数不要用
isEmpty判空,改用$x != null。
提示:对列做函数运算(如
YEAR(visit_date) = $year)会让索引失效,数据量大时改为先算好边界再比较:visit_date >= ${...}。