表达式与计划构建器
概述
逻辑计划和表达式在 SQL 查询的生命周期中扮演着重要角色。 本文档旨在解释表达式和计划构建器的新 设计。
表达式
别名表达式
别名在 SQL 中非常有用,我们可以将一个复杂的表达式别名为一个简短的别名名称。例如:SELECT a + 3 as b
。
在标准 SQL 协议中,别名可以在以下情况下使用:
- Group By, 例如:
SELECT a + 3 as b, count(1) from table group by b
- Having, 例如:
SELECT a + 3 as b, count(1) as c from table group by b having c > 0
- Order By: 例如:
SELECT a + 3 as b from table order by b
ClickHouse 扩展了表达式别名的使用,它可以在以下情况下工作:
-
递归别名表达式:例如:
SELECT a + 1 as b, b + 1 as c
-
过滤:例如:
SELECT a + 1 as b, b + 1 as c from table where c > 0
注意:目前我们不支持 ClickHouse 风格的别名表达式。这可以在以后实现。
对于表达式别名,我们只在最后处理,在投影阶段。但我们必须尽早替换表达式的别名,以防止后续的歧义。
例如:
SELECT number + 1 as c, sum(number) from numbers(10) group by c having c > 3 order by c limit 10
- 首先,我们可以从投影 AST 中扫描所有的别名表达式。
c ---> (number + 1)
- 然后我们在 having、order by、group by 子句中将别名替换为相应的表达式。因此查询将变为:
SELECT number + 1 as c, sum(number) from numbers(10) group by (number + 1) having (number + 1) > 3 order by (number + 1) limit 10
- 最后,当查询完成时,我们应用投影将列
(number+1)
重命名为c
让我们看一下这个查询的解释结果:
| Limit: 10
Projection: (number + 1) as c:UInt64, sum(number):UInt64
Sort: (number + 1):UInt64
Having: ((number + 1) > 3)
AggregatorFinal: groupBy=[[(number + 1)]], aggr=[[sum(number)]]
RedistributeStage[state: AggregatorMerge, id: 0]
AggregatorPartial: groupBy=[[(number + 1)]], aggr=[[sum(number)]]
Expression: (number + 1):UInt64, number:UInt64 (Before GroupBy)
ReadDataSource: scan partitions: [4], scan schema: [number:UInt64], statistics: [read_rows: 10, read_bytes: 80]
我们可以看到,我们不需要在投影之前关心别名,因此应用其他表达式会非常方便。
物化表达式
物化表达式处理是指如果上游已经处理了相同的表达式,我们可以将表达式重新定义为 ExpressionColumn。
例如:
SELECT number + 1 as c, sum(number) as d group by c having number + 1 > 3 order by d desc
在别名替换之后,我们将知道 order by 是 sum(number)
,但 sum(number)
已经在聚合阶段处理过了,因此我们可以将 order by 表达式 SortExpression { ... }
重新定义为 Column("sum(number)")
,这可以消除相同表达式的无用计算。
因此,having 中的 number + 1
也可以应用重新定义表达式。
表达式函数
表达式函数有很多种。
- ScalarFunctions,一对一的计算过程,结果行数与输入行数相同。例如:
SELECT database()
- AggregateFunctions,多对一的计算过程,例如:
SELECT sum(number)
- BinaryFunctions,一种特殊的 ScalarFunctions 例如:
SELECT 1 + 2
- ...
对于 ScalarFunctions,我们并不关心整个块,我们只关心参数涉及的列。sum(number)
只关心名为 number 的列。结果也是一个列,因此我们在 IFunction
中有虚拟方法:
fn eval(&self, columns: &[DataColumn], _input_rows: usize) -> Result<DataColumn>;
对于 AggregateFunctions,我们应该在相应的函数实例中保持状态以应用两级合并,我们在 IAggregateFunction
中有以下虚拟方法:
fn accumulate(&mut self, columns: &[DataColumn], _input_rows: usize) -> Result<()>;
fn accumulate_result(&self) -> Result<Vec<DataValue>>;
fn merge(&mut self, _states: &[DataValue]) -> Result<()>;
fn merge_result(&self) -> Result<DataValue>;
过程 是 accumulate
(将数据应用于函数)--> accumulate_result
(获取当前状态)--> merge
(从其他状态合并当前状态)---> merge_result
(获取最终结果值)
ps:我们不在函数中存储参数类型和参数名称,如果需要,我们可以在以后存储它们。
列
Block 是流之间传递的数据单元,用于管道处理,而 Column 是表达式之间传递的数据单元。 因此,在表达式的视角(函数、字面量等),一切都是 Column,我们有 DataColumn 来表示一列。
#[derive(Clone, Debug)]
pub enum DataColumn {
// 值的数组。
Array(DataArrayRef),
// 单个值。
Constant(DataValue, usize)
}
DataColumn::Constant 类似于 ClickHouse 中的 ConstantColumn。
注意:我们没有 ScalarValue