SQLazy 是不是你的 dbt 工作流里缺的那一环
dbt虽好亦有盲区
如果你在做 analytics engineering,dbt 大概率已经在工作流里了。把 ELT 中的 T 交给它,SQL 变成可测试、可文档化、可协作的代码,遇到个简单转换比如 JOIN、CASE、GROUP BY,应付起来得心应手。
但当分析逻辑变得复杂,比如涉及窗口函数嵌套、条件分段、会话化、累计重置等场景时,dbt model 里的 SQL 迅速膨胀。嵌套层级一多,代码就变成了一堵墙:写出来没人能读,没人敢改,改了不知道对不对。三个月后需求变了,你打开那段 model,盯着五个 CTE 和三个窗口函数发呆——改,怕塌;不改,数据错了也没人发现。最后只能绕过去,或者干脆放弃这个需求。
dbt解决了 SQL 工程化的 "管道" 问题,但管不了复杂分析逻辑的 "设计过程"。需要一种方式,在 dbt 之外先把复杂逻辑设计清楚、验证通过,再把干净的 SQL 嵌入 model。
讲一个会话化的真实例子
业务逻辑:用户行为事件表,按时间间隔重置会话编号,超过 1 小时无活动,则新开一个会话。这是行为分析的基础,用户停留时长、会话转化率、留存漏斗,全都依赖它。dbt 社区里几乎每个 analytics engineer 都遇到过这类会话 / 事件查询。
在 SQL 里实现这个逻辑,通常需要三层嵌套 CTE:先用 LAG 取上一条时间戳,再用 CASE WHEN 累加判断是否跨会话,最后用 ROW_NUMBER 生成序号。
WITH lagged AS (
SELECT *, LAG(dt) OVER (PARTITION BY account_number ORDER BY dt) AS prev_time
FROM event_tb
), grouped AS (
SELECT *,
SUM(CASE WHEN TIMESTAMPDIFF(SECOND, prev_time, dt) > 3600 THEN 1 ELSE 0 END)
OVER (PARTITION BY account_number ORDER BY dt) AS grp
FROM lagged
)
SELECT *, ROW_NUMBER() OVER (PARTITION BY account_number, grp ORDER BY dt) AS seq
FROM grouped
嵌套层级多,改任何一处都要从最内层往外逐层验证。SQL 能跑,但没人愿意维护它——三个月后要增加点逻辑,比如跨过 0 点时会话都算作新的,你得从最内层的 LAG 看懂这段代码开始改,逐层确认逻辑是否还成立,整个查询相当于重写一遍。
SQLazy的解法:分步设计,编译嵌入
把复杂分析逻辑拆成一步步清晰的操作,逐步验证,然后编译成 SQL 嵌入 dbt。
回到会话化这个例子,用 SQLazy 分步编写:
Name |
Anchor |
Statement |
T1 |
event_tb |
sort account_number asc dt asc |
T2 |
segment condition ((dt[-1] elapse 3600 second)<= dt) partition account_number as grp |
|
T3 |
compute # as seq partition account_number grp |
在线运行本例:https://www.sqlazy.com/?4M4
3 步,和人类在脑子里想的逻辑顺序完全一致。
第 1 步,按用户和时间排序,确保事件按时间顺序处理
sort account_number asc dt asc

第 2 步:按时间间隔分段,标记会话 ID
segment condition ((dt[-1] elapse 3600 second)<= dt) partition account_number as grp
这是关键的一步。segment 用于分段:遍历每个用户的数据,当相邻事件的时间间隔超过 1 小时,就新开一个组。dt[-1] elapse 3600 second 表示 "上一行的时间加上 3600 秒",如果这个值小于等于当前行的时间,说明间隔没超 1 小时,同组;否则新增组。account_number 确保每个用户独立分段。
执行后,中间表会多出一列 grp,同一个数字代表同一个会话。分段对不对,当场就知道。

第 3 步:在每个会话内生成递增序号
compute # as seq partition account_number grp
compute用于计算列。# 是行号,在每个 account_number + grp 的组合内生成从 1 开始的递增序号。partition 指定分区或分组维度,确保序号在每个会话内独立编号。

每一步都可以单独执行、预览中间结果。改间隔阈值,只改 T2 一行,其他步骤不变;分段对不对,执行完 T2 当场就能看到 grp 的变化。不需要在脑子里展开嵌套,不需要推理 "这一层改了会不会影响上一层"。
Workflow(SQLazy步骤 ) 设计完,逻辑确认无误,点一下 "编译"。SQLazy 的编译器把 workflow 确定性地转换成目标数据库的原生 SQL——MySQL、PostgreSQL、Snowflake、BigQuery,切换一个选项即可。把编译后的 SQL 粘贴到 dbt model 的 .sql 文件里,dbt 负责物化、测试、文档、血缘。
SQLazy不替代 dbt,它补的是 dbt 管不到的那一环:复杂分析逻辑的设计和验证。
workflow是活文档。三个月后需求变了,打开 workflow 看每一步就知道逻辑是什么,改对应的步骤即可。编译器保证 SQL 准确——不是 AI 猜测生成,是确定性编译,同一 workflow 永远生成同一 SQL。
为什么用 SQLazy,而不是手写
用户可能会想:我直接手写就行了,复杂一点而已。
问题是,"复杂一点" 在 SQL 里的代价不是线性增长的。窗口函数嵌套两层和嵌套四层,维护难度往往差一个量级,而分析需求往往就是越做越复杂的。
SQLazy嵌入 dbt 工作流后,具体带来这些变化:
步骤级调试。像调试代码一样查看每个中间表,不需要在 dbt 里加 debug 字段反复跑 dbt run。第 2 步分段分错了,当场修正,后面步骤自动重算。
逻辑即文档。workflow本身就是可读的逻辑描述。新人打开 workflow,几分钟就能理解这段分析在做什么,不需要翻 dbt 的 docs 或者去问写代码的人。
跨库方言。同一份 workflow随时编译成 MySQL、PG、Snowflake、BigQuery 方言。dbt 项目从 PostgreSQL 迁到 Snowflake,分析逻辑不用重写,编译器自动适配。
LLM辅助设计。把口语化的步骤描述转成规范的 workflow,降低复杂逻辑的设计门槛。AI 只负责 "翻译",不负责 "决策",最终 SQL 由编译器确定性生成,零幻觉。
当然,SQLazy 有它的边界。简单 CRUD 查询不需要它,三五行就能写完的 SQL 用它是杀鸡用牛刀。它真正发挥威力的地方,恰好是 dbt 用户最头疼的部分:复杂到需要分步拆解的分析逻辑。
写在最后
dbt控制转换的管道,SQLazy进行分析逻辑的设计。两者不是替代关系,是互补。dbt 负责把分析结果物化、测试、文档化、追踪血缘;SQLazy 负责让你在写那些复杂分析 SQL 之前,先把逻辑想清楚、验证通过。
如果你的 dbt model 里有一段超过 30 行的分析 SQL,试试先在 SQLazy 里拆成步骤。
在线体验:sqlazy.com(免费,无需注册)
安装包下载地址:https://www.raqsoft.com.cn/download-NaturalSPL
