数据分析课程笔记 - 18 - HIVE 核心技能之表连接

大家好呀,这节课是 HIVE 的第三次课,我们要学习 HIVE 的表连接。表连接我们之前在 MySQL 部分学过,其实就是 Join 和 Union,包括四种 Join 形式和两种 Union 形式。MySQL 的表连接和 HIVE 的表连接用法大体相同,但存在一些细节上的差异,所以这节课我们会通过一些实际案例中相对复杂的需求来学习 HIVE 中表连接的用法。

本节课主要内容:

1、JOIN
(1)INNER JOIN:需求1、2、3
(2)LEFT JOIN:需求4、5、6
(3)FULL JOIN
2、UNION & UNION ALL:需求7、8、9
3、重点练习:需求10、11

一、JOIN

1、INNER JOIN

内连接:返回两个表的交集,

举例说明:

表1:user_list_1

user_id user_name
10001 Abby
10002 Ailsa
10003 Alice
10004 Alina
10005 Allison
10006 Angelia

表2:user_list_2

user_id user_name
10001 Abby
10003 Alice
10004 Alina
10007 Amanda
10008 Anne
10009 Ann

找出既在 user_list_1 也在 user_list_2 的用户:

select *
from user_list_1 a
inner join user_list_2 b
on a.user_id = b.user_id;

注意

  • 表连接时,必须进行重命名
  • on后面使用的连接条件必须起到唯一键值的作用
  • inner可省略不写,效果一样

需求1:找出在2019年购买后又退款的用户

这个需求需要用到两个表,一个是 user_trade,一个是 user_refund,要找出在2019年购买后又退款的用户,那就需要在 user_trade 表中找出2019年购买过的用户,再在 user_refund 表中找出在2019年退款过的用户,两个取交集即可。所以我们需要先从两个子查询中选出相应的 user_name(注意去重),再用 JOIN 对 user_name 取交集。

注意: 由于一般情况下先有购买,才又退款,所以我们不用特别考虑时间先后的问题。对于2018年购买,2019年退款的情况,事实上并不在题目要求的范围内,因为题目要求的是2019年购买。

user_trade表结构
user_refund表结构
SELECT trade.user_name
FROM
(SELECT distinct user_name
FROM user_trade
WHERE year(dt)=2019) as trade
JOIN
(SELECT distinct user_name
FROM user_refund
WHERE year(dt)=2019) as refund
on trade.user_name = refund.user_name;

!!注意:⼀定要先去重,再做表连接,养成良好习惯!!虽然可以先连接后再去重,但是这么做的话,执行效率会低。

需求1结果

需求2:在2017年和2018年都购买的用户

这个需求跟需求1类似,甚至更简单,因为只用同一张表,当然由于要取交集,所以还是要用两个子查询。

SELECT a.user_name
FROM
(SELECT DISTINCT user_name
FROM user_trade
WHERE year(dt)=2017) a
JOIN
(SELECT DISTINCT user_name
FROM user_trade
WHERE year(dt)=2018) b
on a.user_name = b.user_name;

这个题运行结果太长,就不截图了。

需求3:在2017年、2018年、2019都有交易的用户

很简单,做两次连接即可。

SELECT a.user_name
FROM
(SELECT DISTINCT user_name
FROM user_trade
WHERE year(dt)=2017) a
JOIN
(SELECT DISTINCT user_name
FROM user_trade
WHERE year(dt)=2018) b
on a.user_name = b.user_name
JOIN
(SELECT DISTINCT user_name
FROM user_trade
WHERE year(dt)=2019) c
on b.user_name = c.user_name;

注意: 这里 b 和 c 的 JOIN ON 应该是 b 和 c 的 user_name。当然你也可以先 a b连接得到一张表再和 c 连接,道理都一样,只是顺序问题而已。

需求3结果

2、LEFT JOIN

首先,回到我们最开始的简单例子:

表1:user_list_1

user_id user_name
10001 Abby
10002 Ailsa
10003 Alice
10004 Alina
10005 Allison
10006 Angelia

表2:user_list_2

user_id user_name
10001 Abby
10003 Alice
10004 Alina
10007 Amanda
10008 Anne
10009 Ann

先来看⼀下,对表1和表2进行左连接后,发生了什么。

SELECT *
FROM user_list_1 a
LEFT JOIN user_list_2 b
ON a.user_id=b.user_id;
左连接结果

结论: 进行左连接后,以左边的表1为全集,返回能够匹配上的右边
表2的匹配结果,没有匹配上的则显示NULL。

拓展:
right join:以右边的表为全集,返回能够匹配上的右表的匹配结
果,没有匹配上的则显示NULL。但其完全可以由left join改写出同样的结果,所以较少使用。

左连接和右连接

举例说明:

如何取出在user_list_1表中但是不在user_list_2的用户?

SELECT a.user_id,
a.user_name
FROM user_list_1 a
LEFT JOIN user_list_2 b
ON a.user_id=b.user_id
WHERE b.user_id is null;

需求4:在2019年购买,但是没有退款的用户

