MaxCompute SQL中select语句使用详情

SELECT语法介绍

本文介绍MaxCompute SELECT语法格式及使用SELECT语法执行嵌套查询、排序操作、分组查询等操作的注意事项。在进行操作之前构建操作示例表sale_detail以及insert测试数据:

---------------------------------------数据表和数据准备---------------------------------------
CREATE TABLE IF NOT EXISTS sale_detail(
  shop_name     STRING,
  customer_id   STRING,
  total_price   DOUBLE)
PARTITIONED BY (sale_date STRING, region STRING);

create table if not exists shop (
  shop_id string,
  shop_name string,
  address string,
  owner_id string
);

insert into table sale_detail
partition (sale_date='202003', region='hangzhou')
values ('unique', 'user1', 1000),
('adidas', 'user2', 2000),
('zara', 'user3', 1500),
('veromoda', 'user1', 2000),
('nike', 'user4', 1000),
('ur', 'user2', 1400),
('ochirly', 'user1', 2000),
('lily', 'user3', 3000),
('onemore', "user2", 1200);

insert into table sale_detail
partition (sale_date='202004', region='beijing')
values ('unique', 'user1', 1000),
('adidas', 'user2', 2000),
('zara', 'user3', 1500),
('veromoda', 'user1', 2000),
('nike', 'user4', 1000),
('ur', 'user2', 1400),
('ochirly', 'user1', 2000),
('lily', 'user3', 3000),
('onemore', "user2", 1200);

insert into table sale_detail
partition (sale_date, region)
(shop_name, customer_id, total_price, sale_date, region)
values
('unique', 'user1', 1000, '202001', 'shanghai'),
('adidas', 'user2', 2000, '202001', 'shanghai'),
('zara', 'user3', 1500, '202001', 'shanghai');

select * from sale_detail;

insert into table shop(shop_id, shop_name, address, owner_id)
values
('1', 'unique', 'hangzhou', 'owner1'),
('2', 'adidas', 'beijing', 'owner2'),
('3', 'zara', 'shanghai', 'owner3'),
('4', 'veromoda', 'shanghai', 'owner4'),
('5', 'nike', 'beijing', 'owner5'),
('6', 'ur', 'shanghai', 'owner6'),
('7', 'ochirly', 'shanghai', 'owner7'),
('8', 'lily', 'shanghai', 'owner8'),
('9', 'onemore', 'shanghai', 'owner9');

select * from shop;

本文MaxCompute命令参考自: MaxCompute Select语句使用文档。测试数据为自行创建,学习和使用过程中可以根据情况自行修改。

SELECT语法格式

SELECT [ALL | DISTINCT] select_expr, select_expr, ...
FROM table_reference
[WHERE where_condition]
[GROUP BY col_list]
[ORDER BY order_condition]
[DISTRIBUTE BY distribute_condition [SORT BY sort_condition] ]
[LIMIT number]
  • 当使用SELECT语句时,屏显目前最多只能显示10000行结果。当SELECT语句作为子句时则无此限制,SELECT子句会将全部结果返回给上层查询。
  • SELECT语句查询分区表时禁止全表扫描。

假设sale_detail表为分区表,需要同时执行如下语句进行全表查询。

set odps.sql.allow.fullscan=true;
select * from sale_detail;

如果整个项目都需要全表扫描,执行如下命令打开开关。

setproject odps.sql.allow.fullscan=true;

列表达式(select_expr)

SELECT操作从表中读取数据,列表达式有以下几种形式:

1. 用列名指定要读取的列。例如,读取表sale_detail的列shop_name

select shop_name from sale_detail;

2. 用*代表所有的列。读取表sale_detail中所有的列。

select * from sale_detail;

3. 在WHERE中可以指定过滤的条件

select * from sale_detail where shop_name like 'hang%';

4. select_expr支持正则表达式。举例如下:

SELECT `abc.*` FROM t; -- 选出t表中所有列名以abc开头的列。
SELECT `(ds)?+.+` FROM t; -- 选出t表中列名不为ds的所有列。
SELECT `(ds|pt)?+.+` FROM t; -- 选出t表中排除ds和pt两列的其它列。
SELECT `(d.*)?+.+` FROM t; -- 选出t表中排除列名以d开头的其它列。

5. DISTINCT去重。您可以在选取的列名前使用DISTINCT去掉重复字段,只返回一个值;而使用ALL会返回字段中所有重复的值。不指定此选项时,默认值为ALL

-- 查询表sale_detail中region列数据,如果有重复值时仅显示一条。
select distinct region from sale_detail;
+------------+
| region     |
+------------+
| shanghai   |
+------------+
-- distinct多列时,distinct的作用域是select的列集合,不是单个列。
select distinct region, sale_date from sale_detail;
+------------+------------+
| region     | sale_date  |
+------------+------------+
| shanghai   | 20191110   |
+------------+------------+

TABLE_REFERENCE

table_reference为查询的目标表信息。除了支持已存在的目标表名称还支持使用嵌套子查询,如下所示。

select * from (select region from sale_detail) t where region = 'shanghai';

WHERE子句过滤

where子句支持的过滤条件,如下表所示。

 
过滤条件 描述
> 、 < 、 =、 >=、 <=、 <> 关系操作符。
like、rlike like和rlike的source和pattern参数均仅接受STRING类型。
in、not in 如果在in/not in条件后加子查询,子查询只能返回一列值,且返回值的数量不能超过1000。
Between…and 限定查询范围。

在SELECT语句的where子句中,可以指定分区范围,只扫描表的指定部分,避免全表扫描,如下所示:

SELECT sale_detail.* 
FROM sale_detail
WHERE sale_detail.sale_date >= '2019'
AND sale_detail.sale_date <= '2020';

between…and查询示例如下:

SELECT sale_detail.* 
FROM sale_detail 
WHERE sale_detail.sale_date BETWEEN '2019' AND '2020';

GROUP BY分组查询

通常,GROUP BY和聚合函数配合使用。在SELECT中包含聚合函数时有以下规则:

  • 在SQL解析中,GROUP BY操作先于SELECT操作,因此GROUP BY的取值是SELECT输入表的列名或者由输入表的列构成的表达式,不允许是SELECT语句的输出列的别名
  • GROUP BY的值既是输入表的列或表达式,又是SELECT的输出列时,取值为输入表的列名。
  • 当SQL语句set flag,即set hive.groupby.position.alias=true;时,GROUP BY中的整型常量会被当做SELECT的列序号处理。
set hive.groupby.position.alias=true;--与下一条sql语句一起执行。
select region, sum(total_price) from sale_detail group by 1;-- 1代表select的列中第一列即region,以region值分组,返回每一组的region值(组内唯一)及销售额总量。

使用示例:

--直接使用输入表列名region作为group by的列,即以region值分组。
select region from sale_detail group by region;

--以region值分组,返回每一组的销售额总量。
select sum(total_price) from sale_detail group by region;

--以region值分组,返回每一组的region值(组内唯一)及销售额总量。
select region, sum(total_price) from sale_detail group by region;

--使用select列的别名运行,报错返回。
select region as r from sale_detail group by r;

--必须使用列的完整表达式。
select 2 + total_price as r from sale_detail group by 2 + total_price;

--报错返回,select的所有列中,没有使用聚合函数的列,必须出现在group by中(下面语句total_price列没有出现在group by中)。
select region, total_price from sale_detail group by region;

--没有使用聚合函数的列,出现在group by中后,运行成功。
select region, total_price from sale_detail group by region, total_price;     

ORDER BY/SORT BY/DISTRIBUTE BY

DISTRIBUTE BY

功能说明:用于对数据按照某几列的值做Hash分片,必须使用SELECT的输出列别名。

--查询表sale_detail中的列region值并按照region值进行哈希分片。
select region from sale_detail distribute by region;
-- 列名即是别名,可以运行。
select region as r from sale_detail distribute by region;
等同于
select region as r from sale_detail distribute by r;

ORDER BY

功能说明:用于对所有数据按照指定列进行全局排序。

  • 对记录进行降序排序,需要使用desc关键字。默认以升序排列。
  • 在使用order by排序时,NULL会被认为比任何值都小,这个行为与MySQL一致,但是与Oracle不一致。
  • order by后面须加SELECT列的别名。当SELECT某列时,如果没有指定列的别名,则列名会被作为列的别名。
  • OFFSET 和 ORDER BY LIMIT语句配合,可以指定跳过OFFSET数目的行。
--将src按照key从小到大排序后,输出第11到第30行(OFFSET 10指定跳过前10行,LIMIT 20指定最多输出20行)。
SELECT * FROM src ORDER BY key LIMIT 20 OFFSET 10;

order by/sort by/distribute by的取值必须是SELECT语句的输出列,即列的别名。列的别名可以为中文。

在MaxCompute SQL解析中,order by/sort by/distribute by是后于SELECT操作的,因此它们的取值只能为SELECT语句的输出列。

使用示例:

--查询表sale_detail的信息,并按照region升序排列前100条。
select * from sale_detail order by region limit 100;

-- order by没有与limit共同使用时,报错返回。
select * from sale_detail order by region;

--order by加列的别名。
select region as r from sale_detail order by region limit 100;
select region as r from sale_detail order by r limit 100;

LIMIT NUMBER限制输出行数

limit number中的number是常数,限制输出行数。当使用无limitSELECT语句直接从屏幕输出查看结果时,最多只输出10000行。每个项目空间的这个屏显最大限制可能不同,您可以通过setproject命令控制。

SELECT语序

对于按照SELECT语法格式书写的SELECT语句,它的逻辑执行顺序与标准的书写语序并不相同。

SELECT  key
        ,MAX(value)
FROM    src t
WHERE   value > 0
GROUP BY key
HAVING  SUM(value) > 100
ORDER BY key
LIMIT   100
;

以上语句的逻辑执行顺序是FROM->WHERE->GROUY BY->HAVING->SELECT->ORDER BY->LIMIT

  • ORDER BY中只能引用SELECT列表中生成的列,而非访问FROM的源表中的列。
  • HAVING可以访问GROUP BY key和聚合函数。
  • SELECT时,如果语句中有GROUP BY,便只能访问GROUP BY key和聚合函数,而不是FROM中源表中的列。

为避免混淆,MaxCompute支持以执行顺序书写查询语句:

FROM    src t
WHERE   value > 0
GROUP BY key
HAVING  SUM(value) > 100
SELECT  key
        ,MAX(value)
ORDER BY key
LIMIT   100
;

使用示例:

SELECT  shop_name
        ,total_price
        ,region
FROM    sale_detail
WHERE   total_price > 150
DISTRIBUTE BY region
SORT BY region
;

以上语句的逻辑执行顺序是FROM->WHERE->SELECT->DISTRIBUTE BY->SORT BY

子查询

子查询基本定义

普通查询操作的对象是目标表,但是查询的对象也可以是另一个SELECT语句,此查询为子查询(SUBQUERY)。子查询必须要有别名

--普通查询。
select * from sale_detail;
--子查询。
select * from (select shop_name from sale_detail) a;

from子句中,子查询可以被当作一张表,与其它的表或子查询进行join操作。

-- 示例1
select a.shop_id, a.shop_name, a.address
from (select * from shop) a
join sale_detail b on a.shop_name=b.shop_name;

-- 返回值
shop_id	shop_name	address
+--------+----------+--------+
1	unique	hangzhou
2	adidas	beijing
3	zara	shanghai
1	unique	hangzhou
2	adidas	beijing
3	zara	shanghai
4	veromoda	shanghai
5	nike	beijing
6	ur	shanghai
7	ochirly	shanghai
8	lily	shanghai
9	onemore	shanghai
1	unique	hangzhou
2	adidas	beijing
3	zara	shanghai
4	veromoda	shanghai
5	nike	beijing
6	ur	shanghai
7	ochirly	shanghai
8	lily	shanghai
9	onemore	shanghai

-- 示例2
select a.shop_id, a.shop_name, b.total_price, b.customer_id
from shop a join sale_detail b on a.shop_name=b.shop_name;
-- 返回结果
shop_id	shop_name	total_price	customer_id
+--------+----------+------------+------------+
1	unique	1000.0	user1
2	adidas	2000.0	user2
3	zara	1500.0	user3
4	veromoda	2000.0	user1
5	nike	1000.0	user4
6	ur	1400.0	user2
7	ochirly	2000.0	user1
8	lily	3000.0	user3
9	onemore	1200.0	user2
1	unique	1000.0	user1
2	adidas	2000.0	user2
3	zara	1500.0	user3
1	unique	1000.0	user1
2	adidas	2000.0	user2
3	zara	1500.0	user3
4	veromoda	2000.0	user1
5	nike	1000.0	user4
6	ur	1400.0	user2
7	ochirly	2000.0	user1
8	lily	3000.0	user3
9	onemore	1200.0	user2

IN SUBQUERY/NOT IN SUBQUERY

IN SUBQUERY

in subqueryleft semi join用法类似。

select * from mytable1 where id in (select id from mytable2);
--等效于以下语句。
select * from mytable1 a left semi join mytable2 b on a.id = b.id;

目前MaxCompute不仅支持in subquery,还支持Correlated条件。 下面查询语句中子查询中的where value = mytable1.value即是一个Correlated条件。MaxCompute 1.0版本不支持这种既引用了子查询中源表,又引用了外层查询源表的表达式。MaxCompute 2.0版本已支持这种用法,这样的过滤条件构成了semi joinon条件的一部分。

select * from mytable1 
where id 
in (select id from mytable2 where value = mytable1.value);

使用示例:

select * from sale_detail where shop_name in (select shop_name from shop);

select * from sale_detail where shop_name
in (select shop_name from shop where sale_detail.shop_name=shop_name);

select * from sale_detail a left semi join shop b
on a.shop_name=b.shop_name;

-- 上述三个语句的返回结果均为:
shop_name	customer_id	total_price	sale_date	region
+----------+------------+------------+----------+-------+
unique	user1	1000.0	202003	hangzhou
adidas	user2	2000.0	202003	hangzhou
zara	user3	1500.0	202003	hangzhou
veromoda	user1	2000.0	202003	hangzhou
nike	user4	1000.0	202003	hangzhou
ur	user2	1400.0	202003	hangzhou
ochirly	user1	2000.0	202003	hangzhou
lily	user3	3000.0	202003	hangzhou
onemore	user2	1200.0	202003	hangzhou
unique	user1	1000.0	202001	shanghai
adidas	user2	2000.0	202001	shanghai
zara	user3	1500.0	202001	shanghai
unique	user1	1000.0	202004	beijing
adidas	user2	2000.0	202004	beijing
zara	user3	1500.0	202004	beijing
veromoda	user1	2000.0	202004	beijing
nike	user4	1000.0	202004	beijing
ur	user2	1400.0	202004	beijing
ochirly	user1	2000.0	202004	beijing
lily	user3	3000.0	202004	beijing
onemore	user2	1200.0	202004	beijing

NOT IN SUBQUERY

not in subqueryleft anti join用法类似,但并不完全相同。示例如下。

select * from mytable1 where id not in (select id from mytable2);
--如果mytable2中的所有id都不为NULL,则等效于以下语句。
select * from mytable1 a left anti join mytable2 b on a.id = b.id;

如果mytable2中有任意一列为NULL,则not in表达式会为NULL,导致where条件不成立,无数据返回,这点与left anti join不同。

EXISTS SUBQUERY/NOT EXISTS SUBQUERY

使用exists subquery时,当Subquery中有至少一行数据时,返回True,否则返回False。使用not exists subquery时,则相反。目前只支持含有Correlatedwhere条件的子查询。exists subquerynot exists subquery实现的方式是转换为left semi join或者left anti join

示例一

SELECT * from mytable1 
where 
exists (select * from mytable2 where id = mytable1.id);
--等效于以下语句。
SELECT * from mytable1 a 
left semi join mytable2 b on a.id = b.id;
-- exists subquery
select * from sale_detail where
exists (select shop_name from shop where sale_detail.shop_name=shop_name);

示例二

select * from mytable1 
where 
not exists (select * from mytable2 where id = mytable1.id);
--等效于以下语句。
select * from mytable1 a 
left anti join mytable2 b on a.id = b.id;

SCALAR SUBQUERY

当Subquery的输出结果为单行单列时,可以做为标量使用。

select * from t1 
where (select count(*) from t2 where t1.a = t2.a) > 1; 
--等效于以下语句
select t1.* from t1 
left semi join (
    select a, count(*) 
    from t2 
    group by a 
    having count(*) > 1
) t2 on t1.a = t2.a;

select count(*) from t2 where t1.a = t2.a;的输出结果是一个Row Set,可以判断出这条语句的输出有且仅有一行一列。因此它可以作为标量,即可以参与标量运算(‘>’)。但在实现过程中,会尽可能地转成join来处理,例如上述示例中的等效语句。

交集、并集和补集

下面介绍UNION、UNOIN ALL、UNION DISTINCT并集,INTERSECT、INTERSECT ALL、INTERSECT DISTINCT交集,EXCEPT、EXCEPT ALL、EXCEPT DISTINCT补集等SQL语法。

语法格式

