MySQL 在复杂查询中,用表连接代替子查询。
在 MySQL 中,经常有一些复杂操作需要用到子查询来实现,有时甚至需要嵌套 5 - 6 个子查询,并涉及多个表。实际上,大部分这样的子查询操作可以用表连接代替,实现更精简更高效的语句。
多表联合查询通过建立临时表,减少查询数据的次数,同时可以利用索引提高查询效率,因此多表联合查询比子查询效率更高。
一,子查询
在查询的语句的条件中,可能要用到其它表的数据。比如 商品表的查询,它要查看 “图书类” 的商品都有哪些,这时候可能不知道 “图书类” 对应的类别 cid,而商品表中的商品只有对应的类别 id。所以先需要从 类别表 中获取图书类对应的cid,然后用获取到的 cid 来查询商品表中哪些 “图书类” 的表。
1.1 分步查询
这时候如果按照分步查询,那就是先从分类表中获取 “图书类对应的 cid” 如
select cid from category where cname = '图书类';
然后得到图书类对应的 cid 是 3。
接下来再去查询是图书类的所有商品,如
select gid,gname,gprice from goods where cid = 3;
这是分步查询的步骤,因为只有两步看起来很简单,但是如果涉及到很多很多表,那样效率会很低。
1.2 子查询替代分步查询
这时候我们可以用子查询把两条语句合成为一句:
select gid,gname,gprice from goods where cid = (select cid from category where cname = '图书类');
我们可以看到,这里先会执行子语句中的获取cid, 然后查询外面语句对应 cid 的商品id,商品名称,商品价格。
二,表连接
像上面这个例子,可以通过表连接来实现。
也就是先将两个表连接成一张临时的表,然后再在临时的表中根据条件查询。
select gid,gname,gprice from goods g join category c on g.cid=c.cid where c.cname='图书';
在这里可以用更简单的方式来实现:
select gid,gname,gprice from goods g join category c using(cid) where c.cname='图书'
2.2 表连接代替子查询
连接表分为多种,交叉连接,内连接,自连接,外连接。
我们这里最常用的就是内连接和自连接,常用的两种方式有 等值连接和自然连接。
等值连接:
如:
... join category c on g.cid=c.cid;
我们可以看到这里的 g.cid 和 c.cid 的名相同。
这样也属于等值连接。
... join category c using(cid)
但是当我们要连接的字段名不同时,就只能用第一种等值连接。
... join category c on g.cid=c.categoryid;
自然连接:
自然连接会匹配相同的字段名来连接。
... natural join category c;
由于子查询也是为了将多个表相互符合条件的值拿来用,其实我们可以直接把表根据条件连起来,然后再操作,。
三,子查询和表连接的替换例子
3.1 普通的子查询替换
- users 表中包含用户名称,用户邮箱,用户所在城市。我们需要查询和 “Connor” 在同一城市的用户的信息,并且不包含 “Connor” 这个用户。
子查询:
select uname,uemail,ucity from users where ucity = (select ucity from users where uname = 'Connor') and uname !='Connor'
表连接(自连接)
select uname,uemail,ucity from users u1 join users u2 using(ucity) where u2.uname = 'Connor' and u1.uname!='Connor'
这个例子比较简单,可能看不出表连接的语义优势,让我们继续看。
- goods 表中包含商品id,商品类别id,商品销量,商品价格,商库存。另一个表 category 包含 商品类别id 以及对应的 类别名。 现在我们要查询类别名为 “乐器” 类的商品id,销售量,库存,按照销售额降序输出:
子查询:
select gid,gprice*gsale_qty as 销售额,gquantity from goods where cid = (select cid from category where cname = '乐器') order by 销售额 desc;
表连接(内连接)
select gid,gprice*gsale_qty as 销售额,gquantity from goods join category c using(cid) where c.cname='乐器' order by 销售额 desc;
这个例子我们可以看到我们没用到子查询,仅仅将两个表连起来,然后按照条件筛选即可。
- 第三个例子涉及到 4 个表,我们要查询购买过 “我的世界” 商品的用户信息,列出用户名、性别、出生日期。
三个表分别是:
users:
字段 | 内容 |
---|---|
uname | 用户名 |
ugender | 性别 |
ubirthday | 用户生日 |
uid | 用户id |
orders:
字段 | 内容 |
---|---|
oid | 订单id |
uid | 商品id |
orderitems :
字段 | 内容 |
---|---|
oid | 订单id |
gid | 商品id |
goods :
字段 | 内容 |
---|---|
gid | 商品id |
gname | 商品名 |
分析:
首先观察三个表里相同的字段,有 oid, uid 和 gid。
再看要求 “列出用户名、性别、出生日期” 可以确定它最终一定是要从 users 表中获取这几个字段,先写出主干部分。
select uname,ugender,ubirthday from users ....
再来看 “查询购买过 “我的世界” 商品的用户信息” 这里的 “购买过” 我们知道这里要从 orderitems 里查购买记录。同时 ““我的世界” 商品的...” 商品名只在 goods 里 。所以就是我们要从 goods 里拿到对应的 gid ,然后再从 orderitems 里通过 oid 再在 oders 里找到对应的uid。
子查询:
select uname,ugender,ubirthday from users where uid = (select uid from orders where gid = (select gid from orderitems where gid = (select gid from goods where gname like '%我的世界%')))
表连接(内连接):
select uname,ugender,ubirthday from users join orders o using(uid) join ordersitem os on o.oid = os.oid join goods g on g.gid = os.gid where g.gname like "%我的世界%;"
这里我们对比下两种方式:
- 可读性对比:
子查询 | 表连接 |
---|---|
嵌套层过多,语句较长,不利于维护 | 语句结构清晰,容易理解和维护 |
- 性能对比:
子查询 | 表连接 |
---|---|
需要多个嵌套查询,性能上低于表连接,尤其是大数据量大情况下 | 数据库系统会优化连接操作,执行效率更高。 |
3.2 替换规律
我们可以看到,
from a where xxx = (select xxx from b) ... ;
通常可以替换为
... join b b using (xxx) ... ;
3.3 涉及聚合函数的子查询替换
In 子查询,Any 子查询,ALL 子查询,通常也可以通过 Join 替换为表连接。