这个需求和需求1对应,分别取出 user_trade 2019年购买用户和user_refund 2019年退款用户,再以左边为准,取右边为 null 的用户即可。

SELECT trade.user_name
FROM
(SELECT DISTINCT user_name
FROM user_trade
WHERE year(dt)=2019) trade
LEFT JOIN
(SELECT DISTINCT user_name
FROM user_refund
WHERE year(dt)=2019) refund
on trade.user_name = refund.user_name
WHERE refund.user_name is null;

注意: is null 可以用来判断数据是否为 null 。

需求4结果

这个结果可以跟需求1的结果对比一下,应该是没有重复的,有重复的话就不对了。

需求5:在2019年有购买的用户的学历分布

这个需求需要用到两个表,一个是交易表 user_trade,一个是用户信息表 user_info,学历信息在 user_info 的 extra 字段的 education 键中。解题思路是,先把 user_trade 表中 2019 年有购买记录的用户选出来作为左表,再把 user_info 中的用户名和学历情况选出来作为右表,二者左连接即可取出在 2019 年有购买的用户的学历情况。那么要看学历分布的话,只需要最后根据右表的 education 字段分一下组,再统计一下人数即可。

SELECT b.education,
    count(a.user_name)
FROM
    (SELECT DISTINCT user_name
    FROM user_trade
    WHERE year(dt)=2019) a
    LEFT JOIN
    (SELECT user_name,
    get_json_object(extra1,'$.education') as education
    FROM user_info) b
    on a.user_name = b.user_name
GROUP BY b.education;
需求5结果

需求6:在2017和2018年都购买,但是没有在2019年购买的用户

这个需求跟前面的需求3类似,三张表连接,第三张表取 null 值。不过要注意连接类型,2017和2018年取交集用 inner join,但和2019年的连接应该是左连接,因为要以 2017 和 2018 为准。这里我们正好有 trade_2017、trade_2018、trade_2019 三张表,所以我们就不从 user_trade 表中取了。

SELECT a.user_name
FROM
    (SELECT user_name
    FROM trade_2017) a
    JOIN
    (SELECT user_name
    FROM trade_2018) b
    on a.user_name = b.user_name
    LEFT JOIN
    (SELECT user_name
    FROM trade_2019) c
    on b.user_name = c.user_name
WHERE c.user_name is null;

这里运行结果也很多,所以就不截图了。

3、FULL JOIN

举例说明:

首先,我们还是来看看对一开始最简单的表1和表2进行全连接后,会发生什么。

SELECT *
FROM user_list_1 a
FULL JOIN user_list_2 b
ON a.user_id=b.user_id;
全连接举例
全连接图示

那么,选出 user_list_1 和 user_list_2 的所有用户就可以用如下语句:

SELECT coalesce(a.user_name,b.user_name)
FROM user_list_1 a
FULL JOIN user_list_2 b
on a.user_id=b.user_id;
全连接取全集

这里有一个新知识点:coalesce 函数,它主要用来取全集,它的参数是 (expression_1, expression_2, ...,expression_n),它会依次看各个参数表达式,遇到非 null 的值就会返回该值,并继续。如果所有的表达式都是空值,它最终将返回⼀个空值。

二、UNION & UNION ALL

表1:user_list_1

user_id user_name
10001 Abby
10002 Ailsa
10003 Alice
10004 Alina
10005 Allison
10006 Angelia

表3:user_list_3

user_id user_name
10290 Michael
10291 Avery
10292 Reilly
10293 Dillon
10294 Walton

举例说明:

将user_list_1和user_list_3合并在⼀起:

SELECT user_id,
user_name
FROM user_list_1
UNION ALL
SELECT user_id,
user_name
FROM user_list_3 ;
image.png

注意:

  • 字段名称必须⼀致!
  • 字段顺序必须⼀致!
  • 没有连接条件!

UNION ALL和UNION的区别:

对比 UNION ALL UNION
对重复结果的处理 不会去除重复记录 在进行表连接后会筛选掉重复的记录
对排序的处理 只是简单的将两个结果合并后就返回 将会按照字段的顺序进行排序
效率 更快 更慢
总述 不去重不排序 去重且排序

注意:如果表很大时推荐先去重,再进行union all。

常见错误:

常见错误一:没有对UNION ALL后的表进行重命名:

-- 错误写法
SELECT count(distinct user_name)
FROM
(
SELECT user_name
FROM trade_2017
UNION ALL
SELECT user_name
FROM trade_2018
UNION ALL
SELECT user_name
FROM trade_2019);

常见错误二:直接对表进行UNION ALL:

-- 错误写法
SELECT count(distinct user_name)
FROM trade_2017
UNION ALL trade_2018
UNION ALL trade_2019;

练习

需求7:2017-2019年有交易的所有用户数

这个需求就是取出2017-2019所有有交易的用户进行 union,得到的就是去重的所有用户,最后再对这些用户 count 一下求出用户数即可。