select_statement UNION ALL select_statement;
select_statement UNION [DISTINCT] select_statement;
select_statement INTERSECT ALL select_statement;
select_statement INTERSECT [DISTINCT] select_statement;
select_statement EXCEPT ALL select_statement;
select_statement EXCEPT [DISTINCT] select_statement;
select_statement MINUS ALL select_statement;
select_statement MINUS [DISTINCT] select_statement;

UNION(并集)

功能说明:求两个数据集的并集,即将两个数据集合并成一个数据集。

1. 当UNION后指定参数ALL时,返回两个数据集的所有记录:

SELECT * FROM VALUES (1, 2), (1, 2), (3, 4) t(a, b) 
UNION ALL 
SELECT * FROM VALUES (1, 2), (1, 4) t(a, b);

返回结果如下。

+------------+------------+
| a          | b          |
+------------+------------+
| 1          | 2          |
| 1          | 4          |
| 1          | 2          |
| 1          | 2          |
| 3          | 4          |
+------------+------------+

2. 当UNION后不指定参数时,返回记录中会去掉重复的记录,效果等同于UNION DISTINCT。示例如下。

SELECT * FROM VALUES (1, 2), (1, 2), (3, 4) t(a, b) 
UNION 
SELECT * FROM VALUES (1, 2), (1, 4) t(a, b);
--等同于如下语句。
SELECT DISTINCT * FROM (<UNION ALL的结果>)t;

结果如下:

+------------+------------+
| a          | b          |
+------------+------------+
| 1          | 2          |
| 1          | 4          |
| 3          | 4          |
+------------+------------+

3. UNION后如果有CLUSTER BYDISTRIBUTE BYSORT BYORDER BY或者LIMIT子句,当设置set odps.sql.type.system.odps2=false;时,其作用于UNION的最后一个select_statement;当设置set odps.sql.type.system.odps2=true;时,作用于前面所有UNION的结果。

set odps.sql.type.system.odps2=true;
SELECT explode(array(3, 1)) AS (a) UNION ALL SELECT explode(array(0, 4, 2)) AS (a) ORDER BY a LIMIT 3;

返回结果如下。

+------+
| a    |
+------+
| 0    |
| 1    |
| 2    |
+------+

INTERSECT(交集)

功能说明:求两个数据集的交集,即输出两个数据集均包含的记录。

1. INTERSECT ALL示例

SELECT * FROM VALUES (1, 2), (1, 2), (3, 4), (5, 6) t(a, b) 
INTERSECT ALL 
SELECT * FROM VALUES (1, 2), (1, 2), (3, 4), (5, 7) t(a, b);

返回结果如下:

+------------+------------+
| a          | b          |
+------------+------------+
| 1          | 2          |
| 1          | 2          |
| 3          | 4          |
+------------+------------+

2. INTERSECT DISTINCT示例

SELECT * FROM VALUES (1, 2), (1, 2), (3, 4), (5, 6) t(a, b) 
INTERSECT 
SELECT * FROM VALUES (1, 2), (1, 2), (3, 4), (5, 7) t(a, b);

结果如下,等同于SELECT DISTINCT * FROM (< INTERSECT ALL的结果 >) t;语句。

+------------+------------+
| a          | b          |
+------------+------------+
| 1          | 2          |
| 3          | 4          |
+------------+------------+

EXCEPT

功能说明:求第二个数据集在第一个数据集中的补集,即输出第一个数据集包含而第二个数据集不包含的记录。

1. EXCEPT ALL示例。

SELECT * FROM VALUES (1, 2), (1, 2), (3, 4), (3, 4), (5, 6), (7, 8) t(a, b) 
EXCEPT ALL 
SELECT * FROM VALUES (3, 4), (5, 6), (5, 6), (9, 10) t(a, b);

返回结果如下。

+------------+------------+
| a          | b          |
+------------+------------+
| 1          | 2          |
| 1          | 2          |
| 3          | 4          |
| 7          | 8          |
+------------+------------+

2. EXCEPT DISTINCT示例。

SELECT * FROM VALUES (1, 2), (1, 2), (3, 4), (3, 4), (5, 6), (7, 8) t(a, b) 
EXCEPT
SELECT * FROM VALUES (3, 4), (5, 6), (5, 6), (9, 10) t(a, b);

结果如下,相当于SELECT DISTINCT * FROM left_branch EXCEPT ALL SELECT DISTINCT * FROM right_branch;

+------------+------------+
| a          | b          |
+------------+------------+
| 1          | 2          |
| 7          | 8          |
+------------+------------+

MINUS

功能说明:等同于EXCEPT

