SQL基础能力-SELECT用法 | 8月更文挑战

798 阅读6分钟

现如今因为有了计算引擎各种source与sink连接器的支持,ETL的开发人员可以聚焦全部的精力在extraction与translation中。其中的重点自然是编写各种SQL从不同的数据源中抽取数据进行转化操作。所以编写SELECT语句约等于就是全部的工作内容。掌握了SQL的select语法,无疑掌握了数据测试的取胜之匙。

SELECT 基础

SELECT语句用于从数据库表中查询数据。它允许人为指定要获取表中的哪些列以及行的标准。返回的数据称为结果集,并显示在开发工具中,或由运行查询的应用程序使用。SELECT语句可以很简单,它们可以从一个小表中选择一些列。或者,它们可能很复杂,有很多列和复杂计算。文章将在MySQL与flink、spark中查看SELECT语句的语法差异与具体的使用案例。

基本语法

SELECT [ ALL | DISTINCT ] { 字段列表(*) }
[ INTO1 ]
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 实际测试

image.png

spark 实际测试

spark-sql> select 1+1 ;
2
Time taken: 0.225 seconds, Fetched 1 row(s)

flink 实际测试

image.png

当然,select还支持常量查询,可以理解为为某个临时表固定插入某个值。如

SELECT *,"cq_test" from (SELECT 1 as id,'diga' as `name` UNION SELECT 1 as `no`,'japan' as `courtry`) t

mysql 实际测试

image.png 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 实际测试

image.png

为什么需要这样做?一个很简单的场景是,当使用 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的实际测试结果

image.png

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会有诸多的不同。没想到会出现的得这么快,这么明显。这样一来更加坚定了我把整个系列的文章完成的想法。为了给后面的接触大数据的同学减少走弯路,能够快速上手,识别清楚差异,我会持续更新,希望对大家有帮助。