背景:在工作中遇到了当使用select * from view时,若字段取了别名,当对它进行了列裁剪之后,会出现别名消失的情况
问题定位:
1.首先介绍一下列裁剪,假设a表有字段a、b、c,b表有字段d、e、f,将a和b表做Join。但最后我们查询所需要的字段只有a、b、d,
那么我们可以在Join之前,只获取a、b、d字段和join条件的字段即可,不需要获取更多的数据。
2.那么为什么会出现别名消失?
因为我们查询的是全字段,即所有的数据都可以从Project的子节点中获得,那么该Project字段从获取全部数据的角度来说 确实没有存在的必要。
calcite中可以看到在列裁剪时,会构造一个裁剪完成的Project算子,该算子就是需要用到哪些字段则构造那些字段的一个Project
但是进入relBuilder.project()方法中,可以看到传入了当前的rowType即包含别名的rowType。在该方法中也做了判断,子算子的rowType和传入的rowType是否相同,按理说会在其中进行一个别名的判断,若有别名则不消除该全字段的Project。但是Calcite并没有这么做,所以我认为这可能是一个小bug或者也可能是我理解错误。
解决方案:
由上述可知,别名消除的根本原因是因为对于全字段的Project,会被认为没有必要存在,所以我认为的一种解法就是重写Calcite中的
public TrimResult trimFields(Project project, ImmutableBitSet fieldsUsed, Set extraFields)
如果使用到的字段是子算子中的全字段,那么就不要消除该字段,继续递归下去。
@Override
public TrimResult trimFields(Project project, ImmutableBitSet fieldsUsed, Set<RelDataTypeField> extraFields)
{
TrimResult result = super.trimFields(project, fieldsUsed, extraFields);
// if project fieldsUsed equals fieldsCount of child, then do not remove current project
if (result.left.getRowType().getFieldCount() == fieldsUsed.cardinality()) {
result = result(project, result.right);
}
return result;
}
第二种解法就是修改Calcite的源代码,在两个rowType中名字不同时,会保留Project(当然真正的过程时再生成一个和原先Project相同的Project)
拓展:下面再浅谈一下TrimFields的基本原理,便于更好理解以上解决方案的原因。
以一个简单的例子来讲述这个过程:
假设当前的查询结构如下:
LogicalProject[alias,b,c,d]
LogicalProject[a,b,c,d]
Scan[table1]
首先第一步:
传入当前的根节点,获取它使用到了哪些字段,并通过dispatchTrimFields()进行分发,对不同的节点会有不同的处理
第二步:
分发到Project算子,这是一个递归的操作,递归前会分析当前算子获取它用到的字段,然后再进入下一个算子。
最后再自底向上进行判断,以上例子,scan上方的Project算子,选中Scan全字段因此会变成如下
LogicalProject[alias,b,c,d]
Scan[table1]
再递归向上找到最顶层的Project,也是全字段的Project,因此最后就会变成
Scan[table1]
所以现在回顾我们的解法,当递归到底,再向上时判断是否全字段,若是全字段则可能会出现别名消失情况,因此跳过。