简介

总体上JOIN的操作如下图所示
在这里插入图片描述
数据准备

create table user 
(
 user_id bigint COMMENT '用户id'
 ,name STRING COMMENT '姓名'
 ,gender STRING COMMENT '性别'
) COMMENT '用户表';

create table class
(
 class_id bigint COMMENT '班级id'
 ,class_name STRING COMMENT '班级名'
 ,user_id BIGINT COMMENT '用户id'
) COMMENT '班级表';

INSERT  INTO user values(1,'李四','男');
INSERT  INTO user values(2,'张三','男');
INSERT  INTO user values(3,'翠花','女');
INSERT  INTO user values(4,'武则天','女');

INSERT  INTO class values(1001,'物理',1);
INSERT  INTO class values(1001,'物理',3);
INSERT  INTO class values(1002,'物理',2);
INSERT  INTO class values(1003,'化学',null);
INSERT  INTO class values(1003,'化学',5);


select * from user ;
user_id	name	gender
4	武则天	女
2	张三	男
3	翠花	女
1	李四	男

select * from class ;
class_id	class_name	user_id
1001	物理	1
1001	物理	3
1002	英语	2
1003	化学 \N
1003	化学 5

1.LEFT JOIN

返回左表的全部数据,和右表中满足on条件的行,如果左表的行在右表中没有匹配的数据,那么这一行中右表对应的数据为null

  --SQL
	SELECT  
	 a.user_id
	,a.name
	,a.gender
	,b.class_id
	,b.class_name
	FROM    (
	        SELECT
	                user_id
	                ,name
	                ,gender
	        FROM    USER
	        ) 
	LEFT JOIN (
	        SELECT
	                class_id
	                ,class_name
	                ,user_id
	        FROM    class
	     )
	ON      a.user_id = b.user_id;
	--结果
	user_id	name	gender	class_id	class_name
	1	李四	男	1001	物理
	2	张三	男	1002	英语
	3	翠花	女	1001	物理
	4	武则天	女	\N	\N

HIVE中没有IN与NOT IN操作,可使用LEFT JOIN方式代替
(1) 获取【a在b中不存在】的数据

--获取【a在b中不存在】的数据
SELECT  
 a.user_id
,a.name
,a.gender
,b.class_id
,b.class_name
FROM    (
        SELECT
                user_id
                ,name
                ,gender
        FROM    USER
        ) a
LEFT JOIN (
        SELECT
                class_id
                ,class_name
                ,user_id
        FROM    class
     )b ON  a.user_id = b.user_id
where b.user_id is NULL ;
--结果
user_id	name	gender	class_id	class_name
4	武则天	女	\N	\N

(2) 获取【a在b中存在】的数据

--获取【a在b中存在】的数据
SELECT  
 a.user_id
,a.name
,a.gender
,b.class_id
,b.class_name
FROM    (
        SELECT
                user_id
                ,name
                ,gender
        FROM    USER
        ) a
LEFT JOIN (
        SELECT
                class_id
                ,class_name
                ,user_id
        FROM    class
     )b ON  a.user_id = b.user_id
where b.user_id is NOT NULL ;
--结果
user_id	name	gender	class_id	class_name
1	李四	男	1001	物理
2	张三	男	1002	英语
3	翠花	女	1001	物理

以上写法也可以使用 left semi join ,详细可见8.1

2.INNER JOIN

返回左右表同时存在的数据,其实INNER JOIN 等价于上述获取【a在b中存在】数据的写法。

--SQL
SELECT  
 a.user_id
,a.name
,a.gender
,b.class_id
,b.class_name
FROM    (
        SELECT
                user_id
                ,name
                ,gender
        FROM    USER
        ) a
INNER JOIN (
        SELECT
                class_id
                ,class_name
                ,user_id
        FROM    class
     )b ON  a.user_id = b.user_id;
 --结果
user_id	name	gender	class_id	class_name
1	李四	男	1001	物理
2	张三	男	1002	英语
3	翠花	女	1001	物理

3.RIGHT JOIN

