Вычисляемое поле SQL в предложении SELECT и GROUP BY

11

Часто при запросе к моим базам данных MS SQL Server мне нужно создать вычисляемое поле, например

(CASE WHEN A.type = 'Workover' THEN 'Workover' 
      ELSE (CASE WHEN substring(C.category, 2, 1) = 'D' THEN 'Drilling' 
                 WHEN substring(C.category, 2, 1) = 'C' THEN 'Completion' 
                 WHEN substring(C.category, 2, 1) = 'W' THEN 'Workover' 
                 ELSE 'Other' 
            END)
END)

а затем мне нужно сгруппировать свои результаты по этому рассчитанному полю (среди прочих). Следовательно, у меня одинаковые вычисления в предложениях SELECT и GROUP BY. Действительно ли SQL-сервер выполняет эти вычисления дважды, или он достаточно умен, чтобы делать это только один раз?

Доктор Дрю
источник

Ответы:

13

У меня одинаковые вычисления в предложениях SELECT и GROUP BY. Действительно ли SQL-сервер выполняет эти вычисления дважды, или он достаточно умен, чтобы делать это только один раз?

Простой ответ заключается в том, что SQL Server не дает общих гарантий относительно того, когда и сколько раз скалярное выражение будет оцениваться во время выполнения.

В оптимизаторе и механизме исполнения есть все виды сложных (и недокументированных) поведений, касающихся размещения, выполнения и кэширования скалярных выражений. Книгам онлайн не нужно много говорить об этом, но то , что он говорит , это:

Вычислить скалярное описание

Это описывает одно из поведений, на которое я ссылался ранее, отложенное выполнение выражений. Я написал о некоторых других текущих поведениях (которые могли измениться в любое время) в этом сообщении в блоге .

Другое соображение заключается в том, что модель затрат, используемая оптимизатором запросов , в настоящее время мало что делает для оценки стоимости скалярных выражений. Без надежной структуры затрат текущие результаты основаны на широкой эвристике или чистой случайности.

Для очень простых выражений, вероятно, не имеет большого значения, вычисляется ли выражение один или несколько раз в большинстве случаев. Тем не менее, я столкнулся с большими запросами, где производительность снижалась, когда выражение избыточно оценивалось очень много раз, или когда оценка выполнялась в одном потоке, где было бы выгодно оценивать параллельную ветвь выполнения. план.

Таким образом, текущее поведение не определено, и в планах выполнения ничего особенного нет, чтобы помочь вам выяснить, что произошло (и не всегда будет удобно подключать отладчик для изучения подробного поведения движка, как в сообщении в блоге).

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

Пол Уайт 9
источник
3

Как говорится в комментарии к вашему вопросу, ответ (по моему опыту, по крайней мере) «да». SQL Server, как правило, достаточно умен, чтобы избежать повторных вычислений. Вероятно, вы могли бы убедиться в этом, показав план выполнения из среды SQL Server Management Studio. Каждое вычисляемое поле обозначено Exprxxxxx(где xxxxx - число). Если вы знаете, что искать, вы сможете убедиться, что оно использует то же выражение.

Чтобы добавить к обсуждению, другой вариант эстетики - это обычное табличное выражение :

with [cte] as
(
    select
        (case when a.type = 'workover' then 'workover' else 
        (case when substring(c.category, 2, 1) = 'd' then 'drilling'
              when substring(c.category, 2, 1) = 'c' then 'completion'
              when substring(c.category, 2, 1) = 'w' then 'workover'
              else 'other' end)
         end)) as [group_key],
         *
    from
        [some_table]
)
select
    [group_key],
    count(*) as [count]
from
    [cte]
group by
    [group_key]

Короткий ответ, они функционально идентичны представлению, но действительны только для использования в следующем утверждении. Я рассматриваю их в качестве более удобочитаемой альтернативы производным таблицам, поскольку она позволяет избежать вложения.

Хотя они и не имеют отношения к этому вопросу, они могут ссылаться на себя и таким образом использоваться для построения рекурсивных запросов.

Быстрый Джо Смит
источник
@Quick Джо Смит: Я думаю, вы правы насчет Exprxxxxx, поскольку я тоже это видел. Однако, если я даю имя выражению вручную (case ... end) как OpType, а затем использую поле OpType в предложении GROUP BY, я получаю ошибку, что это недопустимое имя столбца.
Доктор Дрю
К сожалению, часто единственным выходом из указания выражения дважды является использование одного из вышеуказанных методов: CTE, view или вложенного запроса.
Быстрый Джо Смит
2
Если только вы не знаете о CROSS APPLY .
Андрей М
Использование cross applyв этом случае немного растянуто, и это, скорее всего, повредит производительности, введя ненужное самостоятельное соединение.
Быстрый Джо Смит
2
Я не думаю, что вы "получили" предложение. CROSS APPLYПросто определяет псевдоним из столбцов в одной и той же строке. Нет необходимости в соединении. напримерSELECT COUNT(*), hilo FROM master..spt_values CROSS APPLY (VALUES(high + low)) V(hilo) GROUP BY hilo
Мартин Смит
1

Производительность - это только один аспект. Другое дело в ремонтопригодности.

Лично я склонен делать следующее:

SELECT T.GroupingKey, SUM(T.value)
FROM
(
    SELECT 
        A.*
        (CASE WHEN A.type = 'Workover' THEN 'Workover' ELSE 
        (CASE WHEN substring(C.category, 2, 1) = 'D' THEN 'Drilling' WHEN substring(C.category, 2, 1) = 'C' THEN 'Completion' WHEN substring(C.category, 2, 1) = 'W' THEN 'Workover' ELSE 'Other' END)
        END) AS GroupingKey
    FROM Table AS A
) AS T

GROUP BY T.GroupingKey

ОБНОВИТЬ:

Если вы не любите делать вложения, вы можете создать VIEW для каждой таблицы, где вам нужно использовать сложные выражения.

CREATE VIEW TableExtended
AS 
SELECT 
    A.*
    (CASE WHEN A.type = 'Workover' THEN 'Workover' ELSE 
    (CASE WHEN substring(C.category, 2, 1) = 'D' THEN 'Drilling' WHEN substring(C.category, 2, 1) = 'C' THEN 'Completion' WHEN substring(C.category, 2, 1) = 'W' THEN 'Workover' ELSE 'Other' END)
    END) AS GroupingKey
FROM Table AS A

Тогда вы могли бы делать выборку без дополнительных вложений;

SELECT GroupingKey, SUM(value)
FROM TableExtended
GROUP BY GroupingKey
Каспарс Озолс
источник