ClickHouse的WITH-ALIAS是如何实现的

2023-11-03

ClickHouse的WITH-ALIAS是如何实现的

WITH-ALIAS包含相似但不同的两个特性:

  • WITH <表达式> as <别名>
  • WITH <别名> as <子查询>

WITH <表达式> as <别名> 特性

以下SQL展示了 WITH <表达式> as <别名> 特性的用法。

with c1 + 1 as c2 select c2 from t1;

运行的时候,select c2 中的别名c2会被改写为表达式c1+1。 使用With…Alias特性能够大大缩小SQL的大小。

WITH <表达式> as <别名> 的语法树

用EXPLAIN展现语法树。

explain ast with c1 as a1, f1() as a2 select a1, a2 from t1;

得到结果:

SelectWithUnionQuery (children 1)
 ExpressionList (children 1)
  SelectQuery (children 3)
   ExpressionList (children 2)
    Identifier c1 (alias a1)
    Function f1 (alias a2) (children 1)
     ExpressionList
   ExpressionList (children 2)
    Identifier a1
    Identifier a2
   TablesInSelectQuery (children 1)
    TablesInSelectQueryElement (children 1)
     TableExpression (children 1)
      TableIdentifier t1

以上语法树关键之处是用IdentifierFunction 内部包含alias别名属性,表示WITH子句中的"<表达式> as <别名>"。

所有继承于ASTWithAlias都带有alias别名属性。以下是继承树。

  ^ASTWithAlias$
  └── ASTWithAlias
      ├── ASTFunction
      ├── ASTLiteral
      ├── ASTQueryParameter
      ├── ASTSubquery
      └── ASTIdentifier
          └── ASTTableIdentifier

ASTWithAlias 可以被指定为一个alias别名的语法单元,包括 ASTFunction, ASTLiteral, ASTQueryParameter, ASTSubquery, ASTIdentifier。举个例子,sum(abc) as sum_value 就是为ASTFunction 指明一个别名sum_valuecolumn1 as c1也是另一个例子,为ASTIdentifier指定一个别名。

Enable_global_with_statement 开关

enable_global_with_statement开关会影响到表达式别名的处理。这个开关控制WITH…ALIAS是不是全局有效,“Propagate WITH statements to UNION queries and all subqueries”。如果关闭,ApplyWithAliasVisitor 和 ApplyWithGlobalVisitor 将不会工作,下面的SQL就会出错。

with 1 as a1
select a1
union all
select a1
union all
select a1
settings enable_global_with_statement =0;

settings enable_global_with_statement =0去掉就可以正常工作。

表达式别名的处理

表达式别名由ApplyWithAliasVisitor、ApplyWithGlobalVisitor、QueryAliasesVisitor、ExecuteScalarSubqueriesVisitor、QueryNormalizer访问者类来处理。

当打开enable_global_with_statement开关时,ApplyWithAliasVisitor和ApplyWithGlobalVisitor这两个访问者类会将表达式别名复制到subquery子查询和UNION语句中的其他SELECT查询(且称之为“兄弟查询”吧)中。

QueryAliasesVisitor访问者类会遍历整个AST树,搜集所有的表达式别名,构建出一个别名映射表,从String -> IAST。QueryNormalizer会根据收集的表达式别名把出现别名的地方替换成实际的AST(通过IAST的clone的新对象)。

ApplyWithAliasVisitor

遍历语法树,将外层的WITH的带别名的表达式(不包括子查询)复制到内层查询中。

with c1 + 1 as a1, c2 * 2 as a2
select * from (select a1, a2 from t1);

ApplyWithGlobalVisitor

将union select的第一个select的with子句传递给其他select语句。

以下是一个带with子句的union select的示例。

with 1 as a1
select a1
union all
select a1
union all
select a1

ApplyWithGlobalVisitor 会把第一个select上的WITH内容复制到其他select语句上。

QueryAliasesVisitor

收集AST树中的表达式别名,并且做以下两件事情:

  1. 对于没有alias别名的subquery子查询,此Visitor会为其加上唯一的alias别名;
  2. 保证其上的prefer_alias_to_column_name属性置为true。

