随着大数据技术的普及,以及使用门槛的降低。SQL已经作为计算引擎的标配,越来越重要,无论是老一代的仓库hive,还是现在流行的计算引擎spark或者flink都已经分别根据ANSI标准,支持SQL作为标准的查询语言。作为一名合格的数据测试工程师,成天面对庞大的提数ETL SQL。如果具备良好的SQL编写能力,显然实际的测试工作有非常大的帮助。作为SQL基础能力建设开篇文章,“SQL查询顺序”无疑是首选的目标。
一、SQL查询顺序
不夸张的说,很多写了多年程序的人,因为习惯了使用ORM框架,已经很少手写SQL了,对SQL的执行顺序多半都已经印象模糊。问起,可能只能说个大概。然而在数据测试中,尤其是spark,flink中,当对象为parquet文件或者stream log时,现行并没有合适的ORM框架进行使用。这也是为什么必须要深刻理解SQL执行顺序的原因。
SQL语言不同于其他编程语言,最明显的不同体现在处理代码的顺序上。在大多数编程语言中,代码按编码顺序被处理。但在SQL语言中,第一个被处理的子句总是FROM子句
from t1 # 准备初始数据
join t2 Join # 另外需要join的数据
on t1.id = t2.user_id # 连接条件
where x>10 # 过滤条件
group by y # 分组
having sum(x)>10 # 分组后过滤
select calculation expression(distinct) # 查询结果
union # 联表
order by # 过滤结果
limit (offset) # 限制输出条数
最先执行的是FROM操作,最后执行的是LIMIT操作。每个操作都会产生一张虚拟表,该虚拟表作为一个处理的输入。spark与flink对SQL的处理会变成物理执行计划,每个SQL步骤实际上对应了不同的算子,每部产生的结果会变成引擎内特有的数据集(这里不扩展)。这些虚拟表(数据集)对用户是透明的,只有最后一步生成的虚拟表才会返回给用户。。如果没有在查询中指定某一子句,则将跳过相应的步骤。看懂如上的顺序,便能迅速判断一个给定的 SQL 查询将会返回什么结果,也可以轻松解答如下疑问:
- 可以对
GROUP BY
的结果进行WHERE
筛选吗? - 可以对窗口函数的执行结果进行过滤吗?
- 可以对
GROUP BY
的结果再执行ORDER BY
操作吗?
诸如此类的问题。
虽然如此,但实际上数据库/大数据引擎并非严格按照这个顺序运行查询,因为它们还会执行一系列的优化,以便提升查询速度。
所以:
- 当你想了解查询语句的有效性,或是想搞明白为什么会返回这样一个查询结果时,可以尝试用该图来解释;
- 但是,使用该图是无法解释查询性能或索引相关问题的,它们会涉及到更多变量,因而也更为复杂。
二、别名计算
随意使用一张表数据来演示。
SQL 语法是允许这样写:
SELECT CONCAT(username, ' hello') AS hello, count(*)
FROM `user`
GROUP BY hello
在MySQL实际测试中结果:
在spark SQL中实际测试结果:
spark-sql> SELECT CONCAT(username, ' hello') AS hello, count(*)
> FROM (
> select "tailou" as username
> UNION
> select "diga" as username) as t
> GROUP BY hello;
diga hello 1
tailou hello 1
Time taken: 1.808 seconds, Fetched 2 row(s)
在flink SQL中实际测试结果:
Flink SQL> SELECT CONCAT(username, ' hello') AS hello, count(*) FROM (select 'tailou' as username UNION select 'diga' as username) as t GROUP BY hello;
[ERROR] Could not execute SQL statement. Reason:
org.apache.calcite.sql.validate.SqlValidatorException: Column 'hello' not found in any table
Flink SQL> SELECT CONCAT(username, ' hello') AS hello, count(*) FROM (select 'tailou' as username UNION select 'diga' as username) as t GROUP BY CONCAT(username, ' hello');
[INFO] Result retrieval cancelled.
上面的查询,在MySQL与spark中看起来像是在 SELECT
之后执行 GROUP BY
,但其实 GROUP BY
是先执行的,因为 GROUP BY
引用了 SELECT
中的 alias
。
引擎是可以将查询重写为:
SELECT CONCAT(username, ' hello') AS hello, count(*) FROM (select 'tailou' as username UNION select 'diga' as username) as t GROUP BY CONCAT(username, ' hello');
接着,先执行 GROUP BY
中的语句,再进行 SELECT
操作,所以上面那么写是可行的。引擎肯定会执行一系列检查,以确保在查询开始运行之前,SELECT
和 GROUP BY
中的内容相匹配,因此在制定执行计划之前,它必须将查询语句当作一个整体来检查。
与他们不同是,flink无法做到此类优化,而是需要改这些成正常的SQL顺序才可以正常执行。这里需要注意。
写在最后
通过探究 SQL 查询语句的执行顺序,希望能帮助到更多的人理解 SQL 的执行顺序以及如何正确编写 SQL 查询语句。另外也从实际测试中,发现实际上在面对不同引擎的时候,SQL的书写有一定的“方言”区分。就spark而言,目前也有基于ANSI与非ANSI的版本。故而平常在实际工作工作中,需要多练习,多积累。才能在后续的工作中得心应手。那么就以此篇,开启后续的SQL基础能力学习。所有的后续文章也都会附上MySQL、spark、flink三中不同引擎下的实例以示区别。
参考文献: