简介
本文讨论 sql 单表执行计划工具类 SqlPartFieldListExplain.
此工具类会将单行 sql 中的内容区分出不同的执行优先级.
比如: (sum(a)/sum(b)) as c, 应该先执行 sum(a) 和 sum(b), 延后执行除法.
需求
日常的查询语句中 select 部分不同字段之间是有执行优先级差异的.
在暂时不考虑 where 和 having 的情况下, 比如:
SELECT a, cast(b as SIGNED) as c, (a+1) as d, sum(c) as e, (1/e) as f, (sum(g)/sum(h)) as i from table group by d
其执行计划为:
- 步骤1: 获取基础字段
SELECT a, b, g, h
FROM table
蓝色的 a 为 直接指定的字段;
红色的 b,g,h 为函数中引用的字段;
c, d, e, f, i 为计算后派生出来的字段当前阶段不需要处理
- 步骤2: 执行 group by 前置计算, 互相依赖的参数按顺序执行
SELECT a, cast(b as SIGNED) as c, (a+1) as d, g, h
FROM 上一步结果
其中蓝色的为 直接指定的部分;
红色的为函数中引用的字段;
e, f, i 为计算后派生出来的字段当前阶段不需要处理
- 步骤3: 执行 group by
SELECT max(a) as a, max(c) as c, d, sum(c) as e, sum(g) as __sum_g, sum(h) as __sum_h
FROM 上一步结果
GROUP BY d
a, c 为 未被聚合函数和 group by 属性命中的游离字段. mysql中对游离字段不承诺返回值运算方式,因此我们用max统一处理
__sum_g, __sum_h 为派生出来的临时字段, 仅在 group by 阶段使用, 不出现在最终的返回值中
- 步骤4: 执行 group by 后置计算, 互相依赖的参数按顺序执行
SELECT a, c, d, e, (1/e) as f, (__sum_g/__sum_h) as i
FROM 上一步结果
i 为组装历史字段得到
SqlPartFieldListExplain 实现
a. 将中 SqlPartFieldList 的基础字段记录在 dependFields 中, 用于支持步骤1
b. 将中 SqlPartFieldList 中的复合字段拆分为 originalFields, temporaryFields, compositeFields
分类 | 说明 | sql来源 | sql输出 |
---|---|---|---|
originalFields | sql语句中显式指定的字段及其函数(其参数不包含聚合成分) | a, cast(b as SIGNED) as c, (a+1) as d, sum(c) as e | |
temporaryFields | sql语句中隐式包含的聚合函数 | (sum(g)/sum(h)) as i | sum(g) as __sum_g, sum(h) as __sum_h |
compositeFields | sql语句中显式指定的字段及其函数(其参数中的聚合成分被替换为temporaryFields的字段名) | (1/e) as f, (sum(g)/sum(h)) as i | (1/e) as f, (__sum_g/__sum_h) as i |
c. 将 originalFields, temporaryFields, compositeFields 从新组合为执行计划
步骤 | 说明 | 成分 | sql输出 |
---|---|---|---|
步骤1 | 获取基础字段 | dependFields, 表名 | SELECT dependFields FROM 表名 |
步骤2,步骤3 | 执行 group by及其前置函数 | originalFields, temporaryFields | SELECT originalFields, temporaryFields FROM 上一步结果 |
步骤4 | 执行 group by 后置计算 | originalFields.asFieldName, compositeFields | SELECT originalFields.asFieldName, compositeFields FROM 上一步结果 |