动态SQL拼接中百分比字段的实现与问题修复

问题描述

在工单某报表开发过程中,需新增工单占比字段wkord_percentage,该字段要求计算某类工单数量占总工单数的百分比。核心需求如下:

  1. 百分比计算需动态适配时间范围参数sTime/eTime
  2. 结果保留两位小数并添加百分号
  3. 兼容部门ID过滤条件dId
Failed to parse SQL: Syntax error near 'AND'

错误分析

1. 动态条件拼接缺陷

生成的SQL片段存在语法错误:

SELECT ... 
WHERE event_id IS NOT NULL 
    AND event_id <> '' 
    AND date >= '2024-05-01'AND date <= '2024-05-31' /* 缺失空格 */

2. 类型转换不兼容

使用非标准函数toString()导致数据库兼容性问题:

CONCAT(toString(...), '%') /* MySQL不支持toString */

3. 除零风险未处理

当总工单数为零时触发除零异常:

a.wkord_cnt / (SELECT COUNT(...))

解决方案

1. 动态条件安全拼接

let subQuery = "SELECT COUNT(event_id) FROM crm.payroll " + 
"WHERE event_id IS NOT NULL " + "AND event_id <> ''";
if (sTime) subQuery += " AND date >= '" + sTime + "'"; // 前缀空格保障 
if (eTime) subQuery += " AND date <= '" + eTime + "'";

2. 类型转换标准化

CONCAT( 
CAST(ROUND(
 COALESCE(
 a.wkord_cnt * 100.0 / NULLIF((${subQuery}), 0), 
0 
), 2 ) 
AS CHAR),
 '%' ) AS wkord_percentage

3. 数学运算安全防护

  • NULLIF处理零分母NULLIF(subquery, 0)
  • COALESCE默认值COALESCE(..., 0)
  • 浮点精度保障:使用100.0代替100

完整实现代码

unction buildPercentageField(params) { 
const { sTime, eTime } = params;
// 构建子查询 
let subQuery = SELECT COUNT(event_id) 
FROM crm.payroll 
WHERE event_id IS NOT NULL AND event_id <> '';
// 动态时间条件(安全拼接) 
const conditions = []; 
if (sTime) conditions.push(date >= '${sTime}'); 
if (eTime) conditions.push(date <= '${eTime}'); 
if (conditions.length) { 
subQuery += ' AND ' + conditions.join(' AND '); }
// 构建百分比字段 
return CONCAT( CAST(ROUND( COALESCE( a.wkord_cnt * 100.0 / NULLIF((${subQuery}), 0), 0.0 ), 2 ) AS CHAR), '%' ) AS wkord_percentage; 
}

经验总结

  1. 动态SQL开发规范
    • 所有动态条件前必须包含空格
    • 使用参数化查询代替字符串拼接
    • 显式处理NULL和除零情况