6

这个问题与执行顺序无关。这只是关于 ORDER BY。

在标准执行中是:

  • 在哪里
  • 通过...分组
  • 拥有
  • 选择
  • 订购方式
  • 最佳

编辑:这个问题或多或少是“ SQL Server 在执行 ORDER BY 表达式时应用短路评估吗? ” 答案是有时!我只是没有找到一个合理的理由。见编辑#4。

现在假设我有这样的声明:

DECLARE @dt18YearsAgo AS DATETIME = DATEADD(YEAR,-18,GETDATE());
SELECT
  Customers.Name
FROM
  Customers
WHERE
  Customers.DateOfBirth > @dt18YearsAgo
ORDER BY
  Contacts.LastName ASC, --STATEMENT1
  Contacts.FirstName ASC, --STATEMENT2
  (
   SELECT
     MAX(PurchaseDateTime)
   FROM
     Purchases
   WHERE
     Purchases.CustomerID = Customers.CustomerID
  ) DESC --STATEMENT3

这不是我要执行的真实语句,而只是一个示例。共有三个 ORDER BY 语句。第三个语句仅用于姓氏和名字匹配的极少数情况。

如果没有重复的姓氏,SQL Server 是否不执行 ORDER BY 语句 #2 和 #3?而且,从逻辑上讲,如果没有重复的姓和名,SQL Server 是否会注意执行语句 #3。

这真的是为了优化。从采购表中读取应该只是最后的手段。在我的应用程序中,从按“CustomerID”分组的“Purchases”中读取每个“PurchaseDateTime”并不是很有效。

请保留与我的问题相关的答案,而不是像在 Purchases 中为 CustomerID、PurchaseDateTime 建立索引这样的建议。真正的问题是,SQL Server 会跳过不必要的 ORDER BY 语句吗?

编辑:显然,只要有一行,SQL Server 就会始终执行每条语句。即使只有一行,这也会给你一个除以零的错误:

DECLARE @dt18YearsAgo AS DATETIME = DATEADD(YEAR,-18,GETDATE());
SELECT
  Customers.Name
FROM
  Customers
WHERE
  Customers.DateOfBirth > @dt18YearsAgo
ORDER BY
  Contacts.LastName ASC, --STATEMENT1
  Contacts.FirstName ASC, --STATEMENT2
  1/(Contacts.ContactID - Contacts.ContactID) --STATEMENT3

Edit2:显然,这并没有除以零:

DECLARE @dt18YearsAgo AS DATETIME = DATEADD(YEAR,-18,GETDATE());
SELECT
  Customers.Name
FROM
  Customers
WHERE
  Customers.DateOfBirth > @dt18YearsAgo
ORDER BY
  Contacts.LastName ASC, --STATEMENT1
  Contacts.FirstName ASC, --STATEMENT2
  CASE WHEN 1=0
    THEN Contacts.ContactID
    ELSE 1/(Contacts.ContactID - Contacts.ContactID)
  END --STATEMENT3

好吧,我的问题的原始答案是肯定的,它确实执行了,但好的是我可以在适当的情况下停止执行

编辑 3:我们可以使用适当的 CASE WHEN 停止执行 ORDER BY 语句。我想,诀窍是弄清楚如何正确使用它。CASE WHEN 会给我我想要的东西,即 ORDER BY 语句中的短路执行。我比较了 SSMS 中的执行计划,根据 CASE WHEN 语句,即使它是一个清晰可见的 SELECT/FROM 语句,也根本不扫描 Purchases 表:

DECLARE @dt18YearsAgo AS DATETIME = DATEADD(YEAR,-18,GETDATE());
SELECT
  Customers.Name
FROM
  Customers
WHERE
  Customers.DateOfBirth > @dt18YearsAgo
ORDER BY
  Contacts.LastName ASC, --STATEMENT1
  Contacts.FirstName ASC, --STATEMENT2
  CASE WHEN 1=0
    THEN
    (
     SELECT
       MAX(PurchaseDateTime)
     FROM
       Purchases
     WHERE
       Purchases.CustomerID = Customers.CustomerID
    )
    ELSE Customers.DateOfBirth
  END DESC