返回右表中所有的行,和左表中满足on条件的行,如果右表的行在左表中没有匹配,那么这一行中左表的对应数据为NULL。
可以看作是LEFT JOIN的反面,一般用的也较少

   --SQL1
	SELECT  
	 a.user_id
	,a.name
	,a.gender
	,b.class_id
	,b.class_name
	FROM    (
	        SELECT
	                user_id
	                ,name
	                ,gender
	        FROM    USER
	        ) a
	RIGHT JOIN (
	        SELECT
	                class_id
	                ,class_name
	                ,user_id
	        FROM    class
	     )b ON  a.user_id = b.user_id;
	--结果
	user_id	name	gender	class_id	class_name
	1	李四	男	1001	物理
	3	翠花	女	1001	物理
	2	张三	男	1002	英语
	\N	\N	\N	1003	化学
	\N	\N	\N	1003	化学	

一般将主表的关联键保留下来

	--SQL2
	SELECT  
	 b.user_id  --保留主表的关联键
	,a.name
	,a.gender
	,b.class_id
	,b.class_name
	FROM    (
	        SELECT
	                user_id
	                ,name
	                ,gender
	        FROM    USER
	        ) a
	RIGHT JOIN (
	        SELECT
	                class_id
	                ,class_name
	                ,user_id
	        FROM    class
	     )b ON  a.user_id = b.user_id;
	     --结果
	     user_id	name	gender	class_id	class_name
		1	李四	男	1001	物理
		3	翠花	女	1001	物理
		2	张三	男	1002	英语
		\N	\N	\N	1003	化学
		5	\N	\N	1003	化学

4.FULL JOIN

会返回左表,右表所有的行,对应表中没有数据以NULL填充。
(1)SQL1及结果

--SQL1
SELECT  a.*,b.*
FROM    (
            SELECT  user_id
                    ,name
                    ,gender
            FROM    USER
        ) a
FULL JOIN (
              SELECT  class_id
                      ,class_name
                      ,user_id
              FROM    class
          ) b
ON      a.user_id = b.user_id;

--结果
user_id	name	gender	class_id	class_name	user_id2
2	张三	男	1002	英语	2
4	武则天	女	\N	\N	\N
3	翠花	女	1001	物理	3
1	李四	男	1001	物理	1
\N	\N	\N	1003	化学	5
\N	\N	\N	1003	化学	\N

(2)SQL2及结果
FULL JOIN
可以理解为
LEFT JOIN
UNION
RIGHT JOIN

	--SQL2
    SELECT  
	 a.user_id
	,a.name
	,a.gender
	,b.class_id
	,b.class_name
	FROM    (
	        SELECT
	                user_id
	                ,name
	                ,gender
	        FROM    USER
	        ) a
	LEFT  JOIN (
	        SELECT
	                class_id
	                ,class_name
	                ,user_id
	        FROM    class
	     )b ON  a.user_id = b.user_id
	UNION 
	SELECT  
	 b.user_id
	,a.name
	,a.gender
	,b.class_id
	,b.class_name
	FROM    (
	        SELECT
	                user_id
	                ,name
	                ,gender
	        FROM    USER
	        ) a
	RIGHT JOIN (
	        SELECT
	                class_id
	                ,class_name
	                ,user_id
	        FROM    class
	     )b ON  a.user_id = b.user_id;
	     
--结果
   user_id	name	gender	class_id	class_name
	\N	\N	\N	1003	化学
	1	李四	男	1001	物理
	2	张三	男	1002	英语
	3	翠花	女	1001	物理
	4	武则天	女	\N	\N
	5	\N	\N	1003	化学

(3)在取两个表中的共同值的时候,可以使用coalesce

--SQL3
SELECT  coalesce(a.user_id,b.user_id)
        ,a.name
        ,a.gender
        ,b.class_id
        ,b.class_name
FROM    (
            SELECT  user_id
                    ,name
                    ,gender
            FROM    USER
        ) a
FULL JOIN (
              SELECT  class_id
                      ,class_name
                      ,user_id
              FROM    class
          ) b