Join表连接

MaxCompute的JOIN操作分为左连接、右连接、全连接、内连接。支持多路连接,但不支持CROSS JOIN笛卡尔积,即无ON条件的连接。

语法格式

命令格式如下。

join_table:
        table_reference join table_factor [join_condition]
        | table_reference {left outer|right outer|full outer|inner|Natural} join table_reference join_condition
    table_reference:
        table_factor
        | join_table
    table_factor:
        tbl_name [alias]
        | table_subquery alias
        | ( table_references )
    join_condition:
        on equality_expression ( and equality_expression )

语法说明

1. Left Outer Join:左连接。返回左表中的所有记录,即使右表中没有与之匹配的记录。

select a.shop_name as ashop, b.shop_name as bshop from shop a
        left outer join sale_detail b on a.shop_name=b.shop_name;--由于表shop及sale_detail中都有shop_name列,因此需要在select子句中使用别名进行区分。

说明 如果右边值不唯一,建议不要连续使用过多left join,以免在join的过程中产生数据膨胀,导致作业停滞。

2. Right Outer Join:右连接。返回右表中的所有记录,即使左表中没有与之匹配的记录。

select a.shop_name as ashop, b.shop_name as bshop from shop a
        right outer join sale_detail b on a.shop_name=b.shop_name;

3. Full Outer Join:全连接。返回左右表中的所有记录。

select a.shop_name as ashop, b.shop_name as bshop from shop a
        full outer join sale_detail b on a.shop_name=b.shop_name;

4. Inner Join:内连接,关键字inner可以省略。表中存在至少一个匹配时,inner join返回行。

select a.shop_name from shop a inner join sale_detail b on a.shop_name=b.shop_name;
select a.shop_name from shop a join sale_detail b on a.shop_name=b.shop_name;

5. Natural Join :自然连接, 即参与JOIN的两张表根据字段名称自动决定连接字段。支持Outer Natural Join,支持使用Using子句执行JOIN,输出字段中公共字段只出现一次。假设存在表src(key1, key2, a1, a2),表src2(key1, key2, b1, b2)。

SELECT * FROM src NATURAL JOIN src2;
--由于src和src2有两个同名字段(key1, key2),所以上面的JOIN相当于:
SELECT src.key1 as key1, src.key2 as key2, src.a1, src.a2, src2.b1, src2.b2 FROM src INNER JOIN src2 ON src.key1 = src2.key1 AND src.key2 = src2.key2;

6. Implicit Join:隐式连接,即不指定Join关键字执行连接。

SELECT * FROM table1, table2 WHERE table1.id = table2.id;
--执行的效果相当于以下语句。
SELECT * FROM table1 JOIN table2 ON table1.id = table2.id;

连接条件:只允许and连接的等值条件。只有在MAPJOIN中,可以使用不等值连接或者使用or连接多个条件。

--支持多路Join连接示例。
select a.* from shop a full outer join sale_detail b on a.shop_name=b.shop_name
        full outer join sale_detail c on a.shop_name=c.shop_name;
--不支持不等值Join连接条件,报错返回。    
select a.* from shop a join sale_detail b on a.shop_name != b.shop_name;  

使用示例:假设表A为test_table_a,表B为test_table_b,查询两表中分区大于20180101且originid一致的记录数。使用left join可以保留左表中test_table_a的全表记录。

错误示例如下:

SELECT s.id
        ,s.name
        ,s.origin
        ,d.value
FROM    test_table_a s
LEFT JOIN   test_table_b d
ON      s.origin = d.id
WHERE   s.ds > "20180101" AND d.ds>"20180101";

如果joinwhere条件之前, 会先进行join操作,然后对join的结果执行where条件过滤。您会发现获取的结果是两个表的交集,而不是全表。修正后的SQL如下,通过这种方式可以显示全表:

SELECT s.id
        ,s.name
        ,s.origin
        ,d.value
FROM  (select * from  test_table_a where ds > "20180101" ) s
LEFT JOIN (select * from  test_table_b where ds > "20180101") d
ON s.origin = d.id;

SEMI JOIN(半连接)

MaxCompute支持SEMI JOIN(半连接)。SEMI JOIN中,右表只用于过滤左表的数据而不出现在结果集中。支持LEFT SEMI JOIN和LEFT ANTI JOIN两种语法

LEFT SEMI JOIN

join条件成立时,返回左表中的数据。如果mytable1中某行的idmytable2的所有id中出现过,则此行保留在结果集中。

