Hive QL多表连接查询

Hive中的join概念和RDBMS中是一样的。到目前为止,Hive只支持基于等值条件的join连接,Hive并不支持任何基于非等值条件的join连接。

HiveQL多表连接查询的语法如下:

join_table:
    table_reference [INNER] JOIN table_factor [join_condition]
  | table_reference {LEFT|RIGHT|FULL} [OUTER] JOIN table_reference join_condition
  | table_reference LEFT SEMI JOIN table_reference join_condition
  | table_reference CROSS JOIN table_reference [join_condition] (as of Hive 0.10)
 
table_reference:
    table_factor
  | join_table
 
table_factor:
    tbl_name [alias]
  | table_subquery alias
  | ( table_references )
 
join_condition:
    ON expression   

请参考Hive join官方手册

1、Hive支持表间的等值连接,能够联合两张表的数据。

语法:

     SELECT table_fields
     FROM table_one
     JOIN table_two
     ON (table_one.key_one = table_two.key_one
        AND table_one.key_two = table_two.key_two);

2、Hive支持内连接、左外连接、右外连接和全外连接。Hive只支持等值连接,连接谓词中不支持or。

语法:

     SELECT table_fields
     FROM table_one
     JOIN table_two
     [LEFT, RIGHT, FULL OUTER] JOIN table_two
     ON (table_one.key_one = table_two.key_one AND table_one.key_two = table_two.key_two);

使用左外连接、右外连接和全外连接

例如:

select * from Sales a LEFT OUTER JOIN Sales_orc b ON a.id = b.id;
select * from Sales a RIGHT OUTER JOIN Sales_orc b ON a.id = b.id;
select * from Sales a FULL OUTER JOIN Sales_orc b ON a.id = b.id;
select * from Sales a LEFT OUTER JOIN Sales_orc b ON a.id = b.id WHERE a.fname = 'John';
select * from Sales a RIGHT OUTER JOIN Sales_orc b ON a.id = b.id WHERE a.fname = 'John';

3、使用左半连接(Left Semi-Joins)

左半连接用来查看左表中符合JOIN条件的记录。左半连接被用来在Hive中替代IN/EXISTS子查询。Hive可以使用semi join命令来处理这样的查询。在左半连接中,右手边的表只可以被用在join子句中,不可以用在where子句或select子句中。 其语法如下:

     SELECT table_fields
     FROM table_one
     LEFT SEMI JOIN table_two
     ON (table_one.key_one = table_two.key_one);

例如:

SELECT a.* FROM Sales a LEFT SEMI JOIN Sales_orc b ON a.id = b.id;

下面两句执行时会出错:

SELECT a.*, b.* FROM Sales a LEFT SEMI JOIN Sales_orc b ON a.id = b.id;
SELECT a.* FROM Sales a LEFT SEMI JOIN Sales_orc b ON a.id = b.id WHERE b.id = 1;

4、交叉连接(cross join)

即笛卡尔集连接。在进行表连接时,cross join不是一个高效地、优化的方式。

例如:

SELECT * FROM Sales JOIN Sales_orc;
SELECT * FROM Sales JOIN Sales_orc WHERE Sales.id = 1;
SELECT * FROM Sales CROSS JOIN Sales_orc;

SELECT * 
FROM Sales a 
CROSS JOIN Sales_orc b 
JOIN Location c on a.id = c.id;

5、使用带单个MapReduce的连接

如果相同的key被用在一个连接链中的话,Hive支持使用单个MapReduce在多个表间连接。

例如,要执行一个三表连接,其语法如下:

     SELECT table_one.key_one, table_two.key_one, table_three.key_one
     FROM table_one 
     JOIN table_two
        ON (table_one.key_one = table_two.key_one)
     JOIN table_three
        ON (table_three.key_one = table_two.key_one);

6、使用最大表最后(Largest Table Last)

Hive执行join的方式:缓冲第一张表,然后将最后一张表映射到它们。最佳实践:总是最后列出最大的表,因为这将加快处理速度。

语法:

     SELECT table_one.key_one, table_two.key_one, table_three.key_one
     FROM table_one 
     JOIN table_two ON (table_one.key_one = table_two.key_one)
     JOIN table_three ON (table_three.key_one = table_two.key_one);

其中table_one和table_two缓存在内存,而table_three直接从磁盘映射。

7、map端join

当在Hive中进行多表连接时,会有这样的场景:其中一个表较小(行较少),而另一个表较大。为了以更有效的方式得到结果,Hive使用map-side join。