为subquery子查询加上别名是因为subquery会被随后的优化器修改,导致以subquery内容生成subquery字段名的算法无法使用,因为当subquery的内容被优化器改变后,此算法生成的字段名就会更以前的不一样,这样会破坏一个不变式:同一个subquery在不同的时候对应的字段名必须一样

Subquery子查询在随后的处理中可能会变成字面值。例如把子查询(SELECT sum(number) FROM numbers(10)) 变为 (SELECT sum(number) FROM numbers(10)) as _subquery_1。这个子查询会被ExecuteScalarSubqueriesVisitor转换成最终的字面值,即(45 as _subquery_1)

需要显式地将prefer_alias_to_column_name 设置为true的原因是关于分布式表引擎Distributed engine的一个缺陷。对于分布式表引擎Distributed engine,查询被发到远程服务器时会丢失prefer_alias_to_column_name属性,因此对于带_subquery_前缀的别名的子查询或者是字面值,QueryAliasesVisitor会将其prefer_alias_to_column_name属性重新置为true。

对于ARRAY JOINLEFT ARRAY JOIN有特殊处理逻辑,跳过其AST树上的一级和二级子节点,专处理第三级子节点,这里不是很优雅,可能会隐藏着bug。

ExecuteScalarSubqueriesVisitor

将可以求值为一个单值的子查询替换成求出的单值,作为字面值替代原有的子查询。例如子查询(SELECT sum(number) FROM numbers(10)) as _subquery_1会变成(45 as _subquery_1)。其上的prefer_alias_to_column_name属性会被置为true。

QueryNormalizer

根据别名映射表,替换别名为实体AST。有一些设置开关控制其行为细节:

  1. max_expanded_ast_elements

    展开后的AST节点的数量上限。

  2. max_ast_depth
    AST树的深度限制。

  3. prefer_column_name_to_alias
    尽量用列名替代别名。

  4. allow_self_aliases
    允许a1 + 1 as a1这样的表达式别名,现在的代码中allow_self_aliases都为true。

WITH <别名> as <子查询> 特性

WITH...SUBQUERY是另外一个相似的特性,用于定义WITH上的子查询,在SQL的需要表的地方(例如FROM后面)可以引用。下面是一个例子:

with q1 as (select * from t1), q2 as (select * from t2) select * from q1;

WITH <别名> as <子查询>WITH <表达式> as <别名>虽然很类似但是生成的语法树差别很大。

WITH <别名> as <子查询> 的语法树

用EXPLAIN展现语法树。

explain ast with q1 as (select * from t1), q2 as (select * from t2) select * from q1;

得到结果:

SelectWithUnionQuery (children 1)
 ExpressionList (children 1)
  SelectQuery (children 3)
   ExpressionList (children 2)
    WithElement (children 1)
     Subquery (children 1)
      SelectWithUnionQuery (children 1)
       ExpressionList (children 1)
        SelectQuery (children 2)
         ExpressionList (children 1)
          Asterisk
         TablesInSelectQuery (children 1)
          TablesInSelectQueryElement (children 1)
           TableExpression (children 1)
            TableIdentifier t1
    WithElement (children 1)
     Subquery (children 1)
      SelectWithUnionQuery (children 1)
       ExpressionList (children 1)
        SelectQuery (children 2)
         ExpressionList (children 1)
          Asterisk
         TablesInSelectQuery (children 1)
          TablesInSelectQueryElement (children 1)
           TableExpression (children 1)
            TableIdentifier t2
   ExpressionList (children 1)
    Asterisk
   TablesInSelectQuery (children 1)
    TablesInSelectQueryElement (children 1)
     TableExpression (children 1)
      TableIdentifier q1

以上语法树关键之处是用WithElement 表示WITH子句中的"<别名> as <子查询>"。

如果一个查询为子查询,它是用ASTSubquery来表示,否则就是用ASTSelectQuery来表示。在ASTSelectQuery中包含WITH部分,通过with()方法获得。

子查询别名的处理

子查询别名的处理相对比较简单。访问者类ApplyWithSubqueryVisitor遍历AST树,替换别名为真实子查询AST实例。

ApplyWithSubqueryVisitor

遍历语法树,将WITH中的带别名的子查询替换引用子查询别名的地方。

其他细节

SELECT的语法树结构

