2012-02-09 10 views
11

Załóżmy, że masz tabelę "Samochody" z setkami tysięcy wierszy, i chciał zrobić GROUP BY:T-SQL GROUP BY z hrabią, a następnie dołączyć MAX z COUNT

SELECT CarID 
     , CarName 
     , COUNT(*) AS Total 
FROM  dbo.tbl_Cars 
GROUP BY CarID 
     , CarName 

Grupowanie pozostawia wynik podobny do:

CarID  CarName Total 
1872  Olds  202,121 
547841  BMW  175,298 
9877  Ford  10,241 

Wszystko dobrze i dobrze. Moje pytanie jest jednak to, co jest najlepszym sposobem, aby uzyskać Razem i MAX sumy na jednym stole, pod względem wydajności i czystych kodowania, więc masz wynik takiego:

CarID  CarName Total  Max Total 
1872  Olds  202,121 202,121 
547841  BMW  175,298 202,121 
9877  Ford  10,241 202,121 

Jednym podejściem byłoby umieścić wynik GROUP w tabeli temp, , a następnie uzyskać MAX z tabeli temp do zmiennej lokalnej. Ale zastanawiam się, jaki byłby najlepszy sposób na zrobienie tego.


UPDATE

Common Expression tabeli wydaje się najbardziej elegancki pisać, jeszcze podobny do @EBarr, mój ograniczone badania wskazują na znacznie mniejszą wydajność. Więc nie będę z CTE.

Ponieważ link @EBarr dla opcji COMPUTE oznacza, że ​​funkcja jest przestarzała, to nie wydaje się być najlepszą trasą.

Opcja zmiennej lokalnej dla wartości MAX i użycia tabeli temp będzie prawdopodobnie trasą, którą pokonuję, ponieważ nie jestem świadoma problemów z nią związanych.