在map-side join中,将较小的表缓存在内存中,而大表通过mapper进行传输。 通过这样做,Hive只在mapper的一侧完成连接,从而消除了reducer job。通过这样做,性能得到了极大的提高。

在Hive中,使用map-side join的方式有两种:

  • 在select关键字后使用/*+ MAPJOIN(<table_name>)*/ 暗示. 其中table_name必须是小表的表名。(这是旧的用法)
  • 另一种方式是设置如下的属性为true,然后运行一个join查询:set hive.auto.convert.join=true;
select /*+ mapjoin(Sales_orc)*/ a.fname, b.lname from Sales a join Sales_orc b on a.id = b.id;
select a.* from Sales a join Sales_orc b on a.id = b.id and a.fname = b.fname;

使用map端join有一些限制。下面是不支持使用map-side join:

  • Union后跟MapJoin
  • Lateral view后跟一个MapJoin
  • Reduce sink (group by/join/sort by/cluster by/distribute by)后跟MapJoin
  • MapJoin后跟union
  • MapJoin后跟join
  • MapJoin后跟MapJoin

注意:

/*+ MAPJOIN(table_name) */暗示应该只用于数据被排序过或表被分桶时。 在这种场景下,join自动地被转换到一个bucket map join或一个bucket sort merge map join, 所以,使用set hive.auto.convert.join=true; 来代替语句中的暗示。

Hive QL多表连接应用示例

1、使用相等连接来join连接表。

Hive支持表间的等值连接,能够让我们联合两张表的数据。

语法:

SELECT table_fields
FROM table_one
JOIN table_two
ON (table_one.key_one = table_two.key_one
    AND table_one.key_two = table_two.key_two);    

下面我们创建两个表,person和address表,填充数据,并对这两张表执行join连接操作。

-- 创建person表
create table if not exists person (
        persid int,
	pername string
)
clustered by (persid) into 1 buckets
stored as orc 
tblproperties('transactional'='true');

-- 填充一些测试数据
insert into table person values
(0,'黄渤'),
(1,'黄晓明'),
(2,'迪丽热巴'),
(3,'岳云鹏');

-- 查询
select * from person;

-- 创建另一张表address
create table if not exists address(
        persid int,
        addr string
)
clustered by (persid) into 1 buckets
stored as orc 
tblproperties('transactional'='true');

-- 填充一些数据
insert into table address values
(0,'北京'),
(1,'上海'),
(2,'乌鲁木齐');

-- 查询
select * from address;

-- 现在有了两张表,接下来执行join操作:
select person.pername, address.addr
from person join address on (person.persid = address.persid);

2、使用外连接

Hive支持表间的相等连接,使用left,right 和full outer连接。

语法:

SELECT table_fields
FROM table_one
    LEFT, RIGHT, FULL OUTER] JOIN table_two
    ON (table_one.key_one = table_two.key_one
        AND table_one.key_two = table_two.key_two);

下面对person表和address表执行外连接操作。

-- 左外连接
select person.pername,address.addr
from person left join address on (person.persid = address.persid);

-- 右外连接
select person.pername,address.addr
from person right join address ON (person.persid = address.persid);

-- 全外连接
select person.pername,address.addr
from person full outer join address (person.persid = address.persid);

3、使用左半连接(Left Semi-Joins)

select person.pername
from person left semi join address on(person.persid = address.persid);

4、使用带单个MapReduce的连接

Hive支持使用单个MapReduce在多个表间连接,如果相同的key被用在一个连接链中的话。

语法:

SELECT table_one.key_one, table_two.key_one, table_three.key_one
FROM table_one 
JOIN table_two
    ON (table_one.key_one = table_two.key_one)
JOIN table_three
    ON (table_three.key_one = table_two.key_one);

下面演示在一个MapReduce中实现三表连接。

-- 首先,创建第三张表account
create table account (
	persid int,
	bamount int
)
clustered by (persid) into 1 buckets
stored as orc 
tblproperties('transactional'='true');

-- 填充一些数据
insert into table account values(1,12000),(2,9000);

-- 查询
select * from account;

-- 执行三表连接,以相同的列名作为连接列
select person.pername,address.addr,account.bamount
from person
     join address on (person.persid = address.persid)
     join account on (person.persid = account.persid);

注意底层的MapReduce job作业,会发现这个三表连接只需要一个MapReduce作业。


《PySpark原理深入与编程实战》