Pole obliczeniowe SQL w klauzulach SELECT i GROUP BY

11

Często w zapytaniach do baz danych MS SQL Server muszę utworzyć pole obliczeniowe, takie jak to

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

a następnie muszę pogrupować wyniki według tego pola obliczeniowego (między innymi). Dlatego mam takie same obliczenia zarówno w klauzulach SELECT, jak i GROUP BY. Czy serwer SQL faktycznie wykonuje te obliczenia dwukrotnie, czy jest wystarczająco inteligentny, aby wykonać to tylko raz?

Dr Drew
źródło

Odpowiedzi:

13

Mam takie same obliczenia w klauzulach SELECT i GROUP BY. Czy serwer SQL faktycznie wykonuje te obliczenia dwukrotnie, czy jest wystarczająco inteligentny, aby wykonać to tylko raz?

Prosta odpowiedź jest taka, że ​​SQL Server nie daje ogólnych gwarancji, kiedy i ile razy wyrażenie skalarne będzie oceniane w czasie wykonywania.

W optymalizatorze i silniku wykonawczym występują różnego rodzaju skomplikowane (i nieudokumentowane) zachowania dotyczące umieszczania, wykonywania i buforowania wyrażeń skalarnych. Books Online nie ma wiele do powiedzenia na ten temat, ale mówi :

Oblicz opis skalarny

Opisuje to jedno z zachowań, o których wspominałem wcześniej, odraczania wykonywania wyrażeń. O niektórych innych bieżących zachowaniach (które mogą się zmienić w dowolnym momencie) napisałem w tym poście na blogu .

Inną kwestią jest to, że model kosztów wykorzystywany przez optymalizator zapytań obecnie nie robi wiele w zakresie szacowania kosztów dla wyrażeń skalarnych. Bez solidnych ram kalkulacji kosztów bieżące wyniki opierają się na szerokiej heurystyce lub czystej szansie.

W przypadku bardzo prostych wyrażeń prawdopodobnie nie ma większego znaczenia, czy wyrażenie jest oceniane raz czy wiele razy w większości przypadków. To powiedziawszy, spotkałem się z dużymi zapytaniami, w których wydajność została niekorzystnie obniżona, gdy wyrażenie jest oceniane bardzo wiele razy nadmiarowo, lub ocena występuje w jednym wątku, w którym korzystna byłaby ocena w równoległej gałęzi wykonania plan.

Podsumowując, bieżące zachowanie jest niezdefiniowane i nie ma nic w planach wykonania, które pomogłyby ci dowiedzieć się, co się stało (i nie zawsze wygodne będzie dołączenie debugera do zbadania szczegółowych zachowań silnika, jak w poście na blogu).

Jeśli napotkasz przypadki, w których problemy z oceną skalarną mają znaczenie dla wydajności, podnieś problem ze wsparciem Microsoft. Jest to najlepszy sposób przekazywania opinii w celu ulepszenia przyszłych wersji produktu.

Paul White 9
źródło
3

Jak stwierdza komentarz do twojego pytania, odpowiedź brzmi (przynajmniej z mojego doświadczenia) „tak”. SQL Server jest na ogół wystarczająco inteligentny, aby uniknąć ponownych obliczeń. Prawdopodobnie można to zweryfikować, pokazując plan wykonania z poziomu SQL Server Management Studio. Każde pole obliczeniowe jest oznaczone Exprxxxxx(gdzie xxxxx jest liczbą). Jeśli wiesz, czego szukać, powinieneś być w stanie sprawdzić, czy używa tego samego wyrażenia.

Aby dodać do dyskusji, drugą opcją estetyczną jest wspólne wyrażenie tabeli :

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]

Krótka odpowiedź, są funkcjonalnie identyczne z widokiem, ale są ważne tylko do użycia w następnej instrukcji. Widzę je jako w większości bardziej czytelną alternatywę dla tabel pochodnych, ponieważ unika się zagnieżdżania.

Chociaż nie są istotne dla tego pytania, mogą się do nich odwoływać iw ten sposób mogą być wykorzystywane do konstruowania zapytań rekurencyjnych.

Szybki Joe Smith
źródło
@ Szybki Joe Smith: Myślę, że masz rację co do Exprxxxxx, ponieważ ja też to widziałem. Jeśli jednak podam nazwę wyrażenia ręcznie (wielkość liter ... koniec) jako OpType, a następnie użyję pola OpType w klauzuli GROUP BY, otrzymuję błąd, że jest to niepoprawna nazwa kolumny.
Dr Drew
Niestety, często jedynym sposobem na uniknięcie dwukrotnego określenia wyrażenia jest użycie jednej z powyższych metod: CTE, widok lub zapytanie zagnieżdżone.
Szybki Joe Smith,
2
Chyba że wiesz także o aplikacji CROSS APPLY .
Andriy M
Użycie cross applyw tym przypadku jest nieco rozciągnięte i bardzo prawdopodobne, że zaszkodzi wydajności, wprowadzając niepotrzebne samozłączenie.
Szybki Joe Smith
2
Nie sądzę, że „dostałeś” sugestię. CROSS APPLYWłaśnie definiuje alias z kolumny w tym samym wierszu. Nie ma potrzeby dołączania. np.SELECT COUNT(*), hilo FROM master..spt_values CROSS APPLY (VALUES(high + low)) V(hilo) GROUP BY hilo
Martin Smith
1

Wydajność to tylko jeden aspekt. Druga to łatwość konserwacji.

Osobiście zwykle wykonuję następujące czynności:

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

AKTUALIZACJA:

Jeśli nie lubisz zagnieżdżać, możesz utworzyć WIDOK dla każdej tabeli, w której musisz użyć złożonych wyrażeń.

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

Następnie możesz dokonać wyboru bez dodatkowego zagnieżdżania;

SELECT GroupingKey, SUM(value)
FROM TableExtended
GROUP BY GroupingKey
Kaspars Ozols
źródło