在SQL中执行order by子句

时间:2012-07-13 17:22:58

标签: sql sql-server sql-order-by

这个问题不是关于处决的顺序。这只是ORDER BY。

标准执行是:

  • FROM
  • WHERE
  • GROUP BY
  • HAVING
  • 选择
  • ORDER BY
  • TOP

编辑:这个问题或多或少是“ 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”效率不高。

请保留与我的问题相关的答案,而不是像购买中的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语句,不会扫描Purchases表,即使它是一个清晰可见的SELECT / FROM语句:

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)

将产生除以零误差。

3 个答案:

答案 0 :(得分:5)

首先,你所谓的“陈述”不是这样的。它们是ORDER BY(主要)条款的子条款。区别很重要,因为“Statement”意味着可分离,有序和程序化的东西,而SQL子句则不属于这些。

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

因此,对原始问题(您不应该更改)的正确答案是否定的,不可靠。您不能依赖SQL Server不使用ORDER BY的某些子子句,只是因为它看起来不需要。

唯一常见的例外是CASE函数可以(在大多数情况下)用于短路执行路径( CASE函数中,但不在其外部),但仅限于因为它是专门为此而设计的。我无法想到你可以依赖的任何其他SQL行为。

答案 1 :(得分: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

答案 2 :(得分:0)

我猜你已经回答了你的问题。但是,为什么要对 firstname lastname 上的数据进行排序,如果这两个相同,那么购买订单,否则你将在DOB上进行?

逻辑上,它应该是名字姓氏 DOB 。如果这三者相同,那么您才应该评估 purchaseorderdate 。有许多人具有相同的名称,但很少有相同的名称和DOB。这将减少您查询购买表的时间。