即使只有一个select,也会被套进 SelectWithUnionQuery里面,形成以下三层结构。

  SelectWithUnionQuery (children 1)
   ExpressionList (children 1)
    SelectQuery (children 2)

用一个最简单的为例,

explain ast select 1;

生成的语法树,

SelectWithUnionQuery (children 1)
 ExpressionList (children 1)
  SelectQuery (children 1)
   ExpressionList (children 1)
    Literal UInt64_1

prefer_alias_to_column_name的作用

prefer_alias_to_column_name用于指示用别名作为内部的列的名字,这个可以避免为了得到内部列名而需要的大量的递归调用IAST::appendColumnName方法。

ASTArrayJoin

ARRAY JOINLEFT ARRAY JOIN是一种特殊的语法,在代码中经常被特殊处理。以下是它的AST树的示例:

CREATE TABLE arrays_test
(
    s String,
    arr Array(UInt8)
) ENGINE = Memory;

INSERT INTO arrays_test
VALUES ('Hello', [1,2]), ('World', [3,4,5]), ('Goodbye', []);

SELECT s, arr
FROM arrays_test
ARRAY JOIN arr;

以上SQL中的SELECT查询的AST为:

SelectWithUnionQuery (children 1)
 ExpressionList (children 1)
  SelectQuery (children 2)
   ExpressionList (children 2)
    Identifier s
    Identifier arr
   TablesInSelectQuery (children 2)
    TablesInSelectQueryElement (children 1)
     TableExpression (children 1)
      TableIdentifier arrays_test
    TablesInSelectQueryElement (children 1)
     ArrayJoin (children 1)
      ExpressionList (children 1)
       Identifier arr

总结

表达式别名比子查询别名在处理上要复杂的多,因为表达式本身的处理就很复杂。

别名机制可以减少SQL查询的大小,但是展开后的AST树的大小不会缩小。虽然如此,却可以在访问者模式的某些Visitor的处理过程中减少Visitor访问的工作量。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ClickHouse的WITH-ALIAS是如何实现的 的相关文章

