Skip to main content

用一段sql作为指标计算的模板

在实际的项目中,往往有些指标已经有写好的大段逻辑复杂的sql了。在这之中,只要做到类似于填槽的工作即可。本文档来介绍一下如何实现sql代码填槽功能。

假设现在有一张表,schema如下:

输入图片说明

现在我们要实现一个叫test的指标,他最终的sql版本计算公式为:

SELECT 
sum(case when dim1 = '${问答中dim1的值}' then qty else 0 end) /
sum(case when dim2 = '${问答中dim2的值}' then qty else 0 end) AS r
FROM e1
${问答中其他筛选条件,例如时间条件}

这种情况下,代码配置如下(具体解释见备注):

async (lf, _self, config, { execute, commonLib, moment, DBConnector }) => {
// 问答中dim1的值和问答中dim2的值先从logicform中拿出来
const dim1Query = lf.query.维度1;
const dim2Query = lf.query.维度2;

// 可以根据业务需求适当报错
if(!dim1Query) throw new Error('此指标必须要带上维度1');
if(!dim2Query) throw new Error('此指标必须要带上维度2');

// 保留全局筛选条件(例如时间)
const globalQuery = JSON.parse(JSON.stringify(lf.query));
delete globalQuery.维度1;
delete globalQuery.维度2;

// 用globalQuery替换老的lf。输出模式选择sqlComponents获取其中的sql。可以把Sql中的全局where条件拿到。
const sqlComponents = await execute({
...lf,
preds: [{"operator":"$sum", "pred": "销量"}], // 一定要覆盖掉preds,不然会无限循环调用,可以随便搞一个
query: globalQuery,
output: {
type: 'sqlComponents' // 这里是核心,让execute函数不执行实际的sql,而是单纯地返回sql的各个部分。
}
})

// 打印调试,可以在执行系统设置 -> 日志与记录 -> 后台日志 处查看
console.log('DEBUG sqlComponents: ', JSON.stringify(sqlComponents.result[0]));


// 根据变量生成最终的SQL
const sqlTemplate = `
SELECT
sum(case when dim1 = '${dim1Query}' then qty else 0 end) /
sum(case when dim2 = '${dim2Query}' then qty else 0 end) AS r
FROM e1
${sqlComponents.result[0].where}
`

console.log('DEBUG sqlTemplate: ', sqlTemplate);

// 把生成的SQL放在的sqls记录中,用于前端Debug
config.sqls.push(sqlTemplate);

// 跑一下SQL
const result = await DBConnector.runSQL(sqlTemplate);

console.log('DEBUG result: ', result);

// 把sql的结果,按照我们的需求的结构返回出来。
return [{_id: "0", [lf.preds[0].name]: result[0].r}]
}

最后问答效果:

输入图片说明

最后生成的sql

SELECT  sum(case   when dim1 = 'd1' then qty
else 0
end) / sum(case when dim2 = 'd2' then qty
else 0
end) AS r
FROM e1
WHERE `e1`.`time` >= '2025-01-01 00:00:00'
AND `e1`.`time` <= '2025-03-12 23:59:59';

分组怎么办

如果客户问到带分组的问题。sql实现还要考虑groupby,可能过于复杂。这个时候,可以在shouldSplitGroupby函数中配置:

() => false