ON      a.user_id = b.user_id;
--结果
user_id	name	gender	class_id	class_name
2	张三	男	1002	英语
4	武则天	女	\N	\N
3	翠花	女	1001	物理
1	李四	男	1001	物理
5	\N	\N	1003	化学
\N	\N	\N	1003	化学

注意:以上这种FULL JOIN的使用,在JOIN的字段无NULL值的情况下使用,才能保证数据的准确性。

5.CROSS JOIN

把表A和表B的数据进行一个N*M的组合,即笛卡尔积
即是4x5=20条记录

--SQL
SELECT  
/*+MAPJOIN(b)*/a.*,b.*
FROM    (
            SELECT  user_id
                    ,name
                    ,gender
            FROM    USER
        ) a
CROSS JOIN (
              SELECT  class_id
                      ,class_name
                      ,user_id
              FROM    class
          ) b;
 --结果
	user_id	name	gender	class_id	class_name	user_id2
1	李四	男	1001	物理	1
1	李四	男	1001	物理	3
1	李四	男	1002	英语	2
1	李四	男	1003	化学	\N
1	李四	男	1003	化学	5
2	张三	男	1001	物理	1
2	张三	男	1001	物理	3
2	张三	男	1002	英语	2
2	张三	男	1003	化学	\N
2	张三	男	1003	化学	5
3	翠花	女	1001	物理	1
3	翠花	女	1001	物理	3
3	翠花	女	1002	英语	2
3	翠花	女	1003	化学	\N
3	翠花	女	1003	化学	5
4	武则天	女	1001	物理	1
4	武则天	女	1001	物理	3
4	武则天	女	1002	英语	2
4	武则天	女	1003	化学	\N
4	武则天	女	1003	化学	5

JOIN没有写ON条件,等同于CROSS JOIN

   --SQL2
	SELECT  
	/*+MAPJOIN(b)*/a.*,b.*
	FROM    (
	            SELECT  user_id
	                    ,name
	                    ,gender
	            FROM    USER
	        ) a
	JOIN (
	              SELECT  class_id
	                      ,class_name
	                      ,user_id
	              FROM    class
	          ) b;
	--结果
	user_id	name	gender	class_id	class_name	user_id2
	1	李四	男	1001	物理	1
	1	李四	男	1001	物理	3
	1	李四	男	1002	英语	2
	1	李四	男	1003	化学	\N
	1	李四	男	1003	化学	5
	2	张三	男	1001	物理	1
	2	张三	男	1001	物理	3
	2	张三	男	1002	英语	2
	2	张三	男	1003	化学	\N
	2	张三	男	1003	化学	5
	3	翠花	女	1001	物理	1
	3	翠花	女	1001	物理	3
	3	翠花	女	1002	英语	2
	3	翠花	女	1003	化学	\N
	3	翠花	女	1003	化学	5
	4	武则天	女	1001	物理	1
	4	武则天	女	1001	物理	3
	4	武则天	女	1002	英语	2
	4	武则天	女	1003	化学	\N
	4	武则天	女	1003	化学	5

6.JOIN ON和WHERE条件区别

参考文章

7.其他常用SQL操作

7.1 不同数据类型判断空

数据类型判断空备注
STRINGis not null and !=‘’
DOUBLE/BIGINTis not null如果使用DOUBLE/BIGINT != ‘’,会出现返回\N,结果不正确。

例如:

	select 8.0 != ''; --返回\N
	select true and 8.0 != ''; --返回\N

例如:

在hive或者impala中,null值表示这个字段值为空值(没有值),一般情况下我们把数据从原始系统抽取到数仓ODS层的时候,都要做空值处理,比方说用nvl()函数把空值置为0或者空字符串等等,如果不处理,后续可能给开发带来坑。下面以一个具体的例子说明笔者在开发的时候,由于疏忽大意,没对空值进行处理,导致的问题。

具体就是null值在和任何值作比较的时候,都认为不成立。

比方说,有个student表,有个字段为选修科目,为string类型,正常情况,有些学生,有选修课,有些学生没有选选修课,所有这个字段存在null值。