随机推荐

  • CLIP论文解读

    文章目录 问题 方法 自然语言监督 数据集 有效预训练方法 模型选择 实验 Zero Shot Transfer 结论 论文 Learning Transferable Visual Models From Natural Language
  • 遗传算法(一) 遗传算法的基本原理

    遗传算法 一 遗传算法的基本原理 1 概述 遗传算法 Genetic Algorithm GA 起源于对生物系统所进行的计算机模拟研究 它是模仿自然界生物进化机制发展起来的随机全局搜索和优化方法 借鉴了达尔文的进化论和孟德尔的遗传学说 其本
  • 【Docker】如何运行容器?

    文章目录 容器操作 容器相关命令 创建并运行一个容器 docker命令解析 nacos启动成功 访问 进入容器 修改配置文件 总结 接上集 CentOS 7安装Docker https blog csdn net qq 39017153 a
  • unity中通过www加载外部音频

    IEnumerator DownloadVoice string url WWW w new WWW url yield return w 将声音资源赋值为外部加载的声音即可 myclip w audioClip 加载ogg格式的音频 au
  • 视频分类(Classification)和摘要(Captioning)总结

    想象力比知识更重要 爱因斯坦 论文 Deep Learning for Video Classification and Captioning 视频分类是指将大量的视频数据按照一定的标准和规则进行分类和归类 以便于用户快速找到自己感兴趣的视
  • 【MySQL】不就是子查询

    前言 今天我们来学习多表查询的下一个模块 子查询 子查询包括了标量子查询 列子查询 行子查询 表子查询 话不多说我们开始学习 目录 前言 目录 一 子查询 1 子查询的概念 2 子查询语法格式 2 1 根据子查询结果不同可以分为 2 2 根
  • 51单片机开发系列一-51单片机开发环境搭建以及入门汇编代码

    51单片机开发系列一 51单片机开发环境搭建以及入门汇编代码 象棋小子 1048272975 1 51单片机概述 51单片机是对所有兼容Intel 8031指令系统的单片机的统称 目前教科书基本都是以早期的MCS 51为原型 讲解微机的原理
  • Java上传Excel文件并解析入库,数据生成Excel文档(知识点满满)

    原因 我们经常会遇到甲方给数据是excel格式的数据 当然Navicat是支持导入excel的但是遇到客户想要自己上传Excel入库数据就要手动进行写代码了 一 解析Excel入库 param request param response
  • YOLOV7训练自己的数据集以及训练结果分析(手把手教你)

    YOLOV7训练自己的数据集以及训练结果分析 手把手教你 YOLOV7训练自己的数据集整个过程主要包括 环境安装 制作数据集 参数修改 模型测试 模型推理 一 环境安装 conda create n yolov7 python 3 8 co
  • python---的各种算法

    今天来聊聊python中的算法 比如AES DES RSA 1 MD5加密 md5是一个大的hash算法 它不存在解密的逻辑 市面上所为的解密是通过撞库来实现的 我们可以简单的理解为生活中的防伪码 1 不加salt 简单理解为密钥 from
  • 【语义分割】综述——一文搞定语义分割

    本文记录了博主阅读的关于语义分割 Semantic Segmentation 的综述类文章的笔记 更新于2019 02 19 语义分割 综述 一文搞定语义分割 参考文献网址 An overview of semantic image seg
  • 如何是matlab中的折线图变得更加的光滑?

    原来的代码如下 y2 Convergence2 m figure Name y2 Position 200 200 500 500 plot y2 title E2 xlabel Iteration ylabel Objective Fun
  • 网上学自动化测试靠谱吗 自己亲自试听一下去感受下就知道了

    众所周知 目前我国软件测试每年都要新增大量岗位 但学校还没有开展软件测试相关的课程 而企业培养的人才远远不足需求 因此自学和培训就成为目前主流的从事软件测试的两个最有效的途径 但相对自学来说 参与培训能够在较短时间内学到软件测试的相关知识
  • Claude2 AI实战:重新认识我们自己

    交流源于内心本真的需要 通过交流来降低信息的不对称 今天的交流对象是一个集大成者的老学者 当然是由 Claude2 扮演 相信会有不一样的收获 角色设定 你是一名集大成者的年迈学者 在哲学 社会学 历史 心理学等方面都有很高的造诣 现在我们
  • 【华为OD机试真题 Python】加扰字符串

    前言 本专栏将持续更新华为OD机试题目 并进行详细的分析与解答 包含完整的代码实现 希望可以帮助到正在努力的你 关于OD机试流程 面经 面试指导等 如有任何疑问 欢迎联系我 wechat steven moda email nansun09
  • 到底什么是Cortex、ARMv8、arm架构、ARM指令集、soc?一文帮你梳理基础概念【科普】

    更多信息请关注公众号 一口Linux 一 到底什么是Cortex ARMv8 arm架构 ARM指令集 soc 有粉丝问我到底什么是ARM 搞不清楚Cortex arm内核 arm架构 ARM指令集 soc这些概念都是什么关系 下面一口君给
  • xxljob 定时任务执行 报:job handler not found

    用xxljob做一个定时任务调度 执行以后出现执行失败 提示的找不到执行器 一般情况下是因为管理中心的JobHandler名称与代码中定义的不匹配 或者与pom文件引入的xxljob 版本不匹配 先检查一下这两个地方 在定时任务代码的入口文
  • 钩子函数(HOOK)完整的教程

    基本概念 钩子 Hook 是Windows消息处理机制的一个平台 应用程序可以在上面设置子程以监视指定窗口的某种消息 而且所监视的窗口可以是其他进程所创建的 当消息到达后 在目标窗口处理函数之前处理它 钩子机制允许应用程序截获处理windo
  • MySQL-分库分表详解(七)

    作者 小刘在C站 个人主页 小刘主页 努力不一定有回报 但一定会有收获加油 一起努力 共赴美好人生 学习两年总结出的运维经验 以及思科模拟器全套网络实验教程 专栏 云计算技术 小刘私信可以随便问 只要会绝不吝啬 感谢CSDN让你我相遇 前言
  • ClickHouse的WITH-ALIAS是如何实现的

    ClickHouse的WITH ALIAS是如何实现的 WITH ALIAS包含相似但不同的两个特性 WITH lt 表达式 gt as lt 别名 gt WITH lt 别名 gt as lt 子查询 gt WITH lt 表达式 gt