Home >Database >Mysql Tutorial >详细讲述MySQL中的子查询操作_MySQL

详细讲述MySQL中的子查询操作_MySQL

WBOY
WBOYOriginal
2016-06-01 13:00:301039browse

继续做以下的前期准备工作:

    新建一个测试数据库TestDB;

   

  create database TestDB;

    创建测试表table1和table2;

   CREATE TABLE table1
   (
     customer_id VARCHAR(10) NOT NULL,
     city VARCHAR(10) NOT NULL,
     PRIMARY KEY(customer_id)
   )ENGINE=INNODB DEFAULT CHARSET=UTF8;

   CREATE TABLE table2
   (
     order_id INT NOT NULL auto_increment,
     customer_id VARCHAR(10),
     PRIMARY KEY(order_id)
   )ENGINE=INNODB DEFAULT CHARSET=UTF8;

    插入测试数据;

   INSERT INTO table1(customer_id,city) VALUES('163','hangzhou');
   INSERT INTO table1(customer_id,city) VALUES('9you','shanghai');
   INSERT INTO table1(customer_id,city) VALUES('tx','hangzhou');
   INSERT INTO table1(customer_id,city) VALUES('baidu','hangzhou');

   INSERT INTO table2(customer_id) VALUES('163');
   INSERT INTO table2(customer_id) VALUES('163');
   INSERT INTO table2(customer_id) VALUES('9you');
   INSERT INTO table2(customer_id) VALUES('9you');
   INSERT INTO table2(customer_id) VALUES('9you');
   INSERT INTO table2(customer_id) VALUES('tx');

    准备工作做完以后,table1和table2看起来应该像下面这样:

   mysql> select * from table1;
   +-------------+----------+
   | customer_id | city   |
   +-------------+----------+
   | 163     | hangzhou |
   | 9you    | shanghai |
   | baidu    | hangzhou |
   | tx     | hangzhou |
   +-------------+----------+
   4 rows in set (0.00 sec)

   mysql> select * from table2;
   +----------+-------------+
   | order_id | customer_id |
   +----------+-------------+
   |    1 | 163     |
   |    2 | 163     |
   |    3 | 9you    |
   |    4 | 9you    |
   |    5 | 9you    |
   |    6 | tx     |
   +----------+-------------+
   7 rows in set (0.00 sec)

准备工作做的差不多了,开始今天的总结吧。
一个问题

现在需要查询所有杭州用户的所有订单号,这个SQL语句怎么写?首先,你可以这么写:

select table2.customer_id, table2.order_id from table2 join table1 on table1.customer_id=table2.customer_id where table1.city='hangzhou';

能实现我们需要的结果。但是,我们也可以这么写:

select customer_id, order_id from table2 where customer_id in (select customer_id from table1 where city='hangzhou');

呃?在()括号中的的select语句是什么?问题来了,这到底是什么语法,怎么也可以完成任务,那么这篇博文就围绕着这个问题开始展开。
啥是子查询

简单的说,子查询就是:

201541491747213.png (875×250)

如上图所示,子查询,有叫内部查询,相对于内部查询,包含内部查询的就称为外部查询。子查询可以包含普通select可以包括的任何子句,比如:distinct、group by、order by、limit、join和union等;但是对应的外部查询必须是以下语句之一:select、insert、update、delete、set或者do。

201541491840216.png (485×217)

我们可以在where和having子句中使用子查询,将子查询得到的结果作为判断的条件。
使用比较进行子查询

一个子查询会返回一个标量(就一个值)、一个行、一个列或一个表,这些子查询称之为标量、行、列和表子查询。

当一个子查询返回一个标量时,我们就可以在where或者having子句中使用比较符与子查询得到的结果进行直接判断。比如,我现在要得到比用户tx订单数多的customer_id、city和订单数,这个sql语句怎么写。

先来说说,我写sql的一般步骤:

  •     读懂需求;
  •     得到比用户tx订单数多的customer_id、city和对应的订单数。
  •     看看最终需要得到哪些字段信息;
  •     最终需要得到customer_id、city和订单数信息。
  •     分析这些字段信息涉及到哪几个表;
  •     涉及到表table1和表table2。
  •     这几个表是如何关联的;
  •     表table1和表table2的关联就在于customer_id字段。
  •     分解需求,得到一个个小的需求;
  •         需要得到tx用户的订单数;
  •         需要得到其它用户的订单数;
  •         比较订单数。
  •     确认每一个小需求的过滤条件;
  •     得到每个小需求的结果,进行组装,得到最终结果。

