Campo calculado do SQL na cláusula SELECT e GROUP BY

11

Freqüentemente, ao consultar meus bancos de dados do MS SQL Server, preciso criar um campo calculado, como este

(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)

e, em seguida, preciso agrupar meus resultados por esse campo calculado (entre outros). Portanto, eu tenho o mesmo cálculo nas cláusulas SELECT e GROUP BY. O servidor SQL está realmente realizando esses cálculos duas vezes ou é inteligente o suficiente para fazer apenas uma vez?

Dr. Drew
fonte

Respostas:

13

Eu tenho o mesmo cálculo nas cláusulas SELECT e GROUP BY. O servidor SQL está realmente realizando esses cálculos duas vezes ou é inteligente o suficiente para fazer apenas uma vez?

A resposta simples é que o SQL Server não oferece garantias gerais sobre quando e quantas vezes uma expressão escalar será avaliada no momento da execução.

Existem todos os tipos de comportamentos complicados (e não documentados) no mecanismo de otimização e execução em relação ao posicionamento, execução e armazenamento em cache de expressões escalares. O Books Online não tem muito a dizer sobre isso, mas o que diz é o seguinte:

Computar descrição escalar

Isso descreve um dos comportamentos que eu aludi antes, a execução adiada de expressões. Eu escrevi sobre alguns dos outros comportamentos atuais (que podem mudar a qualquer momento) nesta postagem do blog .

Outra consideração é que o modelo de custo usado pelo otimizador de consultas atualmente não faz muito em termos de estimativa de custos para expressões escalares. Sem uma estrutura de custos robusta, os resultados atuais são baseados em heurísticas amplas ou em puro acaso.

Para expressões muito simples, provavelmente não faz muita diferença se a expressão é avaliada uma ou várias vezes na maioria dos casos. Dito isso, encontrei grandes consultas em que o desempenho foi afetado adversamente quando a expressão é avaliada redundantemente várias vezes ou a avaliação ocorre em um único encadeamento no qual seria vantajoso avaliar em um ramo paralelo da execução plano.

Em resumo, o comportamento atual é indefinido e não há muito nos planos de execução para ajudá-lo a descobrir o que aconteceu (e nem sempre será conveniente anexar um depurador para examinar os comportamentos detalhados do mecanismo, como na postagem do blog).

Se você encontrar casos em que problemas de avaliação escalar são importantes para o desempenho, levante o problema com o Suporte da Microsoft. Essa é a melhor maneira de fornecer feedback para melhorar as versões futuras do produto.

Paul White 9
fonte
3

Como afirma o comentário da sua pergunta, a resposta é (pelo menos na minha experiência) "sim". O SQL Server geralmente é inteligente o suficiente para evitar o recálculo. Provavelmente, você pode verificar isso mostrando o plano de execução no SQL Server Management Studio. Cada campo calculado é designado Exprxxxxx(onde xxxxx é um número). Se você souber o que procurar, poderá verificar se ele usa a mesma expressão.

Para adicionar à discussão, sua outra opção estética é uma expressão de tabela comum :

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]

Resposta curta, eles são funcionalmente idênticos a uma visualização, mas são válidos apenas para uso na declaração a seguir. Eu os vejo como uma alternativa mais legível às tabelas derivadas, porque evita o aninhamento.

Embora não sejam relevantes para essa pergunta, eles podem se referir e, dessa maneira, ser usados ​​para construir consultas recursivas.

Quick Joe Smith
fonte
@ Joe Rápido Smith: Eu acho que você está correto sobre o Exprxxxxx, já que eu também vi isso. No entanto, se eu atribuir um nome à expressão manualmente (case ... end) como OpType, use o campo OpType na cláusula GROUP BY, recebo um erro indicando que é um nome de coluna inválido.
Dr. Drew
Infelizmente, muitas vezes a única maneira de especificar a expressão duas vezes é usar um dos métodos acima: uma CTE, exibição ou uma consulta aninhada.
Quick Joe Smith
2
A menos que você também saiba sobre o CROSS APPLY .
21714 Andriy M
A utilização cross applynesse caso é um pouco exagerada e provavelmente prejudicaria o desempenho ao introduzir uma auto-junção desnecessária.
Quick Joe Smith
2
Eu não acho que você "entendeu" a sugestão. O CROSS APPLYjust define o alias das colunas na mesma linha. Não há necessidade de participar. por exemplo,SELECT COUNT(*), hilo FROM master..spt_values CROSS APPLY (VALUES(high + low)) V(hilo) GROUP BY hilo
Martin Smith,
1

O desempenho é apenas um aspecto. O outro é a manutenção.

Pessoalmente, costumo fazer o seguinte:

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

ATUALIZAR:

Se você não gosta de aninhar, pode criar VIEW para cada tabela em que precisa usar expressões complexas.

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

Então você pode selecionar sem fazer aninhamento extra;

SELECT GroupingKey, SUM(value)
FROM TableExtended
GROUP BY GroupingKey
Kaspars Ozols
fonte