现在我们要把选修课不是为管理学的数据筛选出来。

笔者的第一反应就是

select    *   from   student     where   subject !='管理学';

或者

select  *    from    student   where   subject  not  in  ('管理学')

这种情况查询出来的结果会把选修科目为管理学的数据筛选掉,

但是同时也会把选修科目为NULL的数据筛选掉。原因是因为NULL值和任何值比较时默认认为不成立。

但是我们的本意是要保留这一部分数据的。

所以显然这种查询出来的结果是错的,这个时候我们就要对NULL值做特殊处理,比方说全部置为空字符串。或者置为0

select   *    from    student    where    nvl(subject,'') !='管理学'

7.2 排序或JOIN或Group by字段空值导致结果问题

排序字段(以下a,b,c)为null会导致结果不正确

row_number() over(partition by a,b,c by gmtModified desc) as rk

group by字段(以下a,b,c)为null,会导致结果不正确

group by a ,b ,c 

join字段为null会导致结果不正确

t1  left join t2 on t1.a=t2.a and t1.b=t2.b and t1.c=t2.c

经验:JOIN的字段不能有null,否则会导致结果不正确。

7.3 JOIN操作导致数据膨胀

例如

select 
* from a 
left join b 
on a.id=b.id 

如果在主表a的id唯一情况下,副表b的id不唯一,就会导致数据膨胀(1条数据变多条)。

经验:在JOIN的时候最好保证JOIN的字段唯一,不然会出现数据膨胀,在计算聚合值的时候出现问题。

8.Semi Join和Anti Join

8.1 left semi join

  • 简介

    • LEFT SEMI JOIN (左半连接)是 IN/EXISTS 子查询的一种更高效的实现。
    • 只存在 left SEMI JOIN,不存在SEMI JOIN 和 right SEMI JOI
    • Semi Join,也叫半连接,是从分布式数据库中借鉴过来的方法。它的产生动机是:对于reduce side join,跨机器的数据传输量非常大,这成了join操作的一个瓶颈,如果能够在map端过滤掉不会参加join操作的数据,则可以大大节省网络IO,提升执行效率。
    • 实现方法很简单:选取一个小表,假设是File1,将其参与join的key抽取出来,保存到文件File3中,File3文件一般很小,可以放到内存中。在map阶段,使用DistributedCache将File3复制到各个TaskTracker上,然后将File2中不在File3中的key对应的记录过滤掉,剩下的reduce阶段的工作与reduce side join相同。 left semi join 是只传递表的 join key 给 map 阶段 , 如果 key 足够小还是执行 map join, 如果不是则还是 common join。
  • 特点

    • left semi join 的限制是, JOIN 子句中右边的表只能在 ON 子句中设置过滤条件,在 WHERE 子句、SELECT 子句或其他地方过滤都不行。
    • left semi join 是只传递表的 join key 给 map 阶段,因此left semi join 中最后 select 的结果只许出现左表。
    • 因为 left semi join 是 in(keySet) 的关系,遇到右表重复记录,左表会跳过,而 join 则会一直遍历。这就导致右表有重复值得情况下 left semi join 只产生一条,join 会产生多条,也就是说 left semi join 右表关联key有重复也不会数据膨胀,也会导致 left semi join 的性能更高。
  • 案例验证

8.2 left anti join

  • 简介
  • 左反连接,是not in/ not exists 子查询的一种更高效的实现

参考: hive/spark–left semi/anti join

9.Hive中JOIN的谓词下推

Hive 中的join和谓词下推
Hive的谓词下推

10.Hive Join的数据倾斜

Hive中Join的原理和机制
Hive中的Join可分为Common Join(Reduce阶段完成join)和Map Join(Map阶段完成join)。
往往数据倾斜在Common Join中,Map输出时候以Join on条件中的列为key,根据key的值进行hash,并将key/value按照hash值推送至不同的reduce中,这样确保两个表中相同的key位于同一个reduce中。此时如果join的key是user_id,很可能导致发送到reduce的数据倾斜,导致join的热点问题。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