Nieco więcej szczegółów na temat mojego przypadku użycia: może to prawdopodobnie być seria innych pytań SO z serii . Ale wystarczy powiedzieć, że ładuję duży podzbiór danych do tabeli temp (tak, że podzbiór tbl_Cars jest przechodzący do #tbl_Cars, a nawet #tbl_Cars mogą być dalej filtrowane i mają wykonane agregacje na nim), ponieważ Muszę wykonać wiele filtrowania i kwerendy agregacji na nim w ramach jednego zapisanego procesu, który zwraca wiele zestawów wyników.


UPDATE 2

@ użycie EBarr za okienkowanej funkcją jest miła i krótka. Uwaga dla siebie: jeśli używasz RIGHT JOIN do zewnętrznej tabeli odniesienia, funkcja COUNT() powinna wybrać kolumnę z tbl_Cars, a nie '*'.

SELECT  M.MachineID 
      , M.MachineType 
      , COUNT(C.CarID) AS Total 
      , MAX(COUNT(C.CarID)) OVER() as MaxTotal 
FROM   dbo.tbl_Cars C 
RIGHT JOIN dbo.tbl_Machines M 
     ON  C.CarID = M.CarID 
GROUP BY  M.MachineID 
      , M.MachineType 

Pod względem szybkości, wydaje się w porządku, ale w jakim punkcie masz być zaniepokojeni liczbę odczytów?

Odpowiedz

13

Mechanicznie można to zrobić na kilka sposobów. Możesz użyć zmiennych temp table/table table. Innym sposobem jest wyświetlenie zagnieżdżonych zapytań i/lub CTE jako @Aaron_Bertrand. Trzecim sposobem jest użycie FUNKCJI WINDOWED, takich jak ...

SELECT CarName, 
      COUNT(*) as theCount, 
      MAX(Count(*)) OVER(PARTITION BY 'foo') as MaxPerGroup 
FROM  dbo.tbl_Cars 
GROUP BY CarName 

niekorzystne (czytaj depricated) czwarty sposobem jest użycie słowa kluczowego COMPUTE jako takie ...

SELECT CarID, CarName, Count(*) 
FROM  dbo.tbl_Cars 
GROUP BY CarID, CarName 
COMPUTE MAX(Count(*)) 

COMPUTE kluczowe generuje sumy, które pojawiają się jako dodatkowe kolumny podsumowania na koniec zestaw wyników (see this). W powyższym zapytaniu zobaczysz dwa zestawy rekordów.

Najszybsza

Teraz kolejna kwestia jest to, co jest "najlepszy/najszybszy/najłatwiej". Natychmiast myślę o indexed view. Jak przypomniał mi Aaron, indeksowane widoki mają wiele ograniczeń. Powyższa strategia pozwala jednak utworzyć indeksowany widok na SELECT ... FROM..GROUP BY. Następnie wybierz z widoku indeksowanego zastosowanie klauzuli FUNKCJA WINDOWED.

Nie wiedząc więcej o projekcie, trudno będzie każdemu powiedzieć, co jest najlepsze. Otrzymasz szybkie zapytanie o światło z widoku indeksowanego. Jednak ten występ ma swoją cenę. Cena to koszty utrzymania. Jeśli podstawowa tabela jest celem dużej liczby operacji wstawiania/aktualizacji/usuwania, utrzymanie indeksu spowoduje pogorszenie wydajności w innych obszarach.

Jeśli udostępniasz nieco więcej informacji na temat przypadków użycia i wzorców dostępu do danych, użytkownicy będą mogli udostępniać więcej informacji.


MICRO Performance Test

Więc generowane trochę skrypt danych i spojrzał na liczbach profilera SQL dla wykonywania CTE vs funkcji okienkowym. To jest mikro-test, więc spróbuj kilka liczb rzeczywistych w swoim systemie pod rzeczywistym numerem .

utworzenia:

Create table Cars (CarID int identity (1,1) primary key, 
        CarName varchar(20), 
        value int) 
GO 
insert into Cars (CarName, value) 
values ('Buick', 100), 
     ('Ford', 10), 
     ('Buick', 300),  
     ('Buick', 100), 
     ('Pontiac', 300),  
     ('Bmw', 100), 
     ('Mecedes', 300),  
     ('Chevy', 300),  
     ('Buick', 100), 
     ('Ford', 200); 
GO 1000 

Ten scenariusz powoduje 10000 wierszy. Potem pobiegł każdej z czterech następujących zapytań wiele razy:

--just group by 
select CarName,COUNT(*) countThis 
FROM Cars 
GROUP BY CarName   

--group by with compute (BAD BAD DEVELOPER!) 
select CarName,COUNT(*) countThis 
FROM Cars 
GROUP BY CarName   
COMPUTE MAX(Count(*)); 

-- windowed aggregates... 
SELECT CarName, 
     COUNT(*) as theCount, 
     MAX(Count(*)) OVER(PARTITION BY 'foo') as MaxInAnyGroup 
FROM Cars 
GROUP BY CarName   

--CTE version 
;WITH x AS (
    SELECT CarName, 
      COUNT(*) AS Total 
    FROM  Cars 
    GROUP BY CarName 
) 
SELECT x.CarName, x.Total, x2.[Max Total] 
FROM x CROSS JOIN (
    SELECT [Max Total] = MAX(Total) FROM x 
) AS x2; 

Po przeprowadzeniu powyższych zapytań, stworzyłem indeksowany widok na „tylko grupy przez” kwerendy powyżej. Następnie uruchomiłem kwerendę w indeksowanym widoku, który wykonał MAX(Count(*)) OVER(PARTITION BY 'foo'.

średnie wyniki

Query      CPU  Reads  Duration 
-------------------------------------------------------- 
Group By     15  31  7 ms 
Group & Compute   15  31  7 ms 
Windowed Functions   14  56  8 ms 
Common Table Exp.   16  62  15 ms 
Windowed on Indexed View 0  24  0 ms 

Oczywiście jest to mikro-punkt odniesienia i tylko nieznacznie pouczające, więc należy go za to, co warto.

+0

Nie można używać 'MAX' w widoku indeksowanego (byłem prosić o to przez 5 lat - http://connect.microsoft.com/SQLServer/feedback/details/267516/expand-aggregate-support-in-indexed-views-min-max). Również 'theFieldBeingSearchedForMax' nie znajduje się w tabeli, jest częścią wyjścia (jest to najwyższa liczba). –

+0

Wystarczy ponownie przeczytać pytanie. Źle to przeczytałem. Aktualizacja SQL. – EBarr

+0

- Niestety nie udało mi się dodać GROUP BY w pierwszym zapytaniu; mój błąd. – mg1075

8

Oto jeden ze sposobów:

;WITH x AS 
(
    SELECT CarID 
     , CarName 
     , COUNT(*) AS Total 
    FROM  dbo.tbl_Cars 
    GROUP BY CarID, CarName 
) 
SELECT x.CarID, x.CarName, x.Total, x2.[Max Total] 
FROM x CROSS JOIN 
(
    SELECT [Max Total] = MAX(Total) FROM x 
) AS x2; 
0

SQL Server 2008 R2 i nowsze wersje, można użyć:

GROUP BY CarID, CarName WITH ROLLUP