代数
关系代数是方解石的核心。每个查询都表示为关系运算符树。您可以从SQL转换为关系代数,也可以直接构建树。
规划器规则使用保留语义的数学标识来转换表达式树。例如,如果过滤器未引用其他输入的列,则将过滤器推入内连接的输入是有效的。
Calcite通过重复将规划器规则应用于关系表达式来优化查询。成本模型指导流程,计划程序引擎生成一个替代表达式,该表达式具有与原始语义相同的语义,但成本较低。
规划过程是可扩展的。您可以添加自己的关系运算符,计划程序规则,成本模型和统计信息。
代数建设者永久链接
构建关系表达式的最简单方法是使用代数构建器 RelBuilder。这是一个例子:
TableScan
final FrameworkConfig config;
final RelBuilder builder = RelBuilder.create(config);
final RelNode node = builder
.scan("EMP")
.build();
System.out.println(RelOptUtil.toString(node));
代码打印
LogicalTableScan(table=[[scott, EMP]])
它创建了一个EMP表的扫描; 相当于SQL
SELECT * FROM scott.EMP;
添加项目永久链接
现在,让我们添加一个Project,相当于
SELECT ename, deptno FROM scott.EMP;
我们只是project在调用之前添加对方法的调用 build:
final RelNode node = builder
.scan("EMP")
.project(builder.field("DEPTNO"), builder.field("ENAME"))
.build();
System.out.println(RelOptUtil.toString(node));
而输出是
LogicalProject(DEPTNO=[$7], ENAME=[$1])
LogicalTableScan(table=[[scott, EMP]])
这两个调用builder.field创建了简单的表达式,返回输入关系表达式中的字段,即scan调用创建的TableScan 。
方解石已经通过序数将它们转换为字段引用, 1。
添加过滤器和聚合永久链接
使用Aggregate和Filter的查询:
final RelNode node = builder
.scan("EMP")
.aggregate(builder.groupKey("DEPTNO"),
builder.count(false, "C"),
builder.sum(false, "S", builder.field("SAL")))
.filter(
builder.call(SqlStdOperatorTable.GREATER_THAN,
builder.field("C"),
builder.literal(10)))
.build();
System.out.println(RelOptUtil.toString(node));
等同于SQL
SELECT deptno, count(*) AS c, sum(sal) AS s
FROM emp
GROUP BY deptno
HAVING count(*) > 10
并生产
LogicalFilter(condition=[>($1, 10)])
LogicalAggregate(group=[{7}], C=[COUNT()], S=[SUM($5)])
LogicalTableScan(table=[[scott, EMP]])
推送和弹出永久链接
构建器使用堆栈来存储由一步生成的关系表达式,并将其作为输入传递给下一步。这允许生成关系表达式的方法生成构建器。
大多数情况下,您将使用的唯一堆栈方法是build()
获取最后一个关系表达式,即树的根。
有时堆栈变得如此深入嵌套会让人感到困惑。为了保持正确,您可以从堆栈中删除表达式。例如,我们在这里建立一个浓密的连接:
.
join
/ \
join join
/ \ / \
CUSTOMERS ORDERS LINE_ITEMS PRODUCTS
我们分三个阶段构建它。存储在变量中间结果 left和right,并用push()把他们回来的时候,是时候创建最终在栈上Join:
final RelNode left = builder
.scan("CUSTOMERS")
.scan("ORDERS")
.join(JoinRelType.INNER, "ORDER_ID")
.build();
final RelNode right = builder
.scan("LINE_ITEMS")
.scan("PRODUCTS")
.join(JoinRelType.INNER, "PRODUCT_ID")
.build();
final RelNode result = builder
.push(left)
.push(right)
.join(JoinRelType.INNER, "ORDER_ID")
.build();
字段名称和序号永久链接
您可以按名称或序号引用字段。
普通是从零开始的。每个运算符都保证其输出字段的顺序。例如,Project返回由每个标量表达式生成的字段。
保证运算符的字段名称是唯一的,但有时这意味着名称并不完全符合您的预期。例如,当您将EMP加入DEPT时,其中一个输出字段将被称为DEPTNO,另一个将被称为DEPTNO_1。
一些关系表达式方法可以让您更好地控制字段名称:
project允许你使用包装表达式alias(expr, fieldName)。它会删除包装器但保留建议的名称(只要它是唯一的)。
values(String[] fieldNames, Object... values)接受一组字段名称。如果数组的任何元素为null,则构建器将生成唯一名称。
如果表达式投影输入字段或输入字段的强制转换,它将使用该输入字段的名称。
一旦分配了唯一的字段名称,名称就是不可变的。如果您有特定RelNode实例,则可以依赖不更改的字段名称。实际上,整个关系表达式是不可变的。
但是如果关系表达式已经通过了几个重写规则(参见(RelOptRule)),结果表达式的字段名称可能看起来不像原始文件。此时最好通过序数引用字段。
在构建接受多个输入的关系表达式时,需要构建将其考虑在内的字段引用。在构建连接条件时最常发生这种情况。
假设您正在EMP上构建一个连接,它有8个字段[EMPNO,ENAME,JOB,MGR,HIREDATE,SAL,COMM,DEPTNO]和DEPT,它有3个字段[DEPTNO,DNAME,LOC]。在内部,Calcite将这些字段表示为具有11个字段的组合输入行的偏移:左输入的第一个字段是字段#0(基于0,记住),右输入的第一个字段是字段#8。
但是通过构建器API,您可以指定哪个输入的字段。到参考“SAL”,内部字段#5,写builder.field(2, 0, "SAL"),builder.field(2, "EMP", "SAL")或builder.field(2, 0, 5)。这意味着“两个输入的输入#0的字段#5”。(为什么需要知道有两个输入?因为它们存储在堆栈中;输入#1位于堆栈顶部,输入#0位于堆栈顶部。如果我们没有告诉构建器是两个输入,它不知道输入#0的深度。)
类似地,参考“DNAME”,内部字段#9(8 + 1),写入builder.field(2, 1, "DNAME"),builder.field(2, "DEPT", "DNAME")或builder.field(2, 1, 1)。
网友评论