SELECT * from mytable1 a LEFT SEMI JOIN mytable2 b on a.id=b.id;

只会返回mytable1中的数据,只要mytable1idmytable2id中出现。

LEFT ANTI JOIN

join条件不成立时,返回左表中的数据。如果mytable1中某行的idmytable2的所有id中没有出现过,则此行保留在结果集中。

SELECT * from mytable1 a LEFT ANTI JOIN mytable2 b on a.id=b.id;

只会返回mytable1中的数据,只要mytable1idmytable2id没有出现。

HAVING子句

MaxCompute SQL的WHERE关键字无法与合计函数一起使用,此时您可以使用HAVING子句来实现。

命令格式如下。

SELECT column_name, aggregate_function(column_name)
FROM table_name
WHERE column_name operator value
GROUP BY column_name
HAVING aggregate_function(column_name) operator value

例如有一张订单表Orders,包括客户名称(Customer)、订单金额(OrderPrice)、订单日期(Order_date)、订单号(Order_id)四个字段。现在需要查找订单总额少于2000的客户,SQL语句如下所示。

SELECT Customer,SUM(OrderPrice) FROM Orders
GROUP BY Customer
HAVING SUM(OrderPrice)<2000

 对应本文的示例表和数据,类似的操作如下:

select customer_id, sum(total_price) as order_price from sale_detail
group by customer_id
having sum(total_price) < 4000;

GROUPING SETS

对于经常需要对数据进行多维度的聚合分析的场景,既需要对a列做聚合,也要对b列做聚合,同时要按照a、b两列同时做聚合,因此需要多次使用UNION ALL。使用GROUPING SETS可以快速解决此类问题。

实现示例

1. 准备数据:

create table requests LIFECYCLE 20 as
select * from values
    (1, 'windows', 'PC', 'Beijing'),
    (2, 'windows', 'PC', 'Shijiazhuang'),
    (3, 'linux', 'Phone', 'Beijing'),
    (4, 'windows', 'PC', 'Beijing'),
    (5, 'ios', 'Phone', 'Shijiazhuang'),
    (6, 'linux', 'PC', 'Beijing'),
    (7, 'windows', 'Phone', 'Shijiazhuang')
as t(id, os, device, city);

2. 对数据进行分组。您可以通过以下两种方式进行分组:

(1)使用多个SELECT语句进行分组。

SELECT NULL, NULL, NULL, COUNT(*)
FROM requests
UNION ALL
SELECT os, device, NULL, COUNT(*)
FROM requests GROUP BY os, device
UNION ALL
SELECT null, null, city, COUNT(*)
FROM requests GROUP BY city;

(2)使用GROUPING SETS进行分组。

SELECT os,device, city ,COUNT(*)
FROM requests
GROUP BY os, device, city GROUPING SETS((os, device), (city), ());

执行结果如下。

+----+--------+------+------------+
| os | device | city | cnt        |
+----+--------+------+------------+
| NULL | NULL   | NULL | 7          |
| NULL | NULL   | Beijing | 4          |
| NULL | NULL   | Shijiazhuang | 3          |
| ios | Phone  | NULL | 1          |
| linux | PC     | NULL | 1          |
| linux | Phone  | NULL | 1          |
| windows | PC     | NULL | 3          |
| windows | Phone  | NULL | 1          |
+----+--------+------+------------+

CUBE and ROLLUP函数

CUBE和ROLLUP可以认为是特殊的GROUPING SETS。CUBE会枚举指定列的所有可能组合作为GROUPING SETS,而ROLLUP会以按层级聚合的方式产生GROUPING SETS

示例如下。

GROUP BY CUBE(a, b, c)  
--等价于以下语句。  
GROUPING SETS((a,b,c),(a,b),(a,c),(b,c),(a),(b),(c),())

GROUP BY ROLLUP(a, b, c)
--等价于以下语句。  
GROUPING SETS((a,b,c),(a,b),(a), ())

GROUP BY CUBE ( (a, b), (c, d) ) 
--等价于以下语句。 
GROUPING SETS (
    ( a, b, c, d ),
    ( a, b       ),
    (       c, d ),
    (            )
)

GROUP BY ROLLUP ( a, (b, c), d ) 
--等价于以下语句。
GROUPING SETS (
    ( a, b, c, d ),
    ( a, b, c    ),
    ( a          ),
    (            )
)

