外部联接:除了包含内联接的两个逻辑处理阶段,还包含一个"添加外部行"的第三个阶段。外联接包含LEFT OUTER JOIN、RIGHT OUTER JOIN和FULL OUTER JOIN三种类型,分别表示左侧表为保留表、右侧表为保留表和两侧表均为保留表。这儿的保留表也就是我们常说的基准表,即其中的每一条记录实际上都会在最终的结果中显示出来,之后会通过一个内部行、外部行的概念对其进行阐述。在逻辑处理的第二阶段,也就是筛选阶段,将筛选出符合ON谓词的行,称之为内部行(由内部联接返回)。在以后的第三阶段,将识别出保留表中基于ON谓词未能与另一张表匹配的行,称之为外部行,此阶段会将这些外部行添加到之前的结果集中,在这些外部行中,其非保留表字段将使用NULL作为占位符。
接下来介绍外联接中容易忽视的知识难点,其与之前介绍的查询顺序密切相关,就是在查询中ON关键字和WHERE关键字的区别的问题,通过之前的介绍可知,ON关键字属于FROM字句,其作用时间在WHERE字句之前,也就是说WHERE操作是基于之前结果的,理清逻辑就好理解了。之前提到外联接查询的结果包含内部行和外部行,如果我们想进一步将外部行或内部行剔除,那么就可以使用WHERE进行筛选,需要注意非保留表中的属性一定要选择非NULL属性,这时的查询真正对应到NULL占位符(NULL有两种含义,一种是数据不存在,另一种是联接的占位符),如下所示。
只获取外部行,即订单不存在的用户记录:SELECT c.custid, c.companyname FROM sale.customer AS c LEFT OUTER JOIN sale.order AS o on c.custid = o.custid WHERE o.orderid IS NULL
最后,补充两个使用外联接容易出错的场景。第一个是在一个查询中同时包含内联接和外联接的情况,由于表运算符的处理是有逻辑顺序的(其他为同时操作,之前有介绍),因而不同的联接顺序可能造成不同的结果集,比如在使用LEFT JOIN之后使用INNER JOIN很有可能就会把添加上的外部行再次除去掉,一般推荐确定好保留表的先后顺序,均使用LEFT JOIN为宜。另外一个问题是在有外联接的情况下使用COUNT聚合操作符,会将外部行业作为计数目标,然而有时这是不合理的,此时需要使用指定的非空列,COUNT(col)。
SQL支持在查询内编写查询,外部查询返回结果集,内部查询的结果集被外部查询使用,称之为子查询。如果子查询不依赖其所属外部查询,称之为无关子查询,反之为相关子查询。子查询的返回值可以是一个标量、多个值和一张表。
无关子查询
标量子查询 获取当前最大订单的相关信息: SELECT * FROM sale.[order] WHERE orderid = (SELECT MAX(o.orderid) FROM sale.[order] AS o)
多值子查询 返回中国客户的订单:SELECT * FROM sales.[order] WHERE custid in (SELECT C.custid FROM sales.customer AS c WHERE c.country = N'China')
无关子查询最为简单常见,在之前的IN谓词查询中,可能会想到不是给custid加上DISCTINCT会更高效,实际上查询分析器会默认考虑删除重复记录,此外多值查询还有ALL、ANY和SOME关键字。
相关子查询
相关子查询引用的列位于外部查询中,该子查询依赖于外部查询,自身无法单独调用。
获取每个用户返回最大订单ID的订单:SELECT * FROM sales.[order] AS o1 WHERE o1.orderid = (SELECT MAX(o2.orderid) FROM sales.[order] AS o2 WHERE o2.uid = o1.uid)
EXISTS谓词
接受一个子查询作为输入,如果子查询返回任意行返回TRUE,否则FALSE,这也是SQL中少有的2值逻辑场景。此外,EXISTS在查询优化方面也有一定应用,在之后优化的专题中会涉及。
查询没有下订单的美国客户: SELECT * FROM sales.customer AS c WHERE country = 'American' AND NOT EXISTS (SELECT * FROM sales.order AS o WHERE o.custid = c.custid)
是不是觉得上面的部分很简单,那么接下来将带来两个相对复杂的查询场景。
返回前一个值
返回每个当前订单及其前一个订单ID的信息,前一个逻辑上等价于"小于当前值中最大的那个值"
SELECT orderid, orderdate, empid, custid, ( SELECT MAX(T2.orderid) FROM sales.[order] AS T2 WHERE T2.orderid < T1.orderid ) AS prevorderid
FROM sales.[order] AS T1
随时间累积值的聚合
返回每年的订单年度、数量和经过年度的运行总量
SELECT orderyear, qty, ( SELECT SUM(T2.qty) FROM sales.orderTotalByYear AS T2 WHERE T2.orderyear <= T1.orderyear ) AS runqty
FROM sales.orderTotalByYear AS T1 ORDER BY orderyear