Выполнение порядка выполнения в SQL

Этот вопрос не о порядке выполнения. Это примерно как ORDER BY.

В стандартном исполнении:

  • С
  • WHERE
  • GROUP BY
  • HAVING
  • SELECT
  • ЗАКАЗАТЬ
  • 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.

Это действительно для оптимизации. Чтение из таблицы "Покупки" должно быть только последним. В случае моего приложения было бы нецелесообразно читать каждый "PurchaseDateTime" из группы "Покупки" с помощью "CustomerID".

Пожалуйста, сохраните ответ, связанный с моим вопросом, а не предложение, как создать индекс для 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

Ну, исходный ответ на мой вопрос - ДА, он выполняет, но что приятно, я могу прекратить выполнение с надлежащим CASE WHEN

Изменить 3: Мы можем прекратить выполнение инструкции ORDER BY с надлежащим CASE КОГДА. Хитрость, я думаю, заключается в том, чтобы выяснить, как правильно ее использовать. CASE WHEN даст мне то, что я хочу, что короткое замыкание в инструкции ORDER BY. Я сравнил план выполнения в SSMS и в зависимости от оператора CASE WHEN таблица покупок не отсканирована вообще. ДАЖЕ, ЧТО это отчетливо видный оператор 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 действительно делает оценку короткого замыкания в таблицах SOME, особенно тех, которые созданы с помощью команды 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)

приведет к делению на нулевую ошибку.

Ответ 1

Прежде всего то, что вы называете "заявлениями", не является таковым. Они являются подклассами предложения ORDER BY (major). Разница важна, потому что "Statement" подразумевает что-то отделимое, упорядоченное и процедурное, а подклассы SQL - это ни одна из этих вещей.

В частности, подклассы SQL (то есть отдельные элементы основного предложения SQL (SELECT, FROM, WHERE, ORDER BY и т.д.)) не имеют имплицитного (или явного) порядка выполнения. SQL будет переупорядочивать их в любом случае, что он найдет удобным и почти всегда будет выполнять все из них, если он выполнит любое из них. Короче говоря, SQL Server не выполняет такую ​​оптимизацию "короткого замыкания", поскольку они тривиально эффективны и серьезно мешают очень различным типам оптимизаций, которые он делает (т.е. Статистическому доступу к данным/оптимизации оператора).

Итак, правильный ответ на ваш первоначальный вопрос (который вы не должны были изменить) НЕТ, не надежно. Вы не можете полагаться на SQL Server, чтобы не использовать какой-либо подзаговор ORDER BY, просто потому, что он не похож на него.

Единственным распространенным исключением является то, что функция CASE может (в большинстве случаев) использоваться для коротких замыканий путей выполнения (внутри функции CASE, но не вне ее), а только потому, что она специально предназначена для этого, Я не могу думать ни о чем другом в SQL, на который вы можете положиться, чтобы действовать так.

Ответ 2

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

Ответ 3

Я думаю, вы ответили на свой вопрос. Однако почему вы сортируете данные только по первому, фамилию и если эти два являются одинаковыми, то порядок заказа в противном случае вы будете делать на DOB?

Логически, это должно быть имя, фамилия, DOB. Если эти три одинаковые, только тогда вы должны оценить дату покупки. Есть много людей с одинаковыми именами, но очень немногие имеют одинаковые имена и DOB. Это уменьшит время, когда вы будете запрашивать таблицу закупок.