6

この質問は、実行の順序に関するものではありません。それはちょうどORDER BYについてです。

標準的な実行では次のとおりです。

  • から
  • どこ
  • グループ化
  • 持っている
  • 選択する
  • オーダーバイ

編集: この質問は多かれ少なかれ「SQL Server は ORDER BY 式を実行するときに短絡評価を適用しますか?」という問題でした。答えは SOMETIMES です! 合理的な理由が見つからないだけです。編集#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 ステートメントは 3 つあります。3 番目のステートメントは、姓と名が一致するまれなケースにのみ使用されます。

姓が重複していない場合、SQL Server は ORDER BY ステートメント #2 と #3 を実行しませんか? また、論理的には、姓と名が重複していない場合、SQL Server はステートメント #3 を実行します。

これは本当に最適化のためです。Purchases テーブルからの読み取りは、最後の手段にすぎません。私のアプリケーションの場合、「CustomerID」でグループ化された「Purchases」からすべての「PurchaseDateTime」を読み取るのは効率的ではありません。

Purchases で CustomerID、PurchaseDateTime のインデックスを構築するような提案ではなく、私の質問に関連する回答を保持してください。問題は、SQL Server が不要な ORDER BY ステートメントをスキップするかどうかです。

編集: どうやら、SQL Server は、行が 1 つある限り、常にすべてのステートメントを実行します。1行であっても、これによりゼロ除算エラーが発生します。

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

ええと、私の質問に対する最初の答えは「はい」です。実行しますが、適切な CASE WHEN で実行を停止できるのは素晴らしいことです。

編集 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 が実行する非常に異なる種類の最適化 (つまり、統計データ アクセス/演算子の最適化) の邪魔になるからです。

したがって、元の質問 (変更してはならない) に対する正しい答えは NO であり、確実ではありません。必要がないように見えるという理由だけで、SQL Server が ORDER BY の一部のサブ句を使用しないことに依存することはできません。

これに対する唯一の一般的な例外は、CASE 関数は (ほとんどの状況で) 実行パスを短絡するために使用できることです (ただし、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だけでデータを並べ替えるのはなぜですか。これら2つが同じ場合は注文書で、そうでない場合はDOBで行いますか?

論理的には、firstnamelastnameDOBである必要があります。これら 3 つが同じである場合にのみ、purchaseorderdateを評価する必要があります。同じ名前の人はたくさんいますが、名前と生年月日が同じ人はほとんどいません。これにより、購入テーブルをクエリする時間が短縮されます。

于 2012-07-13T18:39:44.490 に答える