编辑4:现在我完全糊涂了。这是@Lieven 的一个例子

WITH Test (name, ID) AS
(SELECT 'Lieven1', 1 UNION ALL SELECT 'Lieven2', 2)

SELECT * FROM Test ORDER BY name, 1/ (ID - ID)

这不会产生除以零,这意味着 SQL Server 实际上确实对某些表进行短路评估,特别是那些使用 WITH 命令创建的表。

尝试使用 TABLE 变量:

DECLARE @Test TABLE
(
    NAME nvarchar(30),
    ID int
);
INSERT INTO @Test (Name,ID) VALUES('Lieven1',1);
INSERT INTO @Test (Name,ID) VALUES('Lieven2',2);
SELECT * FROM @Test ORDER BY name, 1/ (ID - ID)

将产生除以零错误。

4

3 回答 3

5

首先,您所说的“声明”不是这样的。它们是 ORDER BY(主要)子句的子句。区别很重要,因为“语句”暗示着一些可分离的、有序的和程序性的东西,而 SQL 子句不是这些东西。

具体来说,SQL 子句(即 SQL 主要子句的各个项目(SELECT、FROM、WHERE、ORDER BY 等))没有自己的隐式(或显式)执行顺序。SQL 将以任何它认为方便的方式重新排序它们,并且如果它执行其中任何一个,它几乎总是会执行所有它们。简而言之,SQL Server 不会进行那种“短路”优化,因为它们非常有效并且严重妨碍了它所做的非常不同类型的优化(即,统计数据访问/运算符优化)。

所以你原来的问题(你不应该改变)的正确答案是不,不可靠。您不能依赖 SQL Server 不使用 ORDER BY 的某些子句,仅仅因为它看起来不需要。

唯一常见的例外是 CASE 函数可以(在大多数情况下)用于短路执行路径(尽管CASE 函数内,而不是在它之外),但这仅仅是因为它是专门为此设计的。我想不出 SQL 中还有什么其他东西可以让你这样做。

于 2012-07-13T17:52:50.873 回答
0
DECLARE @MyTable TABLE
(
  Data varchar(30)
)

INSERT INTO @MyTable (Data) SELECT 'One'
INSERT INTO @MyTable (Data) SELECT 'Two'
INSERT INTO @MyTable (Data) SELECT 'Three'

--SELECT *
--FROM @MyTable
--ORDER BY LEN(Data), LEN(Data)/0
  -- Divide by zero error encountered.

SELECT *
FROM @MyTable
ORDER BY LEN(Data), CASE WHEN Data is null THEN LEN(Data)/0 ELSE 1 END
  -- no problem

我也SET STATISTICS IO ON 看到了这些结果:

SELECT *
FROM @MyTable
ORDER BY LEN(Data)
--(3 row(s) affected)
--Table '#4F2895A9'. Scan count 1, logical reads 1    

SELECT *
FROM @MyTable
ORDER BY LEN(Data), CASE WHEN Data = 'One' THEN (SELECT MAX(t2.Data) FROM @MyTable t2) ELSE Data END
--(3 row(s) affected)
--Table '#4F2895A9'. Scan count 2, logical reads 2

SELECT *
FROM @MyTable
ORDER BY LEN(Data), CASE WHEN Data = 'Zero' THEN (SELECT MAX(t2.Data) FROM @MyTable t2) ELSE Data END
--(3 row(s) affected)
--Table 'Worktable'. Scan count 0, logical reads 0
--Table '#4F2895A9'. Scan count 1, logical reads 1
于 2012-07-13T17:52:55.990 回答
0

我想你已经回答了你的问题。但是,为什么你只对firstnamelastname上的数据进行排序,如果这两个相同,那么采购订单,否则你会在 DOB 上做?

从逻辑上讲,它应该是firstnamelastnameDOB。如果这三个相同,那么您应该评估purchaseorderdate。同名的人很多,但同名和出生日期相同的人却很少。这将减少您查询购买表的时间。

于 2012-07-13T18:39:44.490 回答