现如今因为有了计算引擎各种source与sink连接器的支持,ETL的开发人员可以聚焦全部的精力在extraction与translation中。其中的重点自然是编写各种SQL从不同的数据源中抽取数据进行转化操作。所以编写SELECT语句约等于就是全部的工作内容。掌握了SQL的select语法,无疑掌握了数据测试的取胜之匙。
SELECT 基础
SELECT语句用于从数据库表中查询数据。它允许人为指定要获取表中的哪些列以及行的标准。返回的数据称为结果集,并显示在开发工具中,或由运行查询的应用程序使用。SELECT语句可以很简单,它们可以从一个小表中选择一些列。或者,它们可能很复杂,有很多列和复杂计算。文章将在MySQL与flink、spark中查看SELECT语句的语法差异与具体的使用案例。
基本语法
SELECT [ ALL | DISTINCT ] { 字段列表(*) }
[ INTO 表1 ]
FROM { 数据源 }
[ WHERE 表达式 ]
[ GROUP BY 分组表达式 ]
[ HAVING 分组查询表达式 ]
[ ORDER BY 排序表达式 ]
以上的表达式中
- {} 是强制性的,例如选择列表
- | 是可选项,例如DISTINCT
- [] 为可选参数,如WHERE
当计算SELECT语句时,系统首先计算FROM子句,然后计算SELECT子句。 执行顺序在第一篇文章汇总已经说的很清楚,这里顺便提一下。
MySQL在默认情况下,对大小写并不敏感。所以在MySQL中SELECT与select并没有实质性的区别,这也是我在文章里面可以随便用大写的原因。如
SELECT * from (SELECT 1 as id,'diga' as 'name' UNION SELECT 1 as `no`,'japan' as 'courtry') t
与
select * from (select 1 as id,'diga' as 'name' UNION select 1 as `no`,'japan' as 'courtry') t
这在spark于flink中也适用,所以不做特别的演示。不过为了让SQL可读性更佳,这里建议使用大写字母来描述SQL关键字,使用小写字母来写描述字段或者表名。
简单查询
select * from table
表示从table中查所有的字段,如果有编程经验的同学,可能会比较清楚。当表结构发生变动,如果使用星号 ()并且不更改应用程序代码以使其与新表结构一起工作,则应用程序可能无法正常工作。另外,这样的查询在性能上存在瓶颈,会返回非常多无用的字段。显然无论从功能层面还是性能层面,使用''都不是一个很好的决定。另一种是直接根据列名获取数据:
select column,column1,... from table
通过字段名列表来获取数据。
PS:相关简单查询case可以返回前两章进行查阅,本文不再进行演示。
表达式查询
select 1+1
如上SQL所示,在select语句中也可执行一些简单的表达式运算。这在实际的编写过程中很有帮助,能够预处理一些逻辑。另外值得一提是,在where条件中我们也能使用算术表达式,但是不能对索引字段进行运算,尤其是在生产上。不然可能就要准备去财务室结账了。在后面介绍where的时候再详细说明。
MySQL 实际测试
spark 实际测试
spark-sql> select 1+1 ;
2
Time taken: 0.225 seconds, Fetched 1 row(s)
flink 实际测试
当然,select还支持常量查询,可以理解为为某个临时表固定插入某个值。如
SELECT *,"cq_test" from (SELECT 1 as id,'diga' as `name` UNION SELECT 1 as `no`,'japan' as `courtry`) t
mysql 实际测试
spark 实际测试
spark-sql> SELECT *,"cq_test" from (SELECT 1 as id,'diga' as `name` UNION SELECT 1 as `no`,'japan' as `courtry`) t;
1 diga cq_test
1 japan cq_test
Time taken: 0.73 seconds, Fetched 2 row(s)
flink 实际测试
为什么需要这样做?一个很简单的场景是,当使用 UNION 查询根据每个结果集来自哪个表来标记每个结果集时,这会很有帮助。当然对于 UNION 的使用方法,后面的文章也会提到,这里不做深入的介绍。
函数查询
如果大家有看过过面经,MySQL有一道非常出名的面试题-“请说出selct count(*) 和 count(1)的差异”。如果还不明白的同学,这个题目有一篇文章可以认真读一下.这里面的count()就是一个函数。
它可以是简单函数
select count() from...
也可以是稍微复杂一下列处理函数,如
select LENGTH(result) from ...
或者日期处理函数
select FROM_UNIXTIME(unix_timestamp)
等等,诸如此类的函数有上百个之多,这里不做具体函数使用的拓展,SQL 中的函数可能会变得非常复杂,但它们非常强大,关于更多的使用方法大家可以自己上网查阅。在不同的引擎下的函数可能存在不一致。例如早期的MySQL版本不支持from_json等函数,而flink与spark更能够支持用户自定义函数。
子查询
最后,我们还可以在 SELECT 语句中使用子查询。这是一个更高级的功能,一般情况下,我们较少在select语句中直接使用子查询。
在 SELECT 子句中添加子查询意味着子查询的结果显示为列值,例如:
SELECT *,(SELECT `name` from (SELECT 'test' as `name`) t2) from (SELECT 1 as id,'diga' as `name` UNION SELECT 1 as `no`,'japan' as `courtry`) t
哈哈,请原谅我写了个很无聊很没意义的查询。就是为了演示一下而已。因为实际上在我目前这么多年工作以来,真的没有印象有人写过这样的SQL。来看MySQL的实际测试结果
spark 实际测试
spark-sql> SELECT *,(SELECT `name` from (SELECT 'test' as `name`) t2) from (SELECT 1 as id,'diga' as `name` UNION SELECT 1 as `no`,'japan' as `courtry`) t;
1 diga test
1 japan test
Time taken: 0.429 seconds, Fetched 2 row(s)
flink 实际测试
Flink SQL> SELECT *,(SELECT `name` from (SELECT 'test' as `name`)) from (SELECT 1 as id,'diga' as `name` UNION SELECT 1 as `no`,'japan' as `courtry`) t;
[ERROR] Could not execute SQL statement. Reason:
org.apache.flink.table.api.TableException: SINGLE_VALUE aggregate function doesn't support type 'CHAR'.
flink 查询失败。详细的报错日志
org.apache.flink.table.client.gateway.SqlExecutionException: Could not execute SQL statement.
at org.apache.flink.table.client.gateway.local.LocalExecutor.executeOperation(LocalExecutor.java:215) ~[flink-sql-client_2.11-1.13.1.jar:1.13.1]
at org.apache.flink.table.client.gateway.local.LocalExecutor.executeQuery(LocalExecutor.java:235) ~[flink-sql-client_2.11-1.13.1.jar:1.13.1]
at org.apache.flink.table.client.cli.CliClient.callSelect(CliClient.java:479) ~[flink-sql-client_2.11-1.13.1.jar:1.13.1]
at org.apache.flink.table.client.cli.CliClient.callOperation(CliClient.java:412) ~[flink-sql-client_2.11-1.13.1.jar:1.13.1]
at org.apache.flink.table.client.cli.CliClient.lambda$executeStatement$0(CliClient.java:327) [flink-sql-client_2.11-1.13.1.jar:1.13.1]
at java.util.Optional.ifPresent(Optional.java:159) ~[?:1.8.0_271]
at org.apache.flink.table.client.cli.CliClient.executeStatement(CliClient.java:327) [flink-sql-client_2.11-1.13.1.jar:1.13.1]
at org.apache.flink.table.client.cli.CliClient.executeInteractive(CliClient.java:297) [flink-sql-client_2.11-1.13.1.jar:1.13.1]
at org.apache.flink.table.client.cli.CliClient.executeInInteractiveMode(CliClient.java:221) [flink-sql-client_2.11-1.13.1.jar:1.13.1]
at org.apache.flink.table.client.SqlClient.openCli(SqlClient.java:151) [flink-sql-client_2.11-1.13.1.jar:1.13.1]
at org.apache.flink.table.client.SqlClient.start(SqlClient.java:95) [flink-sql-client_2.11-1.13.1.jar:1.13.1]
at org.apache.flink.table.client.SqlClient.startClient(SqlClient.java:187) [flink-sql-client_2.11-1.13.1.jar:1.13.1]
at org.apache.flink.table.client.SqlClient.main(SqlClient.java:161) [flink-sql-client_2.11-1.13.1.jar:1.13.1]
Caused by: org.apache.flink.table.api.TableException: Sql optimization: Cannot generate a valid execution plan for the given query:
FlinkLogicalSink(table=[default_catalog.default_database.Unregistered_Collect_Sink_20], fields=[id, name, $f0])
+- FlinkLogicalJoin(condition=[true], joinType=[left])
:- FlinkLogicalAggregate(group=[{0, 1}])
: +- FlinkLogicalUnion(all=[true])
: :- FlinkLogicalCalc(select=[1 AS id, _UTF-16LE'diga':VARCHAR(5) CHARACTER SET "UTF-16LE" AS name])
: : +- FlinkLogicalValues(tuples=[[{ 0 }]])
: +- FlinkLogicalCalc(select=[1 AS no, _UTF-16LE'japan':VARCHAR(5) CHARACTER SET "UTF-16LE" AS courtry])
: +- FlinkLogicalValues(tuples=[[{ 0 }]])
+- FlinkLogicalAggregate(group=[{}], agg#0=[SINGLE_VALUE($0)])
+- FlinkLogicalCalc(select=[_UTF-16LE'test' AS name])
+- FlinkLogicalValues(tuples=[[{ 0 }]])
SINGLE_VALUE aggregate function doesn't support type 'CHAR'.
从报错日志来看,由于无法生成执行计划,目前flink并不支持在select进行子查询查询。
写在最后
原本来写SQL能力建设过程中,已经预料到flink、spark、mysql会有诸多的不同。没想到会出现的得这么快,这么明显。这样一来更加坚定了我把整个系列的文章完成的想法。为了给后面的接触大数据的同学减少走弯路,能够快速上手,识别清楚差异,我会持续更新,希望对大家有帮助。