SQL SERVER技术内幕之4 子查询

    最外层查询的结果集会返回给调用者,称为外部查询。内部查询的结果是供外部查询使用的,也称为子查询。子查询可以分成独立子查询和相关子查询两类。独立子查询不依赖于它所属的外部查询,而相关子查询则须依赖它所属的外部查询。子查询的期望值可以是单值的、多值的或以表为值。

1.独立子查询

    独立子查询是独立于其外部查询的子查询。在逻辑上,独立子查询在执行外部查询之前只要先执行一次,接着外部查询再使用子查询的结果继续进行查询。

1.1 独立标量子查询

    标量子查询是返回单个值的子查询,标量子查询可以出现在外部查询中期望使用单个值的任何地方(WHERE、SELECT等等)。示例:查询订单号最大的订单信息

SELECT orderid, orderdate, empid, custid
FROM Sales.Orders
WHERE orderid = (SELECT MAX(O.orderid)
                 FROM Sales.Orders AS O);

    对于有效的标量子查询,它的返回值不能超过一个。如果标量子查询返回了多个值,在运行时则可能会失败。

技术分享

1.2 独立多值子查询

    多值子查询是在一个列中返回多个值的子查询,而不管子查询是不是独立的。一些谓词(比如IN谓词)可以处理多值子查询。

SELECT orderid
FROM Sales.Orders
WHERE empid IN
  (SELECT E.empid
   FROM HR.Employees AS E
   WHERE E.lastname LIKE ND%);

  其实对于以上需求,同样可以用联接代替子查询来完成这个任务。

SELECT O.orderid
FROM HR.Employees AS E
  JOIN Sales.Orders AS O
    ON E.empid = O.empid
WHERE E.lastname LIKE ND%;

 

2.相关子查询

    相关子查询是指引用了外部查询中出现的表的列的子查询,这就意味着子查询要依赖于外部查询。

2.1 EXISTS谓词

    支持一个名为EXISTS的谓词,它的输入是一个子查询,如果子查询能够返回任何行,该谓词则返回TRUE,否则返回FALSE。以下查询语句返回下过订单的西班牙客户:

SELECT custid, companyname
FROM Sales.Customers AS C
WHERE country = NSpain
  AND EXISTS
    (SELECT * FROM Sales.Orders AS O
     WHERE O.custid = C.custid);

 

3 高级子查询

3.1 返回前一个或后一个记录

    假设现在要对Orders表进行查询,对于每个订单,返回当前订单的信息和它前一个订单的信息。"前一个"概念可以用"小于当前值的最大值"来进行表述。类似的"后一个"概念可以用"大于当前值的最小值"来进行表述。

SELECT orderid, orderdate, empid, custid,
  (SELECT MAX(O2.orderid)
   FROM Sales.Orders AS O2
   WHERE O2.orderid < O1.orderid) AS prevorderid
FROM Sales.Orders AS O1;

3.2 连续聚合(Running Aggregate)

    连续聚合是一种对累积数据(通常是时间顺序)执行的聚合,首先定义一个视图Sales.OrderTotalsByYear,这个视图包含每年的总订货量。

    技术分享

    假设现在有个任务,需要返回每年的订单年份、订货量以及连续几年的总订货量。也就是每一年返回截止到当年的订货量的总和。以下是解决方案的查询语句:

SELECT orderyear, qty,
  (SELECT SUM(O2.qty)
   FROM Sales.OrderTotalsByYear AS O2
   WHERE O2.orderyear <= O1.orderyear) AS runqty
FROM Sales.OrderTotalsByYear AS O1
ORDER BY orderyear;

3.3 行为不当(Misbehaving)的子查询

    这部分将介绍几种子查询的运行结果可能与你期望的结果恰好相反的情况,以及为了避免在代码中发生与这些情况有关的逻辑缺陷而应该执行的最佳实践。

    NULL的问题

    考虑以下看起来很直观的查询,它原本是想返回没有下过订单的客户:

SELECT custid, companyname
FROM Sales.Customers AS C
WHERE custid NOT IN(SELECT O.custid
                    FROM Sales.Orders AS O);

    考虑一种情况,假如Sales.Orders表中出现一行custid为NULL的记录,这时WHERE条件语句返回的所有结果都是UnKnown,所以最后查询出现的结果集为空。

    那么,有什么可以执行的实践原则能避免这个问题呢?

    1.当一个列不应该允许为NULL时,把它定义为NOT NULL很重要。加强数据的完整性定义,比很多人相像的要重要得多。

    2.在你写的所有查询语句中,应该考虑三值逻辑可能出现的三种真值。明确地考虑一下是否要处理NULL值,如果要处理,对NULL值的默认处理是否适合需要,当不适合时,就要对查询语句进行调整。显式地排除NULL值的一个例子就是在子查询中添加谓词O.custid is not null,如下所示:

SELECT custid, companyname
FROM Sales.Customers AS C
WHERE custid NOT IN(SELECT O.custid 
                    FROM Sales.Orders AS O
                    WHERE O.custid IS NOT NULL);

隐式地排除NULL值的一个例子是使用NOT EXISTS谓词取代NOT IN谓词,如下所示:

SELECT custid, companyname
FROM Sales.Customers AS C
WHERE NOT EXISTS
  (SELECT * 
   FROM Sales.Orders AS O
   WHERE O.custid = C.custid);

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。