最终,我会写出一下的sql语句:

select table1.customer_id,city,count(order_id) 
from table1 join table2 
on table1.customer_id=table2.customer_id 
where table1.customer_id <> 'tx'
group by customer_id 
having count(order_id) > 
            (select count(order_id) 
             from table2 
             where customer_id='tx' 
             group by customer_id);

上面的查询中使用了子查询,外部查询与子查询得到的结果进行了比较判断。如果子查询返回一个标量值(就一个值),那么外部查询就可以使用:=、>、=、符号进行比较判断;如果子查询返回的不是一个标量值,而外部查询使用了比较符和子查询的结果进行了比较,那么就会抛出异常。
使用ANY进行子查询

上面使用比较符进行子查询,规定了子查询只能返回一个标量值;但是,如果子查询返回的是一个集合,怎么办?

没问题,我们可以使用:any、in、some或者all来和子查询的返回结果进行条件判断。这里先总结使用any进行子查询。

any关键词必须与上面总结的比较操作符一起使用;any关键词的意思是“对于子查询返回的列中的任何一个数值,如果比较结果为TRUE,就返回TRUE”。

好比“10 >any(11, 20, 2, 30)”,由于10>2,所以,该该判断会返回TRUE;只要10与集合中的任意一个进行比较,得到TRUE时,就会返回TRUE。

比如,我现在要查询比customer_id为tx或者9you的订单数量多的用户的id、城市和订单数量。

我可以得到以下的sql语句来完成需求。

select table1.customer_id,city,count(order_id)
from table1 join table2
on table1.customer_id=table2.customer_id
where table1.customer_id<>'tx' and table1.customer_id<>'9you'
group by customer_id
having count(order_id) >
any (
select count(order_id)
from table2
where customer_id='tx' or customer_id='9you'
group by customer_id);

any的意思比较好明白,直译就是任意一个,只要条件满足任意的一个,就返回TRUE。
使用IN进行子查询

使用in进行子查询,这个我们在日常写sql的时候是经常遇到的。in的意思就是指定的一个值是否在这个集合中,如何在就返回TRUE;否则就返回FALSE了。

in是“=any”的别名,在使用“=any”的地方,我们都可以使用“in”来进行替换。这里就不举例了,尽情的发挥想象,自行发挥吧。

有了in,肯定就有了not in;not in并不是和any是同样的意思,not in和all是一个意思,关于all,下面马上就要总结了。
使用SOME进行子查询

some是any的别名,用的比较少。只需要理解any的意思就好了,这里就不做过多的总结。具体请参考上面的any部分的总结。
使用ALL进行子查询

all必须与比较操作符一起使用。all的意思是“对于子查询返回的列中的所有值,如果比较结果为TRUE,则返回TRUE”。

好比“10 >all(2, 4, 5, 1)”,由于10大于集合中的所有值,所以这条判断就返回TRUE;而如果为“10 >all(20, 3, 2, 1, 4)”,这样的话,由于10小于20,所以该判断就会返回FALSE。

all的同义词是not in,表示不等于集合中的所有值,这个很容易和any搞混,平时多留点心就好了。
标量子查询

根据子查询返回值的数量,将子查询可以分为标量子查询和多值子查询。在使用比较符进行子查询时,就要求必须是标量子查询;如果是多值子查询时,使用比较符,就会抛出异常。
多值子查询

与标量子查询对应的就是多值子查询了,多值子查询会返回一列、一行或者一个表,它们组成一个集合。我们一般使用的any、in、all和some等词,将外部查询与子查询的结果进行判断。如果将any、in、all和some等词与标量子查询,就会得到空的结果。
独立子查询

独立子查询是不依赖外部查询而运行的子查询。什么叫依赖外部查询?先看下面两个sql语句。

sql语句1:获得所有hangzhou顾客的订单号。

select order_id 
from table2 
where customer_id in 
          (select customer_id 
          from table1 
          where city='hangzhou');

sql语句2:获得城市为hangzhou,并且存在订单的用户。

select * 
from table1 
where city='hangzhou' and exists
                (select * 
                from table2 
                where table1.customer_id=table2.customer_id);