SELECT count(a.user_name)
FROM
(SELECT user_name
FROM trade_2017
UNION
SELECT user_name
FROM trade_2018
UNION
SELECT user_name
FROM trade_2019) a;
需求7结果

需求8:2019年每个用户的支付和退款金额汇总

这个需求是要求出2019年每个用户的支付金额和退款金额的汇总数,支付金额和退款金额分别来自 user_trade 和 user_refund 两张表,每张表根据 user_name 分组并进行聚合计算即可算出每个人的金额总数。最后把两张表 UNION ALL 一下。但是,这里有一个问题需要注意一下,user_trade 表中选出来的数据有两列:user_name 和 每个人的 pay_amount 加总,user_refund 表选出来的数据也有两列: user_name 和每个人的 refund_amount 加总,但是我们知道,UNION ALL 要求两张表的字段名称和字段顺序都一样,但是现在这两张需要 UNION ALL 的表字段不一样,怎么办呢?

我们可以给它们分别补一列数据,user_trade 表补充一列 refund_amount,user_refund 表补充一列 pay_amount,两列补充的数据都规定它们为 0 即可。同时还要注意补充字段的顺序,两张表要保持一致!

代码如下:

SELECT a.user_name, 
    sum(a.pay_amount), 
    sum(a.refund_amount)
FROM
    (SELECT user_name,
        sum(pay_amount) as pay_amount,
        0 as refund_amount
    FROM user_trade
    WHERE year(dt)=2019
    GROUP BY user_name
    UNION ALL
    SELECT user_name,
        0 as pay_amount,
        sum(refund_amount) as refund_amount
    FROM user_refund
    WHERE year(dt)=2019
    GROUP BY user_name) a
GROUP BY a.user_name;

注意: 最后父查询也还是要根据 user_name 进行分组并做聚合运算,因为 UNION ALL 不会去重!

需求8结果1

当然,这个需求也可以用 FULL JOIN 实现,思路就是先每张表做分组聚合,再两张表 FULL JOIN 最后用 coalesce 函数取个全集:

select coalesce(a.user_name,b.user_name) as user_name,
    if(a.pay_amount is null,0,a.pay_amount) as pay_amount,
    if(b.refund_amount is null,0,b.refund_amount) as refund_amount
from
    (select user_name,
        sum(pay_amount) as pay_amount
    from user_trade
    where year(dt)=2019
    group by user_name) a
    full join
    (select user_name,
        sum(refund_amount) as refund_amount
    from user_refund
    where year(dt)=2019
    group by user_name) b on a.user_name=b.user_name;

运行结果就不截图啦~

需求9:2019年每个支付用户的支付金额和退款金额

这个需求比上个需求多了一个条件,就是要求必须是支付用户。上一个需求可能还存在没有支付只有退款的用户,这个需求要求是必须有支付的用户,那就应该用 LEFT JOIN 来做表连接,那父查询也就不需要用 coalesce 函数取全集了,直接取左表的字段即可。

select a.user_name,
    a.pay_amount,
    b.refund_amount
from
    (select user_name,
        sum(pay_amount) as pay_amount
    from user_trade
    where year(dt)=2019
    group by user_name) a
    left join
    (select user_name,
        sum(refund_amount) as refund_amount
    from user_refund
    where year(dt)=2019
    group by user_name) b on a.user_name=b.user_name;
需求9结果

三、重点练习

需求10:首次激活时间在2017年,但是一直没有支付的用户年龄段分布

这个需求有首次激活时间、年龄以及支付情况,那就需要用到 user_info 和 user_trade 两个表,先从 user_info 表中选出首次激活时间在 2017 年的用户及其年龄分组,再和从 user_trade 表中选出的 user_name (注意去重)表进行左连接,筛选条件是右边表的 user_name 为 null。最后再对结果按照年龄段分组,进行聚合计算即可。

select a.age_type,
    count(*)
from
    (select case when age < 20 then '20岁以下'
            when age >=20 and age < 30 then '20-30岁'
            when age >=30 and age < 40 then '30-40岁'
            else '40岁以上' end as age_type,
            user_name
    from user_info
    where year(firstactivetime)=2017) a
    left join
    (select distinct user_name
    from user_trade
    where dt is not null) b on a.user_name=b.user_name
where b.user_name is null
group by a.age_type;
需求10结果

需求11:2018、2019年交易的用户,其激活时间段分布

这个需求是要 得出2018和2019支付用户全集的激活时间段分布,需要用到 trade_2018 、trade_2019 和 user_info 三张表。

先分别从两张交易表中取出去重过的用户进行 union,然后再和 user_info 表基于 user_name 进行左连接。连接后的表中包含了 user_info 表的完整字段,我们从中选出激活时间字段,将其转换为小时,再进行分组聚合计算。

select hour(b.firstactivetime) as hour,
    count(*)
from
        (select user_name
        from trade_2018
        union
        select user_name
        from trade_2019) a
        left join user_info b on a.user_name=b.user_name
group by hour(b.firstactivetime);
需求11结果

好啦,这节课的内容就是这些,还是那句话,自己先思考,试着写写,再看参考代码。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容