SQL复杂查询是指子查询。为什么子查询称为复杂查询?因为子查询相当于一个查询嵌套查询,因为嵌套的复杂度几乎可以被无限放大(infinitenesting),所以称为复杂查询。下面是一个最简单的子查询的例子:SELECTpvFROM(SELECTpvFROMtest)上面的例子等同于SELECTpvFROMtest,但是因为表的位置被新的查询替换了,变成了一个复杂的查询!因此,复杂查询不一定复杂,甚至可以写出等同于普通查询的复杂查询。这种无意义的行为应该避免。让我们也借此机会看看为什么子查询会做它们所做的事情。了解查询的本质当我们查找一个表时,数据库认为我们在寻找什么?这一点很重要,因为下面两条语句是合法的:SELECTpvFROMtestSELECTpvFROM(SELECTpvFROMtest)为什么数据库可以把子查询当作表呢?为了统一理解这些概念,我们有必要对查询内容有一个抽象的认识:任何一个查询位置都是一条或多条记录。比如test表明明有多条记录(当然只有一行是一条记录),SELECTpvFROMtest也有多条记录。但是,由于FROM可以查询任意数量的记录,因此两种语法都受支持。不仅FROM可以跟随单条或多条记录,就连SELECT、GROUPBY、WHERE、HAVING也可以跟随多条记录,后面会讲到。说到这里,就很容易理解子查询的变体了。比如我们可以在子查询中使用WHERE或者GROUPBY,因为无论如何,只要查询结果是多条记录:SELECTsum(people)asallPeople,sum(gdp),cityFROM(SELECTpeople,gdp,cityFROMtestGROUPBYcityHAVINGsum(gdp)>10000)有一些商业影响。子查询是从里到外执行的,先看内部逻辑:按城市分组,过滤掉所有GDP总和超过1万的地区的人口明细。外层查询加总人口,这样就可以比较GDP在1万以上的各地区的总人口和GDP总和,方便这些重点城市的比较。但是,这个例子看起来还是不自然,因为我们不需要写一个复杂的查询。事实上,一个简单的查询是等价的:SELECTsum(people)asallPeople,sum(gdp),cityFROMtestGROUPBYcityHAVINGsum(gdp)>10000那么何必呢?因为复杂查询的真正用法不在这里。因为子查询的存在,我们可以用类似抽取变量的方式来抽取子查询。提取的抽象是视图:CREATEVIEWmy_table(people,gdp,city)ASSELECTsum(people)asallPeople,sum(gdp),cityFROMtestGROUPBYcityHAVINGsum(gdp)>10000SELECTsum(people)asallPeople,sum(gdp),cityFROMmy_table这个视图的好处是这个视图可以被多个SQL语句复用,这样不仅提高了可维护性嘛,只需要查询一次执行。需要注意的是,SELECT可以使用任何视图,但是当INSERT、DELETE、UPDATE用于视图时,视图需要满足以下条件:DISTINCT不用于去除重复。从单表。不使用GROUPBY和HAVING。因为以上几种方式都会导致视图变成聚合数据,不方便做查询以外的操作。还有一个知识点是物化视图,即用MATERIALIZED来描述视图:CREATEMATERIALIZEDVIEWmy_table(people,gdp,city)AS...这种视图会被放到磁盘上,为什么要支持这个特性?由于普通视图是临时表,不能使用索引等优化方法,查询性能低,所以物化视图是一种常见的性能优化方法。说到性能优化的方法,有一些比较常见的概念,就是把读的复杂度分配给写,比如提前聚合新表或者将CASE语句固化成字段等,这里不再展开.上面提到的标量子查询,WHERE也可以跟在子查询之后,例如:SELECTcityFROMtestWHEREgdp>(SELECTavg(gdp)fromtest)这样就可以查询gdp大于平均值的城市。那你为什么不能这样写呢?SELECTcityFROMtestWHEREgdp>avg(gdp)--报错,WHERE不能使用聚合函数。看起来不错,但实际上正如我们在第一篇文章中介绍的,WHERE不能和聚合查询结合使用,因为这样会聚合整个父查询。那为什么子查询可以呢?因为子查询聚合子查询,父查询不聚合,所以这符合我们的意图。所以上面这个例子不合适的地方是在当前查询中直接使用avg(gdp)会导致聚合,而我们不想对当前查询进行聚合,而是想通过聚合得到平均GDP,所以我们需要使用子查询!回过头来看,为什么这一段叫做标量子查询呢?标量是单个值,因为avg(gdp)仅聚合一个值,因此WHERE可以将其用作单个值。反之,如果子查询不使用聚合函数,不使用GROUPBY分组,那么就不能使用WHERE>的语法,而可以使用WHEREIN,这就涉及到单条记录和多条记录的思考。让我们继续下一节。正如单条记录和多条记录的标量子查询介绍中提到的,WHERE>的值必须是单值。但其实WHERE后面也可以是返回多条记录的子查询结果,只要使用合理的条件语句,比如IN:SELECTareaFROMtestWHEREgdpIN(SELECTmax(gdp)fromtestGROUPBYcity)中上面的例子,子查询按照Citiesaregrouped,找到每组GDP最大的记录,所以如果数据粒度是region,那么我们就找到每个城市GDP最大的记录,然后父查询找到通过WHEREIN匹配gdp的复数结果,所以最后列出每个城市gdp最大的区域。但实际上,WHERE>语句和复数查询结果并不会报错,只是没有意义,所以我们需要了解查询结果是单数还是复数,判断WHERE时选择合适的条件。适用于复数查询结果的WHERE语法有:WHEREIN、WHERESOME、WHEREANY。关联子查询所谓关联子查询,就是父子查询之间存在关联关系。在这种情况下,一定不要先单独执行子查询。毕竟是和父查询相关的,所以相关子查询先执行外层查询,再执行内层查询。的。需要注意的是,对于父查询的每一行,子查询都会执行一次,所以性能不高(当然SQL会缓存相同参数的子查询的结果)。那么这个联系是什么?关联的是父查询的每一行在子查询上执行的条件。这可能有点令人费解,例如:SELECT*FROMtestwheregdp>(selectavg(gdp)fromtestgroupbycity)对于这个例子,你想知道gdp是否大于按城市分组的平均gdp,比如北京地区按照北京比较,上海地区按照上海比较。但是很可惜这样是不行的,因为父子查询是不相关的,SQL不知道按照同城比较,所以只要加一个WHERE条件,就变成了相关子查询:SELECT*FROMtestast1wheregdp>(selectavg(gdp)fromtestast2wheret1.city=t2.citygroupbycity)是每次判断WHEREgdp>条件时重新计算子查询结果,限制平均值到同一个城市,满足要求。总结学会灵活使用父子查询,你就会掌握复杂的查询。SQL的第一公民是集合,所以所谓的父子查询就是父子集合的灵活组合。这些集合几乎可以出现在任何位置。根据集合的个数、聚合、关联条件,导出标量查询和关联子查询。更深入的理解需要大量的实战案例,但一切都是一样的。掌握了复杂的查询之后,你就可以理解大部分的SQL案例。讨论地址为:Jingdu《SQL 复杂查询》·Issue#403·ascoders/weekly想参与讨论的请点这里,每周都有新话题,周末或周一发布。前端精读——帮你过滤靠谱的内容。关注前端精读微信公众号