GROUP BY a, CUBE (b, c), GROUPING SETS ((d), (e)) 
--等价于以下语句。 
GROUP BY GROUPING SETS (
    (a, b, c, d), (a, b, c, e),
    (a, b, d),    (a, b, e),
    (a, c, d),    (a, c, e),
    (a, d),       (a, e)
)

GROUP BY grouping sets((b), (c),rollup(a,b,c)) 
--等价于以下语句。 
GROUP BY GROUPING SETS (
    (b), (c),
    (a,b,c), (a,b), (a), ()
 )

GROUPING和GROUPING_ID函数

GROUPING SETS结果中使用NULL充当占位符,导致您会无法区分占位符NULL与数据中真正的NULL。因此,MaxCompute为您提供了GROUPING函数。GROUPING函数接受一个列名作为参数,如果结果对应行使用了参数列做聚合,返回0,此时意味着NULL来自输入数据。否则返回1,此时意味着NULL是GROUPING SETS的占位符

MaxCompute还提供了GROUPING_ID函数,此函数接受一个或多个列名作为参数。结果是将参数列的GROUPING结果按照BitMap的方式组成整数。示例如下:

SELECT a,b,c ,COUNT(*),
GROUPING(a) ga, GROUPING(b) gb, GROUPING(c) gc, GROUPING_ID(a,b,c) groupingid
FROM VALUES (1,2,3) as t(a,b,c)
GROUP BY CUBE(a,b,c);

执行结果如下:

+------------+------------+------------+------------+------------+------------+------------+------------+
| a          | b          | c          | _c3        | ga         | gb         | gc         | groupingid |
+------------+------------+------------+------------+------------+------------+------------+------------+
| NULL       | NULL       | NULL       | 1          | 1          | 1          | 1          | 7          |
| NULL       | NULL       | 3          | 1          | 1          | 1          | 0          | 6          |
| NULL       | 2          | NULL       | 1          | 1          | 0          | 1          | 5          |
| NULL       | 2          | 3          | 1          | 1          | 0          | 0          | 4          |
| 1          | NULL       | NULL       | 1          | 0          | 1          | 1          | 3          |
| 1          | NULL       | 3          | 1          | 0          | 1          | 0          | 2          |
| 1          | 2          | NULL       | 1          | 0          | 0          | 1          | 1          |
| 1          | 2          | 3          | 1          | 0          | 0          | 0          | 0          |
+------------+------------+------------+------------+------------+------------+------------+------------+

默认情况,GROUP BY列表中不被使用的列,会被填充为NULL。您可以通过GROUPING函数输出更有实际意义的值。

SELECT 
  IF(GROUPING(os) == 0, os, 'ALL') as os,
  IF(GROUPING(device) == 0, device, 'ALL') as device, 
  IF(GROUPING(city) == 0, city, 'ALL') as city ,
  COUNT(*) as count
FROM requests
GROUP BY os, device, city GROUPING SETS((os, device), (city), ());

返回结果如下:

os	device	city	count
+---+-------+-----+------+
ALL	ALL	ALL	7
ALL	ALL	Beijing	4
ALL	ALL	Shijiazhuang	3
ios	Phone	ALL	1
linux	PC	ALL	1
linux	Phone	ALL	1
windows	PC	ALL	3
windows	Phone	ALL	1

SELECT TRANSFORM语法

SELECT TRANSFORM语法允许您启动一个指定的子进程,将输入数据按照一定的格式通过标准输入至子进程,并且通过解析子进程的标准输出获取输出数据。SELECT TRANSFORM让您无需编写UDF,便可以实现MaxCompute SQL对其它脚本语言的支持。

语法如下:

SELECT TRANSFORM(arg1, arg2 ...) 
(ROW FORMAT DELIMITED (FIELDS TERMINATED BY field_delimiter (ESCAPED BY character_escape)?)? 
(LINES SEPARATED BY line_separator)? 
(NULL DEFINED AS null_value)?)?
USING 'unix_command_line' 
(RESOURCES 'res_name' (',' 'res_name')*)? 
( AS col1, col2 ...)?
(ROW FORMAT DELIMITED (FIELDS TERMINATED BY field_delimiter (ESCAPED BY character_escape)?)? 
(LINES SEPARATED BY line_separator)? (NULL DEFINED AS null_value)?)?

具体使用见:MaxCompute SELECT TRANSFORM语法文档

发布了330 篇原创文章 · 获赞 71 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/yitian_z/article/details/105594117