上面的两条sql语句,虽然例子举的有点不是很恰当,但是足以说明这里的问题了。

对于sql语句1,我们将子查询单独复制出来,也是可以单独执行的,就是子查询与外部查询没有任何关系。

对于sql语句2,我们将子查询单独复制出来,就无法单独执行了,由于sql语句2的子查询依赖外部查询的某些字段,这就导致子查询就依赖外部查询,就产生了相关性。

对于子查询,很多时候都会考虑到效率的问题。当我们执行一个select语句时,可以加上explain关键字,用来查看查询类型,查询时使用的索引以及其它等等信息。比如这么用:

explain select order_id 
  from table2 
  where customer_id in 
            (select customer_id 
            from table1 
            where city='hangzhou');

使用独立子查询,如果子查询部分对集合的最大遍历次数为n,外部查询的最大遍历次数为m时,我们可以记为:O(m+n)。而如果使用相关子查询,它的遍历次数可能会达到O(m+m*n)。可以看到,效率就会成倍的下降;所以,大伙在使用子查询时,一定要考虑到子查询的相关性。

关于explain的更多解释,请参考这里。
相关子查询

相关子查询是指引用了外部查询列的子查询,即子查询会对外部查询的每行进行一次计算。但是在MySQL的内部,会进行动态优化,会随着情况的不同会有所不同。使用相关子查询是最容易出现性能的地方。而关于sql语句的优化,这又是一个非常大的话题了,只能通过实际的经验积累,才能更好的去理解如何进行优化。

关于sql的性能,我这里不能说什么,如果只是阅读其它人的文章来考虑性能问题,其实是没有任何感觉的,我们需要实际的项目中才能更好的理解。
EXISTS谓词

EXISTS是一个非常牛叉的谓词,它允许数据库高效地检查指定查询是否产生某些行。根据子查询是否返回行,该谓词返回TRUE或FALSE。与其它谓词和逻辑表达式不同的是,无论输入子查询是否返回行,EXISTS都不会返回UNKNOWN,对于EXISTS来说,UNKNOWN就是FALSE。还是上面的语句,获得城市为hangzhou,并且存在订单的用户。

select * 
from table1 
where city='hangzhou' and exists
                (select * 
                from table2 
                where table1.customer_id=table2.customer_id);

使用explain查看一下,就会得到以下内容:

201541491929351.png (1082×140)

我们可以很明显的看到,存在一个相关的子查询(DEPENDENT SUBQUERY)。可以看到EXISTS和IN是非常相似的,那么它们之间的区别是什么呢?

关于IN和EXISTS的主要区别在于三值逻辑的判断上。EXISTS总是返回TRUE或FALSE,而对于IN,除了TRUE、FALSE值外,还有可能对NULL值返回UNKNOWN。但是在过滤器中,UNKNOWN的处理方式与FALSE相同,因此使用IN与使用EXISTS一样,SQL优化器会选择相同的执行计划。

说到了IN和EXISTS几乎是一样的,但是,就不得不说到NOT IN和NOT EXISTS,对于输入列表中包含NULL值时,NOT EXISTS和NOT IN之间的差异就表现的非常大了。输入列表包含NULL值时,IN总是返回TRUE和UNKNOWN,因此NOT IN就会得到NOT TRUE和NOT UNKNOWN,即FALSE和UNKNOWN。

mysql> select 'c' NOT IN ('a', 'b', NULL)\G;

执行一下上述代码,看看结果。你就会感到惊讶。
派生表

上面也说到了,在子查询返回的值中,也可能返回一个表,如果将子查询返回的虚拟表再次作为FROM子句的输入时,这就子查询的虚拟表就成为了一个派生表。语法结构如下:

FROM (subquery expression) AS derived_table_alias

由于派生表是完全的虚拟表,并没有也不可能被物理地具体化。
总结

总算总结的差不多了,当然了子查询的东西还是有很多的,不可能一篇文章就能总结的完的,这里只是把一些基本的概念,常用的知识点进行了总结,关于将子查询使用到update、delete和insert语句中的用法,我这里并没有涉及,大体上都是大同小异的。知识这个东西,展开了,就没有头了,还是需要适可而止,适当的进行深度的挖掘,但是深度最好不要超过2,关于这个2如何定义,自行把握。好了,这篇文章就到此为止了,我们下一篇见。

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn