testbook

多表查询:JOIN子句

语法

SELECT select_expression, select_expression, ...
FROM table_reference JOIN table_reference JOIN table_reference, ... [ON join_condition]
说明
join_condition 连接条件可以是等值条件(这种连接叫做等价连接),也可以是不等值条件。
  • 笛卡尔连接:Cartesian Product
    语法
    SELECT select_expression, select_expression, ...
    FROM table_name JOIN table_name JOIN table_name, ...
    
    如果在JOIN子句中不指定连接条件,这样的连接就是笛卡尔连接。连接的结果是由被连接表中所有记录组成的有序数组(ordered tuples)。所以,笛卡尔连接输出的记录条数等于被连接表的记录条数的乘积。
    举例
    [$host] transwarp> SELECT \* FROM join_demo1;
    1
    2
    3
    [$host] transwarp> SELECT \* FROM join_demo2;
    a
    b
    c
    d
    [$host] transwarp> SELECT \* FROM join_demo1 JOIN join_demo2;
    1       a
    1       b
    1       c
    1       d
    2       a
    2       b
    2       c
    2       d
    3       a
    3       b
    3       c
    3       d
    [$host] transwarp> SELECT count(\*) join_demo1 JOIN join_demo2;
    12
    
    这样的表连接会产生大量的数据,而且并不经常具有意义,所以很少出现在实际应用中。大多数这样的表连接产生于没有加JOIN条件的错误。所以为了代码的清晰,如果真的需要进行笛卡尔积连接,最好使用专门的关键词CROSS JOIN:
    语法
    SELECT table1.col1, table2.col2, ...
    FROM table1 CROSS JOIN table2
    
  • 内连接:INNER JOIN
    语法
    SELECT select_expression, select_expression, ...
    FROM table_refernce1 (JOIN|INNER JOIN) table_reference2
    ON (table_reference1.column_1 = table_reference2.column_a)
    
    内连接只显示参与连接的表中有匹配的记录。JOIN和INNER JOIN在这里用法一样。
    在一次查询中可以连接两个以上的表:
    SELECT a.val, b.val, c.val FROM a JOIN b ON (a.key = b.key1) JOIN c ON (c.key = b.key2)
    
  • 举例

    假设我们有两张表,student_info和course_info。student_info包含了五个学生的名字和他们分别选修的课程编号:

    transwarp> SELECT \* FROM student_info;
    赵一  SCU100
    钱二  MUS101
    孙三  NULL
    李四  COM101
    周五  BIO101
    
    course_info包含了五门选修课的课程编号和课程名称:
    transwarp> SELECT \* FROM course_info;
    BIO101        生物基础
    COM101        面向对象编程
    MUS101        西方古典音乐赏析
    SCU100        雕塑
    MAT100        微积分
    
    现在我们想要通过将两张表在课程编号匹配值处连接来查看学生选择的选修课课名:
    [$host] transwarp> SELECT student_info.stu_name, course_info.course_name FROM student_info JOIN course_info ON (student_info.course_id = course_info.course_id);
    赵一    雕塑
    钱二    西方古典音乐赏析
    李四    面向对象编程
    周五    生物基础
    
    更多例子
    使用JOIN可以从用户信息表和交易信息表中查看哪些用户进行了哪些交易:
    [$host] transwarp> SELECT user_info.name, transactions.trans_id FROM user_info JOIN transactions ON (user_info.acc_num = transactions.acc_num);
    马从筠  943197522
    马从筠  499506900
    马从筠  289018112
    马从筠  895916502
    马从筠  404905188
    马从筠  213859826
    祝韩恒  900192386
    祝韩恒  952639648
    华微    594819547
    华微    817414815
    魏向卉  929634984
    魏向卉  209441379
    魏向卉  719753265
    魏向卉  597565609
    邱坤    648230055
    邱坤    975639131
    邱坤    459590958
    潘营泽  952110653
    李韩瑶  991691937
    管淑艳  162742112
    
  • 外连接:OUTER JOIN
    内连接会将被连接的两张表中互相没有匹配值的纪录忽略。如果想要在连接结果中看到没有匹配值的记录,则应该使用外连接。外连接又分为左外连接(left outer join)、右外连接(right outer join)和全外连接(full outer join)。
    语法
    SELECT select_expression, select_expression, ...
    FROM table_reference (LEFT|RIGHT|FULL) OUTER JOIN table reference
    
    左外连接:LEFT OUTER JOIN
    在之前的学生选修课程的例子中,孙三的记录因为他暂时没有选修课而被忽略了。如果我们想要在查询结果中能够看到暂时没有选修课的学生,我们可以用左外连接 命令Inceptor将左边表中(这里的student_info)的所有记录返回:
    [$host] transwarp> SELECT student_info.stu_name, course_info.course_name FROM student_info LEFT OUTER JOIN course_info ON (student_info.course_id = course_info.course_id);
    赵一    雕塑
    钱二    西方古典音乐赏析
    孙三    NULL
    李四    面向对象编程
    周五    生物基础
    
  • 右外连接:RIGHT OUTER JOIN
    右外连接和左外连接相似,但是会将右边表(这里的course_info)中的所有记录返回:

    [$host] transwarp> SELECT student_info.stu_name, course_info.course_name FROM student_info RIGHT OUTER JOIN course_info ON (student_info.course_id = course_info.course_id);
    周五    生物基础
    李四    面向对象编程
    钱二    西方古典音乐赏析
    赵一    雕塑
    NULL    微积分
    

    全外连接:FULL OUTER JOIN
    如果想要将两张表中的所有记录返回,可以用全外连接:
    [$host] transwarp> SELECT student_info.stu_name, course_info.course_name FROM student_info FULL OUTER JOIN course_info ON (student_info.course_id = course_info.course_id);
    周五    生物基础
    赵一    雕塑
    NULL    微积分
    李四    面向对象编程
    钱二    西方古典音乐赏析
    孙三    NULL
    
  • 隐式连接:Implicit JOIN
    语法
    SELECT table1.col1, table2.col2, ...
    FROM table1, table2
    WHERE table1.col3 = table2.col1;
    
    隐式JOIN的命令中不含有JOIN...ON...关键词,而是通过WHERE子句作为连接条件将两张表连接。
    例子
    [$host] transwarp> SELECT student_info.stu_name, course_info.course_name FROM student_info, course_info WHERE student_info.course_id = course_info.course_id;
    赵一    雕塑
    钱二    西方古典音乐赏析
    李四    面向对象编程
    周五    生物基础
    
    在连接和过滤都有的语句中,使用隐式JOIN会让人容易混淆哪些子句是用来连接的而哪些子句是用来过滤的。比如:
    [$host] transwarp> SELECT name, trans_id FROM user_info, transactions WHERE user_info.acc_num = transactions.acc_num AND trans_time < 20140630235959;
    马从筠  943197522
    马从筠  499506900
    马从筠  404905188
    马从筠  213859826
    祝韩恒  952639648
    华微    817414815
    魏向卉  929634984
    魏向卉  209441379
    魏向卉  719753265
    魏向卉  597565609
    邱坤    648230055
    邱坤    975639131
    邱坤    459590958
    潘营泽  952110653
    
  • 自然连接:NATURAL JOIN
    语法
    SELECT table1.col1, table1.col2, ... table2.col1, table2.col3, ...
    FROM table1 NATRUAL JOIN table2;
    
    使用NATURAL JOIN,用户可以不需要明确写出JOIN条件。Inceptor会自动在被连接的两张表中寻找名字相同的列。如果两张表table1和table2中存在同名列col1,Inceptor会自动生成JOIN条件table1.col1=table2.col1。如果Inceptor在被连接的两张表中找不到同名列,Inceptor会将指令作为无条件的连接,也就是一个笛卡尔积。
    举例
    下例查询所有用户和他们进行过交易的流水号。
    [$host] transwarp> SELECT name, trans_id from user_info NATURAL JOIN transactions;
    邱坤    648230055
    邱坤    975639131
    邱坤    459590958
    李韩瑶  991691937
    管淑艳  162742112
    马从筠  943197522
    马从筠  499506900
    马从筠  289018112
    马从筠  895916502
    马从筠  404905188
    马从筠  213859826
    华微    594819547
    华微    817414815
    魏向卉  929634984
    魏向卉  209441379
    魏向卉  719753265
    魏向卉  597565609
    潘营泽  952110653
    祝韩恒  900192386
    祝韩恒  952639648
    
  • 多表连接
    可以在一次查询中用多个JOIN子句连接多张表。
    语法
    SELECT select_expression, select_expression, ...
    FROM table_reference [(RIGHT|LEFT|FULL) OUTER] JOIN table_reference ON (join_condition) [(RIGHT|LEFT|FULL) OUTER] JOIN table_reference ON (join_condition) ...
    
    说明
    每一个JOIN子句都可以是不同的连接(内连接,左/右/全外连接,左半连接等等)。
    举例
    这里有一张记录了教师和他们所教课程编码的表:
    [$host] transwarp> SELECT \* FROM lecturer_info;
    石仲华  MAT101
    李周    COM101
    樊玲    MUS101
    曹东海  SCU100
    钱文    BIO101
    
    我们通过连接学生信息表,课程信息表和教师信息表来查看所有学生正在上的课和课程的教师
    [$host] transwarp> SELECT stu_name, course_name, lecturer_name FROM student_info LEFT OUTER JOIN course_info ON (student_info.course_id = course_info.course_id)LEFT OUTER JOIN lecturer_info ON (course_info.course_id = lecturer_info.course_id);
    赵一    雕塑    曹东海
    钱二    西方古典音乐赏析        樊玲
    孙三    NULL    NULL
    李四    面向对象编程    李周
    周五    生物基础        钱文
    
  • 重复连接
    有时候同一张表在一次查询中需要和多张其他表连接。假设我们有三张表table1, table2和table3。其中table1要和table2和table3各连接一次。这种情况下,用户需要给table1在两次连接中起两个不同的化名来让Inceptor能够分辨在各子句中table1的角色:
    SELECT t1.col1, tb1.col2, t2.col1, t3.col,...
    FROM table1 t1 JOIN table2 t2
        ON t1.col1 = t2.col1
        INNER JOIN table3 t3
        ON t2.col2 = t3.col1
        INNER JOIN table1 tb1
        ON t3.col = tb1.col2;
    
  • 表的自连接
    一张表也可以和自己连接,此时需要给表取两个不同的化名来让Inceptor能够分辨在各子句中表的角色。
    语法
    SELECT select_expression, select_expression, ...
    FROM table_reference alias1 JOIN table_reference alias2 ON (join_condition)
    
    举例
    我们用一个包含了员工信息的表来作为例子。表中含有员工工号,员工姓名,员工的上级工号和入职时间:
    [$host] transwarp> SELECT \* FROM employee_info;
    10817   张鑫叶  94832   20140509
    13049   王明哲  94832   20140616
    58290   许玮伦  12083   20120901
    94832   戴李仁  12083   20111212
    12083   郑希    56412   20110916
    56412   吴思翼  NULL    20110916
    
    我们用这张表和自己连接来查询员工和他们上级的姓名:
    [$host] transwarp> SELECT e.employee_name, sup.employee_name FROM employee_info e LEFT OUTER JOIN employee_info sup ON (e.sup_id = sup.employee_id);
    张鑫叶  戴李仁
    王明哲  戴李仁
    许玮伦  郑希
    戴李仁  郑希
    郑希    吴思翼
    吴思翼  NULL
    
  • 左半连接和左半反连接:
    左半连接用来查看左表中符合和JOIN条件的记录。左半反连接用来查看左表不符合JOIN条件的记录。左半连接和左半反连接都只显示左表中的记录
    左半连接可以通过LEFT SEMI JOIN, WHERE... IN 和WHERE EXISTS中嵌套子查询来实现。而左半反连接可以通过在LEFT ...NOT IN/EXISTS中嵌套子查询来实现。关于WHERE... IN/EXISTS和WHERE NOT IN /EXISTS的更多说明和例子可以在子查询章节中找到。
    左半连接
    语法:LEFT SEMI JOIN
    SELECT select_expression, select_expression, ...
    FROM table_reference_1
    LEFT SEMI JOIN table_reference_2 ON (join_condition)
    
    举例
    首先,我们有两张表test1和test2。test1和test2的列名都为num和letter:
    [$host] transwarp> SELECT \* FROM test1;
    1       a
    1       b
    1       c
    2       a
    3       e
    [$host] transwarp> SELECT \* FROM test2;
    1       f
    1       g
    2       i
    
    下例我们分别用LEFT SEMI JOIN和在WHERE IN/EXISTS中嵌套子查询执行相同的任务:查看test1中的num列和test2.num匹配的所有记录。
    使用LEFT SEMI JOIN:
    [$host] transwarp> SELECT t1.num, t1.letter FROM test1 t1 LEFT SEMI JOIN test2 t2 ON t1.num = t2.num;
    1       a
    1       b
    1       c
    2       a
    
    使用在WHERE...IN中嵌套子查询
    [$host] transwarp> SELECT t1.num, t1.letter FROM test1 t1 WHERE t1.num IN (SELECT t2.num FROM test2 t2);
    1       a
    1       b
    1       c
    2       a
    
    使用在WHERE EXISTS中嵌套子查询
    [$host] transwarp> SELECT t1.num, t1.letter FROM test1 t1 WHERE EXISTS (SELECT 1 FROM test2 t2 WHERE t2.num = t1.num);
    1       a
    1       b
    1       c
    2       a
    
    举例
    Inceptor不支持LEFT ANTI SEMI JOIN这样的语法,但是左半连接的效果可以又WHERE...NOT IN和WHERE NOT EXISTS来达到。
    下例我们分别用WHERE...NOT IN,WHERE NOT EXISTS 来执行左半反连接:查看test1中num列值在test2.num中没有匹配值的记录:
    在WHERE...NOT IN中嵌套子查询
    [$host] transwarp> SELECT t1.num, t1.letter FROM test1 t1 WHERE t1.num NOT IN (SELECT t2.num FROM test2 t2);
    3       e
    
    在WHERE NOT EXISTS中嵌套子查询
    [$host] transwarp> SELECT t1.num, t1.letter FROM test1 t1 WHERE NOT EXISTS (SELECT 1 FROM test2 t2 WHERE t1.num = t2.num);
    3       e
    

    不等价连接
    到现在为止,我们只用到了等价连接(equi-join),也就是JOIN条件是一个等式。我们也可以将表不等价连接(non-equi join)。
    语法

    SELECT select_expression, select_expression, ...
    FROM table_reference1 JOIN table reference_2
    ON equi_join_condition
    WHERE non_equi_join_condition
    
    从语法中我们看到,要执行不等价连接,ON子句中的连接条件必须是等价条件,不等价条件体现在WHERE子句中的过滤条件中。不等价连接和笛卡尔积相像,很容易返回大量结果(在两表行数乘积的级别),ON子句中要求有等价条件可以限制结果的数量。如果确定不需要限制结果数量,可以在ON子句中的等价条件里放一个永远成立的等式,比如1=1。执行这样的操作必须格外小心。
    举例
    我们用一个自不等价连接作为例子。假设公司想要在员工中间组织一次下棋比赛,每对员工之间都要进行一场比赛,我们想要生成包含所有员工对的数据:

    [$host] transwarp> SELECT e1.employee_name, e2.employee_name FROM employee_info e1 JOIN employee_info e2 ON 1 = 1 WHERE e1.employee_name <> e2.employee_name;
    张鑫叶  王明哲
    张鑫叶  许玮伦
    张鑫叶  戴李仁
    张鑫叶  郑希
    张鑫叶  吴思翼
    王明哲  张鑫叶
    王明哲  许玮伦
    王明哲  戴李仁
    王明哲  郑希
    王明哲  吴思翼
    许玮伦  张鑫叶
    许玮伦  王明哲
    许玮伦  戴李仁
    许玮伦  郑希
    许玮伦  吴思翼
    戴李仁  张鑫叶
    戴李仁  王明哲
    戴李仁  许玮伦
    戴李仁  郑希
    戴李仁  吴思翼
    郑希    张鑫叶
    郑希    王明哲
    郑希    许玮伦
    郑希    戴李仁
    郑希    吴思翼
    吴思翼  张鑫叶
    吴思翼  王明哲
    吴思翼  许玮伦
    吴思翼  戴李仁
    吴思翼  郑希
    
    这里join条件是e1.name<> e2.id,因为一个人不能和自己比赛。这条指令会导致每个员工对都会重复一次,所以我们可以修改连接条件为e1.name > e2.name

    [$host] transwarp> SELECT e1.employee_name, e2.employee_name FROM employee_info e1 JOIN employee_info e2 ON 1 = 1 WHERE e1.employee_name > e2.employee_name;
    张鑫叶  吴思翼
    王明哲  张鑫叶
    王明哲  戴李仁
    王明哲  吴思翼
    许玮伦  张鑫叶
    许玮伦  王明哲
    许玮伦  戴李仁
    许玮伦  吴思翼
    戴李仁  张鑫叶
    戴李仁  吴思翼
    郑希    张鑫叶
    郑希    王明哲
    郑希    许玮伦
    郑希    戴李仁
    郑希    吴思翼
    

  • MAP JOIN
    如果两张被连接的表中有一张比较小(100MB以下),那么可以通过MAP JOIN来提高执行速度。MAP JOIN会将小表放入内存中,在map阶段直接拿另一张表的数据和内存中表数据做匹配,由于省去了shuffle,速度会比较快。
    语法
    SELECT /*+ MAPJOIN(b) */ select_expression, select_expression, ...
    FROM table_reference JOIN table_reference ON join_condition
    
    Inceptor已经有了自动MAP JOIN的功能,就是在有一张表在100MB一下时,Inceptor会自动执行MAP JOIN。所以用户可以无需特别指明使用MAP JOIN。如果在参与JOIN的表都较大时却指明使用MAP JOIN,可能会导致内存溢出。