本文介绍Hive中的JOIN语句,JOIN语句可以通过任意个字段关联对多个表进行列连接,有些类似于Excel中的vlookup函数,但功能更加强大,下面我们来详细介绍。
当两个表有一个或多个列内容有交集时,INNER JOIN会返回两个表关联上的列的所在行的所有列,有些绕,没看懂的初学者继续往后看,看完例子就懂了……
总之先记住,内连接返回关联的表的交集,如图:
语法如下:
SELECT a.列名
,b.列名
FROM 表名1 a
INNER JOIN 表名2 b
ON a.列名1 = b.列名1 [and a.列名2=b.列名2]...
[INNER JOIN 表名3 c
ON a.列名1 = c.列名1 [and a.列名2=c.列名2]...]
...
[WHERE ...
GROUP BY ...
ORDER BY ...];
举个简单的例子:比如表1中有列A、B,表2中有列C、D,表1的A列和表2的C列有交集,内连接ON 表1.A=表2.C后,会返回A、B、C、D,其中所有返回的行都是A=C的。
注意:
表名1和表名2的位置可以是子查询,语法如下:
SELECT a.列名
,b.列名
FROM (子查询1) a
INNER JOIN (子查询2) b
ON a.列名1 = b.列名1 [and a.列名2=b.列名2]...
[INNER JOIN (子查询3) c
ON a.列名1 = c.列名1 [and a.列名2=c.列名2]...]
...
[WHERE ...
GROUP BY ...
ORDER BY ...];
复杂的关联语句强烈建议先用WITH AS 语句将a和b中的数据定义好,只选出自己要用的列,并且通过WHERE条件限定自己所需的行,然后再关联。因为JOIN是极其需要计算资源的操作,所以尽可能先让自己关联的表变小,这样可以有效提高运行效率和代码可读性,语法如下:
with a AS (查询1)
,b AS (查询2)
,c AS (查询3)
...
SELECT a.列名
,b.列名
FROM a
INNER JOIN b
ON a.列名1 = b.列名1 [and a.列名2=b.列名2]...
[INNER JOIN c
ON a.列名1 = c.列名1 [and a.列名2=c.列名2]...]
...
[WHERE ...
GROUP BY ...
ORDER BY ...];
后面的举例会全使用WITH AS的形式,希望大家也能养成这种习惯。
这里使用JOIN重构上一篇博客中的WHERE子查询操作
展现形式:
平台 版本 用户数
脚本如下:
WITH a
AS (
--20190101的平台、版本、活跃用户id
SELECT platform
,app_version
,user_id
FROM app.t_od_use_cnt
WHERE date_8 = 20190101
AND is_active = 1
)
,b
AS (
--20190101的新增用户id
SELECT user_id
FROM app.t_od_new_user
WHERE date_8 = 20190101
)
--20190101的平台、版本、新增活跃用户数
SELECT a.platform
,a.app_version
,count(a.user_id) AS num
FROM a
INNER JOIN b
ON a.user_id = b.user_id
GROUP BY platform
,app_version;
运行结果与使用WHERE子查询一致:
hive (app)> WITH a
> AS (
> --20190101的平台、版本、活跃用户id
> SELECT platform
> ,app_version
> ,user_id
> FROM app.t_od_use_cnt
> WHERE date_8 = 20190101
> AND is_active = 1
> )
> ,b
> AS (
> --20190101的新增用户id
> SELECT user_id
> FROM app.t_od_new_user
> WHERE date_8 = 20190101
> )
> --20190101的平台、版本、新增活跃用户数
> SELECT a.platform
> ,a.app_version
> ,count(a.user_id) AS num
> FROM a
> INNER JOIN b
> ON a.user_id = b.user_id
> GROUP BY platform
> ,app_version;
a.platform a.app_version num
1 1.1 24
1 1.2 21
1 1.3 25
1 1.4 21
1 1.5 25
2 1.1 19
2 1.2 18
2 1.3 15
2 1.4 20
2 1.5 18
可以看到注释并不会影响脚本运行。
看到这里读者应该更深刻的理解了JOIN的作用,它的应用场景就是要取出两个表中的交集部分。
当两个表有一个或多个列内容有交集时,LEFT OUTER JOIN返回左表的全部行和右表满足ON条件的行,如果左表的行在右表中没有匹配,那么这一行右表中对应数据用NULL代替。也有些绕,没看懂的初学者继续往后看,看完例子就懂了……
总之先记住:LEFT JOIN产生表1的完全集,而2表中匹配的则有值,没有匹配的则以null值取代。关系如下图:
语法则和内连接完全一致,只是将INNER JOIN换成LEFT OUTER JOIN即可,其中OUTER同样可以省略,简写为LEFT JOIN。
with a AS (查询1)
,b AS (查询2)
,c AS (查询3)
...
SELECT a.列名
,b.列名
FROM a
LEFT JOIN b
ON a.列名1 = b.列名1 [and a.列名2=b.列名2]...
[LEFT JOIN c
ON a.列名1 = c.列名1 [and a.列名2=c.列名2]...]
...
[WHERE ...
GROUP BY ...
ORDER BY ...];
左关联有两种应用场景:
下面分别对这两种场景进行举例。
需求:
数据展现形式:日期、平台、版本、新用户数、重度活跃用户数(使用次数=50)
日期:20190101-20190102
脚本如下:
WITH a
AS (
--20190101-20190102的平台、版本、重度活跃用户数
SELECT date_8
,platform
,app_version
,count(DISTINCT user_id) AS active_num
FROM app.t_od_use_cnt
WHERE date_8 BETWEEN 20190101 AND 20190102
AND is_active = 1
AND use_cnt = 50
GROUP BY date_8
,platform
,app_version
)
,b
AS (
--20190101-20190102的新增用户数
SELECT date_8
,platform
,app_version
,count(DISTINCT user_id) AS new_user_num
FROM app.t_od_new_user
WHERE date_8 BETWEEN 20190101 AND 20190102
GROUP BY date_8
,platform
,app_version
)
--20190101-20190102的平台、版本、新增用户数、重度活跃用户数
SELECT b.date_8
,b.platform
,b.app_version
,b.new_user_num
,a.active_num
FROM b
LEFT JOIN a
ON a.date_8 = b.date_8
AND a.platform = b.platform
AND a.app_version = b.app_version
GROUP BY b.date_8
,b.platform
,b.app_version
,b.new_user_num
,a.active_num;
这里新用户数每天每个平台每个版本一定都有数据,但重度活跃用户可能并不是每天每个平台每个版本都有,所以新用户数据范围大,作为左表,重度活跃用户数据范围小,作为右表,这样左关联后可以保证所有的数据都不会丢失。如果这里使用JOIN,那么新用户表就会损失一部分数据。
运行结果如下:
b.date_8 b.platform b.app_version b.new_user_num a.active_num
20190101 1 1.1 26 5
20190101 1 1.2 21 3
20190101 1 1.3 27 3
20190101 1 1.4 21 2
20190101 1 1.5 27 4
20190101 2 1.1 21 2
20190101 2 1.2 18 2
20190101 2 1.3 18 1
20190101 2 1.4 20 NULL
20190101 2 1.5 20 3
20190102 1 1.1 42 3
20190102 1 1.2 47 2
20190102 1 1.3 62 NULL
20190102 1 1.4 42 1
20190102 1 1.5 63 4
20190102 2 1.1 49 6
20190102 2 1.2 40 NULL
20190102 2 1.3 53 3
20190102 2 1.4 51 2
20190102 2 1.5 51 2
可以看到活跃用户有三个数值为NULL。
需求:
数据展现形式:日期、平台、老用户数(当日活跃用户去掉当日新增用户)
日期:20190101-20190102
脚本如下:
WITH a
AS (
--20190101-20190102的平台、活跃用户
SELECT date_8
,platform
,user_id
FROM app.t_od_use_cnt
WHERE date_8 BETWEEN 20190101 AND 20190102
AND is_active = 1
GROUP BY date_8
,platform
,user_id
)
,b
AS (
--20190101-20190103的平台、新增用户
SELECT date_8
,platform
,user_id
FROM app.t_od_new_user
WHERE date_8 BETWEEN 20190101 AND 20190102
GROUP BY date_8
,platform
,user_id
)
--20190101-20190102的平台、版本、老用户数(当日活跃用户去掉当日新增用户)
SELECT a.date_8
,a.platform
,count(distinct a.user_id) old_user_num
FROM a
LEFT JOIN b
ON a.date_8 = b.date_8
AND a.platform = b.platform
AND a.user_id = b.user_id
WHERE b.user_id is NULL
GROUP BY a.date_8
,a.platform;
这个脚本逻辑的关键就是最后查询中的 WHERE b.user_id is NULL,因为是左关联,所以先取了左表的全部,这时用WHERE条件限定右表为空,也就是左右表没有关联上的部分。这样就实现了从左表中抛掉左右表重合部分的需求。
运行结果如下:
a.date_8 a.platform old_user_num
20190101 1 371
20190101 2 352
20190102 1 369
20190102 2 363
RIGHT OUTER JOIN返回右表的全部行和左表满足ON条件的行,如果右表的行在左表中没有匹配,那么这一行左表中对应数据用NULL代替。同样的OUTER在这里可以省略。其实说白了就是把LEFT JOIN的左表右表交换下顺序,然后换成RIGHT JOIN 。所以我个人习惯全都使用LEFT JOIN,RIGHT JOIN在工作中从来没有使用过。由于它的用法和LEFT JOIN完全一致,所以这里就不再举例了。
RIGHT JOIN产生表2的完全集,而1表中匹配的则有值,没有匹配的则以null值取代。关系如下图:
FULL OUTER JOIN 会从左表 和右表 那里返回所有的行。如果其中一个表的数据行在另一个表中没有匹配的行,那么对面的数据用NULL代替。同理,这里的OUTER 也可以省略。
FULL JOIN产生1和2的并集。但是需要注意的是,对于没有匹配的记录,则会以null做为值。
关系如下图:
不过老实说,我至今没有发现 FULL JOIN的应用场景,所以在工作中也从来没有用过……所以这里也就不举例了。
下面列出了四种JOIN 类型之间的差异:
另外HIve中还有一种LEFT SEMI JOIN,作用是代替WHERE子查询,但由于JOIN可以实现同样的效果,所以在工作也从来都用不到,这里就不再详细介绍了,感兴趣的同学可以自行百度。
能看到这里的同学,就右上角点个赞顺便关注我吧,3Q~