张工是一名程序员,主要是做java开发,有次到一家软件公司面试软件开发岗位,面试官问了他两个问题,其中有一个问题是这样的这样:

说说left join和left semi join 有什么区别?

对于left join 张工在平时编写sql用得比较多,但对于left semi join张工很少用到,所以说不出个所以然来了。

面试官见张工回答不上来,由于张工应聘的岗位对编写sql的水平要求较高,面试官就没有继续再问下去了,叫他先回去等通知。

太扎心了!一个问题没回答上来就被叫回去等通知了。

我们来看看hive 中的left join和left semi join有什么区别。

对于left join 我们平时经常用到,就不再具体阐述了,重点来关注下left semi join。

left semi join

可以这么理解,LEFT SEMI JOIN (左半连接)是 IN/EXISTS 子查询的一种更高效的实现。

示例

select 
    a.key,
    a.value
from table_name_a a
where a.key in (select b.key from table_name_b b)

我们可以把这条sql可改写成

select 
    a.key,
    a.value
from table_name_a  a
left semi join table_name_b on(a.key=b.key)
  • left semi join特点
  1. left semi join 的限制, join子句中右边的表只能在 on子句中设置过滤条件,在 where子句、select子句或其他地方过滤都是不可取的。
  2. 因为left semi join 是只传递表的 join key 给 map 阶段,所以left semi join 中最后 select 的结果只许出现左表。
  3. 因为 left semi join 是 in(keySet) 的关系,遇到右表重复记录,左表会跳过,而 join 则会一直遍历。这就导致右表有重复值的情况下 left semi join 只产生一条,join 会产生很多条记录,也会导致影响 left semi join 的性能更高。

上面的描述可能不太好理解,理解起来还是很吃力,我们来举个实例。

先建立两张表(学生表和分数表)并将这两张表进行初始化数据。

  • --学生表
create table test.tb_student (
  id int comment 'id',
  name string comment '姓名',
 age int comment '年龄'
)

-- 初始化数据

INSERT INTO test.tb_student VALUES ('1', '张三', '7');
INSERT INTO test.tb_student VALUES ('2', '李四', '7');
INSERT INTO test.tb_student VALUES ('3', '王五', '7');
INSERT INTO test.tb_student VALUES ('4', '小爱', '7');

查询下test.tb_student表的数据

select * from test.tb_student

left join 改成 inner join就能用到索引吗_面试

  • --分数表
create table test.course (
 id int comment '学生id',
name string comment '课程名称',
score string comment '分数'
)

-- 初始化数据

INSERT INTO test.course VALUES ('1', '语文','90');
INSERT INTO test.course VALUES ('1', '数学','100');
INSERT INTO test.course VALUES ('1', '英语','90');

INSERT INTO test.course VALUES ('2', '语文','90');
INSERT INTO test.course VALUES ('2', '数学','90');
INSERT INTO test.course VALUES ('2', '英语','100');

INSERT INTO test.course VALUES ('3', '语文','100');
INSERT INTO test.course VALUES ('3', '数学','100');
INSERT INTO test.course VALUES ('3', '英语','99');

INSERT INTO test.course VALUES ('4', '语文','100');
INSERT INTO test.course VALUES ('4', '数学','100');
INSERT INTO test.course VALUES ('4', '英语','100');

查询下test.course表的数据

select * from test.course

left join 改成 inner join就能用到索引吗_面试_02

通过上面的操作,我们已经成功创建了两张表并对其进行数据初始化。

下面我们来看看用left semi join和left join 操作后,查询出来的数据是怎样的?

-- left semi join

select * from test.tb_student a
left semi join test.course b on(a.id=b.id)

查询结果:

left join 改成 inner join就能用到索引吗_java_03

细心的你会发现,这不就是我们前面查询的test.tb_student表的数据吗?没错,确实是的,这是因为left semi join的select的结果中只允许出现t1(左表)表的字段

刚才的sql其实可以等价于

select * from test.tb_student a where a.id in (select id from test.course)

select * from test.tb_student a where exists (select 1 from test.course b where a.id=b.id)

他们执行的结果是一致的。

我们再来看看用left join查询出来会是什么样的结果。

--left join

select * from test.tb_student a
left join test.course b on a.id=b.id

查询结果:

left join 改成 inner join就能用到索引吗_mysql_04

从上面两者的执行结果我们可以清楚地看到left semi join 和 left join两者的区别了,这也应证了上面提到的关于left semi join的特点。

  1. left semi join 是只传递表的 join key 给 map 阶段,因此left semi join 中最后 select 的结果只许出现左表。
  2. left semi join 遇到右表重复记录,左表会跳过

面试时,要是面试官要是问起left join和left semi join 两者有什么区别这么细节的问题,比较友好地回答不妨参考这样的:

  1. left semi join join子句中右边的表只能在 on子句中设置过滤条件
  2. left semi join 中最后 select 的结果只许出现左表。
  3. 因为 left semi join 是 in(keySet) 的关系,遇到右表重复记录,左表会跳过,而 join 则会一直遍历。

要是能够回答出left semi join的特点,说明你完全有这方面的开发经验,相信能为这次面试加分。

总结:

  1. 对右表中重复key的处理方式差异:因为 left semi join 是 in(keySet) 的关系,遇到右表重复记录,左表会跳过,而 join on 则会一直遍历。
  2. left semi join 中最后 select 的结果只许出现左表,因为右表只有 join key 参与关联计算了,而 join on 默认是整个关系模型都参与计算了。

由于笔者知识及水平有限,文中错漏之处在所难免,如有不足之处,欢迎交流。

-END-