早上一起床就刷微信(真想拍死自己),看到一篇很有意思的推送,名为《图解SQL join语句》,讲的是用Venn图图解SQL join语句。看的过程中有点疑惑,干脆就用MySQL实践一下,顺带复习下MySQL嘛。这一实践下来,还发现了另外的有趣的问题,比如MySQL中并没有full outer join, 那要怎么在MySQL中模拟full outer join的问题。这么一来二去,涉及的内容就更加有意思了,不记下来有点可惜,另外国庆实在是一个放松的好时机啊,又想偷懒了,那就写博客来防止自己偷懒吧!
由于写出排版美观的博客比较费时间,所以这一系列的博客可能比较潦草,希望各位看官海涵。
为了防止遗漏,先直接上各种连接对应的Venn图,图片引自推送,原作者已不可考。另外,自然连接是无法用Venn图表示的。
假设有两个表,A表,B表,表结构与数据如下:
复习MySQL:
进入MySQL的命令: mysql -u root -p 接着输入密码 查看数据库: show databases; 注意复数,有分号 查看数据库表: show tables; 注意复数,有分号 创建表: create table A(id int, name varchar(32)); 插入数据: insert into A values(1, 'Pirate'); 注意字符串加单引号,另外如果只是插入部分字段,只需在表名后打括号,指定字段名,字段名无需加单引号
内连接 inner join:只生成匹配连接条件的记录,最常见没啥好讲。对应Venn图的求交。
全外连接 full outer join: 会生产表A与表B的记录全集,包含两边都匹配的记录(inner join的结果集),另外如果有一边没有匹配,缺失的一边为null。然而,请注意,全外连接与产生笛卡尔积结果集的自然连接是不同的!全外连接可以理解为left join与right join的并集。对应Venn图求并。
由于MySQL中没有提供full outer join关键字,我们需要用left join 与right join 再结合 union来模拟full outer join,用union当然是因为union会去重。
注意一定是A left join B union A right join B, 这样union 的结果才会是真正的模拟到full outer join。(而不是A left join B union B left join A,即两次查询表的顺序要相同,因为union在去重的时候,只是单纯的看每一行是否完全相同, 1 Pirate 2 Pirate 与 2 Pirate 1Pirate会被视为不同的记录(废话),尽管我们人脑一看就知道这是一条重复的记录)
左连接 left join:生成左表的全部记录,对于右表中没有匹配的记录,用null补。对应Venn图就是左集合。
左连接 right join:生成右表的全部记录,对于左表中没有匹配的记录,用null补。对应Venn图就是右集合。
比较有趣并引起我的疑惑的是一开始给出的图中,关于下面这张图的SQL的写法,让我发现了自己一直以来在SQL语句执行顺序中的一个误区。
推送的说法是“为了生成只在表A里而不在表B中的记录,我们同样使用left join, 再用where B.id is null 来排除A与B的交集 ”。一直以来我以为where B.id is null是作用在from B的集上的,事实证明我是错的。 在join后面的where是作用在join on筛选后的结果集上的。
这里附上SQL语句的执行顺序,明确SQL语句的执行顺序很重要。
(8)SELECT (9)DISTINCT (11)<Top Num> <select list> (1)FROM [left_table] (3)<join_type> JOIN <right_table> (2)ON <join_condition> (4)WHERE <where_condition> (5)GROUP BY <group_by_list> (6)WITH <CUBE | RollUP> (7)HAVING <having_condition> (10)ORDER BY <order_by_list> 逻辑查询处理阶段简介 FROM:对FROM子句中的前两个表执行笛卡尔积(Cartesian product)(交叉联接),生成虚拟表VT1 ON:对VT1应用ON筛选器。只有那些使<join_condition>为真的行才被插入VT2。 OUTER(JOIN):如 果指定了OUTER JOIN(相对于CROSS JOIN 或(INNER JOIN),保留表(preserved table:左外部联接把左表标记为保留表,右外部联接把右表标记为保留表,完全外部联接把两个表都标记为保留表)中未找到匹配的行将作为外部行添加到 VT2,生成VT3.如果FROM子句包含两个以上的表,则对上一个联接生成的结果表和下一个表重复执行步骤1到步骤3,直到处理完所有的表为止。 WHERE:对VT3应用WHERE筛选器。只有使<where_condition>为true的行才被插入VT4. GROUP BY:按GROUP BY子句中的列列表对VT4中的行分组,生成VT5. CUBE|ROLLUP:把超组(Suppergroups)插入VT5,生成VT6. HAVING:对VT6应用HAVING筛选器。只有使<having_condition>为true的组才会被插入VT7. SELECT:处理SELECT列表,产生VT8. DISTINCT:将重复的行从VT8中移除,产生VT9. ORDER BY:将VT9中的行按ORDER BY 子句中的列列表排序,生成游标(VC10). TOP:从VC10的开始处选择指定数量或比例的行,生成表VT11,并返回调用者。