zoukankan      html  css  js  c++  java
  • mysql中关于exists的深入讲解

    mysql中关于exists的讲解

    我认为exists语法是mysql中一个很强大的工具,可以简单地实现某些复杂的数据处理。

    下面我谈谈与exists有关的三个方面。


    all 与 any

    首先,看到了exists,难免还会想到all和any,它们比exists容易理解一些。all 和 any都能让一行数据与多行数据进行比较,这是它们的主要功能。

    create table T(X int);
    insert into T(X) values(1),(2),(3),(4);
    
    # eg.1
    select * from T where X > all( select * from T where X < 3 );	#输出3,4
    
    # eg.2
    select * from T where X > any( select * from T where X > 1 );	#输出3,4
    

    先看eg.1, 显然select * from T where X < 3结果是1,2;而all要求存在X大于集合{1,2}内的任意元素,即3,4。

    同理,对于eg.2,select * from T where X > 1结果是2,3,4;any的要求是存在X大于集合{2,3,4}内的某个元素即可,即3,4。


    划分表

    在说exists之前,再看看一个比较特别的语句,关于表(table)的“划分”用法。

    eg.1

    # fruitTable
    Id  Name  Class Count  Date
     1   苹果    水果    10     2011-7-1
     1   桔子    水果    20     2011-7-2
     1   香蕉    水果    15     2011-7-3
     2   白菜    蔬菜    12     2011-7-1
     2   青菜    蔬菜    19     2011-7-2 
    

    现在要求进行筛选,条件是Id唯一,Date选最近的一次

    这种筛选条件潜藏着对于表的划分要求。以fruitTable为例,需要划分为2个子表,Id为1的为一个子表、Id为2的为另一个子表,再从各自子表里面选出时间最大的那个元组。

    先看看下面一个错误的解法

    SELECT DISTINCT Id, Name, Class, Count, Date FROM fruitTable t1
    	WHERE (Date IN 
               (SELECT MAX(Date) FROM fruitTable t2 GROUP BY Id));
               
    # 结果
     1   桔子    水果    20     2011-7-2
     1   香蕉    水果    15     2011-7-3
     2   青菜    蔬菜    19     2011-7-2
    

    这周解法在逻辑上有漏洞。它将不同Id的最大时间混在了一起,没有真正地划分表格。

    再来看看正确的解法

    划分表格的思路是正确的,但问题是怎么划分,如果另外创建2个新的table,那这样显然太麻烦了,于是有了下面这种写法。

    SELECT DISTINCT Id, Name, Class, Count, Date FROM fruitTable t1
    	WHERE (Date = 
               (SELECT MAX(Date) FROM fruitTable t2 WHERE t2.Id=t1.Id));
    

    注意WHERE t2.Id=t1.Id 很巧妙地 对表t2 基于t2.Id=t1.Id这个标准 进行了划分。可以推导一下,比如遍历表t1,先是第1个元组: 1 苹果 水果 10 2011-7-1, 可以知道t1.Id=1, 带入第2个select: (SELECT MAX(Date) FROM fruitTable t2 WHERE t2.Id=1) , 观察这个select语句的筛选条件WHERE t2.Id=1, 发现它的范围限定在了Id为1的元组内,聚集函数MAX(Date)返回Id为1的所有元组中Date最大的值(2011-7-3)。

    因此对于表t1, 当t1.Id=1时,只有Date=2011-7-3的元组才会被选出来;而当tl.Id=2时,第2个select又变为SELECT MAX(Date) FROM fruitTable t2 WHERE t2.Id=2, 返会所有Id=2的元组中Date的最大值(2011-7-2)。

    可以发现,表t2是受t1.Id控制的,根据t1.Id的不同而被划分为不同的子表,这就是表的划分,并且不需要另外创建新的表。


    exists

    先说说exists的基本用法

    create table R(
    	X int, Y varchar(5), Z varchar(5)
    );
    
    create table S(
    	Y varchar(5), Z varchar(5), Q int
    );
    
    
    insert into R(X,Y,Z) values(
    	1,'a','A'
    ),(
    	1,'b','B'
    ),(
    	1,'a','B'
    ),(
    	1,'c','C'
    ),(
    	2,'a','B'
    ),(
    	2,'b','B'
    ),(
    	2,'c','A'
    ),(
    	3,'z','Z'
    );
    
    
    insert into S(Y,Z,Q) values(
    	'b','B',1
    ),(
    	'a','B',2
    );
    
    -----------------------------
    
    select * from R where exists( select * from S where S.Y='b' and R.Y=S.Y );
    # 结果
    '1', 'b', 'B'
    '2', 'b', 'B'
    

    对于exists可以先简单地理解为if判断。
    比如语句select * from R where exists( select * from S where S.Y='b' and R.Y=S.Y );就可以理解为 从表R中筛选出满足条件 S.Y='b' and R.Y=S.Y (select * from S where S.Y='b' and R.Y=S.Y) 的元组。

    这个性质可以看出2个特性

    • 首先exists()括号内的表不会影响最终返回的结果。比如上面的例子,返回的结果始终是关于表R的元组,和表S没有任何关系
    • 对于exists()语句,关键的是括号内的where子句。对于exists( select * from S where S.Y='b' and R.Y=S.Y ) 这种语句,可以直接当作 if( S.Y== 'b' and R.Y ==S.Y )。当然也不是说select不重要,比如exists( select 1 from S where S.Y='b' and R.Y=S.Y )是永远为真的条件。

    理清上面2点,我们就更能意识到exists非常像是一个关于条件判断的语句。

    下面例子类似

    # 选了张三老师课的学生
    select distinct sc.sid from sc 
    	where exists (
    		select * from course c,teacher t 
    			where sc.cid = c.cid and c.tid = t.tid and t.tname = "张三");
    

    但仅仅只有exists还不够,因为很多其它语句也能实现这个功能,真正强大的是not exists。
    对于存在exists只是一个元组与某个局部作比较,因为只要存在即可。而对于不存在,却是一个元组和整体做比较,因为要确定不存在,就必须遍历所有。
    在这方面来说,not exists比exists更强大。

    找最值

    SELECT DISTINCT Id, Name, Class, Count, Date FROM fruitTable t1
    	WHERE (Date = 
               (SELECT MAX(Date) FROM fruitTable t2 WHERE t2.Id=t1.Id));
    #用not exists
    SELECT DISTINCT Id, Name, Class, Count, Date FROM fruitTable t1
    	WHERE NOT EXISTS(
               SELECT * FROM fruitTable t2 WHERE t2.Id=t1.Id and t2.Date > t1.Date );
    

    这里not exists同样可以看作not if,关键是明白哪部分条件被否定(not)。根据之前的理论,这里条件明显是t2.Id=t1.Id and t2.Date > t1.Date , 而t2.Id=t1.Id不能作为否定的对象,因为这是必然存在的(自己想想,t1和t2内容一样),用来限定表t2的范围(即之前说的划分子表),再看t2.Date > t1.Date,这才是否定的部分,即对于t2中Id为t1.Id的所有元组的Date都不大于t1.Date,而此时的t1.Date也即最大值。

    嵌套not exists

    还有更复杂的情况,多层not exists嵌套使用。比如实现关系代数里的除法运算。

    # 表R,S的定义上面已经给出  下面计算 R除以S
    select distinct R1.x from R R1 where not exists ( 
    	select * from S where not exists (
    		select * from R R2 where R1.X=R2.X and R2.Y=S.Y and R2.Z=S.Z ));
    

    一个not exists只表示不存在,需要遍历所有元组才能做出判断
    2个not exists嵌套,表示每一个都存在,同样需要遍历所有元组才能确定,同时还是“肯定”

    这里有3个select,2个not exists。
    最里面的not exists是用来否定R2.Y=S.Y and R2.Z=S.Z (因为R1.X=R2.X一定成立,这个是用来划分子表的), 最外层的not exists就用来表示不存在这个意思,你会发现最后这个句子表达的意思就是关系代数里面除法的定义。


    使用联合来解决exists问题

    因为MySQL每次的操作都是基于行的,当涉及到表与表之间类似集合的关系时,处理起来比较麻烦。比如下面这个问题。

    insert into R(X,Y) values(
    	1,'a'
    ),(
    	1,'b'
    ),(
    	1,'B'
    ),(
    	1,'C'
    ),(
    	2,'A'
    ),(
    	2,'c'
    ),(
    	3,'z'
    );
    
    
    insert into S(Y,Q) values(
    	'b',1
    ),(
    	'B',2
    );
    
    #问题:表R内,对于X值相同的行组成一组(或叫集合)。在这样的每组元素中,要求R(Y)中不能出现与S(Y)相同的值,求这样的组的X值有哪些。
    #这种问题是关于集合之间的关系,不同于 一行与一个集合之间的关系。
    #下面运用之前讲的not exists来求解
    select distinct X from R R1 where not exists (
    	select * from R R2 where R2.X=R1.X and R2.Y in (select distinct Y from S));
    

    下面来介绍另外一种方法,联合。

    仔细观察可以发现R和S之间是有关系的,因此可以将它们进行自然连接,这样就直接得到了所有R(Y)=S(Y)的值。

    select distinct X from R where X not in (select distinct X from R,S where R.Y=S.Y);
    

    但是对于代码可读性来说,in和exists比派生表联合优雅

  • 相关阅读:
    隧道适配器,本地连接过多的解决办法
    C# 遍历HashTable
    sql2005 数据库转为sql 2000数据库的步骤
    自动扫描IP代理地址和自动切换IP的软件
    JS实现网页图片延迟加载[随滚动条渐显]
    批量修改hosts
    C#.NET获取当前月份最大天数
    如何让js调用不影响页面的加载速度?
    在sql中如何替换去掉回车符?
    Linq(01)对Linq的疑问及学习路径
  • 原文地址:https://www.cnblogs.com/friedCoder/p/12678145.html
Copyright © 2011-2022 走看看