2009-09-04 16 views
5

Próbuję skonfigurować niektóre dane, aby obliczyć wiele median w SQL Server 2008, ale mam problem z wydajnością. W tej chwili używam tego pattern ([inny przykład bottom). Tak, nie używam WRT, ale użycie jednego nie rozwiąże problemu, który mam i tak działa, a wydajność jest niska, ponieważ podokresy wiersza numerycznego są uruchamiane szeregowo, a nie równolegle.Wiele połączeń Row_Number() w pojedynczym zapytaniu SQL

Oto pełny przykład. Poniżej SQL wyjaśniam problem bardziej.

-- build the example table  

CREATE TABLE #TestMedian (
    StateID INT, 
    TimeDimID INT, 
    ConstructionStatusID INT, 

    PopulationSize BIGINT, 
    SquareMiles BIGINT 
); 

INSERT INTO #TestMedian (StateID, TimeDimID, ConstructionStatusID, PopulationSize, SquareMiles) 
VALUES (1, 1, 1, 100000, 200000); 

INSERT INTO #TestMedian (StateID, TimeDimID, ConstructionStatusID, PopulationSize, SquareMiles) 
VALUES (1, 1, 1, 200000, 300000); 

INSERT INTO #TestMedian (StateID, TimeDimID, ConstructionStatusID, PopulationSize, SquareMiles) 
VALUES (1, 1, 1, 300000, 400000); 

INSERT INTO #TestMedian (StateID, TimeDimID, ConstructionStatusID, PopulationSize, SquareMiles) 
VALUES (1, 1, 1, 100000, 200000); 

INSERT INTO #TestMedian (StateID, TimeDimID, ConstructionStatusID, PopulationSize, SquareMiles) 
VALUES (1, 1, 1, 250000, 300000); 

INSERT INTO #TestMedian (StateID, TimeDimID, ConstructionStatusID, PopulationSize, SquareMiles) 
VALUES (1, 1, 1, 350000, 400000); 

--TruNCATE TABLE TestMedian 

    SELECT 
     StateID 
     ,TimeDimID 
     ,ConstructionStatusID 
     ,NumberOfRows = COUNT(*) OVER (PARTITION BY StateID, TimeDimID, ConstructionStatusID) 
     ,PopulationSizeRowNum = ROW_NUMBER() OVER (PARTITION BY StateID, TimeDimID, ConstructionStatusID ORDER BY PopulationSize) 
     ,SquareMilesRowNum = ROW_NUMBER() OVER (PARTITION BY StateID, TimeDimID, ConstructionStatusID ORDER BY SquareMiles) 
     ,PopulationSize 
     ,SquareMiles 
    INTO #MedianData 
    FROM #TestMedian 

    SELECT MinRowNum = MIN(PopulationSizeRowNum), MaxRowNum = MAX(PopulationSizeRowNum), StateID, TimeDimID, ConstructionStatusID, MedianPopulationSize= AVG(PopulationSize) 
    FROM #MedianData T 
    WHERE PopulationSizeRowNum IN((NumberOfRows + 1)/2, (NumberOfRows + 2)/2) 
    GROUP BY StateID, TimeDimID, ConstructionStatusID 

    SELECT MinRowNum = MIN(SquareMilesRowNum), MaxRowNum = MAX(SquareMilesRowNum), StateID, TimeDimID, ConstructionStatusID, MedianSquareMiles= AVG(SquareMiles) 
    FROM #MedianData T 
    WHERE SquareMilesRowNum IN((NumberOfRows + 1)/2, (NumberOfRows + 2)/2) 
    GROUP BY StateID, TimeDimID, ConstructionStatusID 


    DROP TABLE #MedianData 
    DROP TABLE #TestMedian 

Problem z tym jest to, że zapytania SQL Server wykonuje zarówno z "ROW__NUMBER() OVER ..." sub-queries w seryjnym, a nie równolegle. Więc jeśli mam 10 z tych obliczeń ROW__NUMBER, obliczę je jeden po drugim i dostaję liniowy wzrost, który śmierdzi. Mam system 8-kanałowy 32 GB, na którym uruchamiam to zapytanie i chciałbym trochę równoległości. Próbuję uruchomić tego typu kwerendy na 5.000.000 tabeli wiersza.

Mogę powiedzieć, że robi to, patrząc na plan zapytania i widząc Sorts w tej samej ścieżce wykonania (wyświetlanie XML planu kwerendy nie działałoby naprawdę dobrze na SO).

Moje pytanie brzmi następująco: Jak mogę zmienić to zapytanie, aby zapytania ROW_NUMBER były wykonywane równolegle? Czy istnieje zupełnie inna technika, której mogę użyć do przygotowania danych do wielu obliczeń median?

+0

+1, wystarczy kod, aby spróbować go na moim systemie !! –

+0

+1, ponieważ nie wiedziałem, że można używać klauzul OVER poza funkcjami rankingu - również w SQL 2005, nie mniej. Woot! –

+0

Philip: Dla normalnych funkcji Aggregate, tylko klauzula PARTITION BY, a nie część ORDER BY :-( – RBarryYoung

Odpowiedz

2

Każdy ROW_NUMBER wymaga posortowania pierwszych wierszy. Ponieważ twoje dwa RN-y mają różne warunki ORDER BY, zapytanie musi wygenerować wynik, następnie zamówić go dla pierwszych RN (może być już zamówione), wytworzyć RN, następnie zamówić dla drugiego RN i wytworzyć drugi wynik RN. Po prostu nie ma magicznego pyłu, który może zmaterializować wartość liczby wierszy bez liczenia, gdzie wiersz jest w wymaganej kolejności.

+0

Rozumiem, że nie ma magicznego pyłu dostępnego, istnieje ogólnoświatowy niedobór. :) Wiem, że nie może on ustalić, jaki jest RN bez jego wcześniejszego zamówienia. Jak mogę to skonfigurować, aby zamawiał to na różne sposoby równolegle do skalowania RN? Czy istnieje technika dzielenia go na wiele zapytań, a następnie dołączania do zestawów wyników? Nie jestem żonaty, gdy używam stylu RN, więc każdy konstruktywny pomysł zostałby doceniony. Nie mogę być pierwszą osobą na świecie, która chce pobrać zestaw danych i obliczyć wiele median jednocześnie! Aby to zrobić, dane muszą być sortowane na różne sposoby. – JayRu

+0

Jest naprawdę trudny przy liczbie rzędów powyżej 8 różnych zamówień i przy partycji według wymagań. Nawet w przypadku podzapytań, które * mogą * być paralelizowane, jest mało prawdopodobne. Opcje Paralele są dostępne jako opcja partycjonowania wykonania pojedynczej operacji, takiej jak skanowanie tabeli, a nie do dzielenia wielu różnych podkwerend. Zrewidowałbym wymagania i zrewiduję potrzebę wszystkich rzędowych liczb ... –

+0

Niestety, obliczenie mediany wymaga posortowania danych w kolejności. Parametr Row_Number po prostu mówi, w jaki sposób dane zostały posortowane dla danego pola. Thx za pomoc do tej pory ... – JayRu

2

Nie jestem pewien, czy może to zrównoleglić, ponieważ musi wykonywać skany nie podzielone na partycje (populacja vs. mile kwadratowe). Będą w konflikcie z każdym na dysku, więc musi on wrzucić wszystko do pamięci co najmniej raz, najpierw, a potem może kwalifikować się do równoległości, jeśli jest wystarczająco duży.

W każdym razie, po wykonuje znacząco (40%) szybciej dla mnie:

;WITH cte AS (
    SELECT 
     StateID 
     ,TimeDimID 
     ,ConstructionStatusID 
     ,NumberOfRows = COUNT(*) OVER (PARTITION BY StateID, TimeDimID, ConstructionStatusID) 
     ,PopulationSizeRowNum = ROW_NUMBER() OVER (PARTITION BY StateID, TimeDimID, ConstructionStatusID ORDER BY PopulationSize) 
     ,SquareMilesRowNum = ROW_NUMBER() OVER (PARTITION BY StateID, TimeDimID, ConstructionStatusID ORDER BY SquareMiles) 
     ,PopulationSize 
     ,SquareMiles 
    FROM TestMedian 
) 
, ctePop AS (
    SELECT MinPopNum = MIN(PopulationSizeRowNum) 
    , MaxPopNum = MAX(PopulationSizeRowNum) 
    , StateID, TimeDimID, ConstructionStatusID 
    , MedianPopulationSize= AVG(PopulationSize) 
    FROM cte T 
    WHERE PopulationSizeRowNum IN((NumberOfRows + 1)/2, (NumberOfRows + 2)/2) 
    GROUP BY StateID, TimeDimID, ConstructionStatusID 
) 
, cteSqM AS (
    SELECT MinSqMNum = MIN(SquareMilesRowNum) 
    , MaxSqMNum = MAX(SquareMilesRowNum) 
    , StateID, TimeDimID, ConstructionStatusID 
    , MedianSquareMiles= AVG(SquareMiles) 
    FROM cte T 
    WHERE SquareMilesRowNum IN((NumberOfRows + 1)/2, (NumberOfRows + 2)/2) 
    GROUP BY StateID, TimeDimID, ConstructionStatusID 
) 
SELECT s.StateID, s.TimeDimID, s.ConstructionStatusID 
, MinPopNum, MaxPopNum, MedianPopulationSize 
, MinSqMNum, MaxSqMNum, MedianSquareMiles 
FROM ctePop p 
JOIN cteSqM s ON s.StateID = p.StateID 
    AND s.TimeDimID = p.TimeDimID 
    AND s.ConstructionStatusID = p.ConstructionStatusID 

Również rodzaju powinny same się parallelized raz dostają wystarczająco duża. Będziesz potrzebować co najmniej 100 000 wierszy testowych, zanim to się stanie.


OK, yep, mam równoległość po załadować go na tyle z tym stwierdzeniem:

INSERT INTO TestMedian 
SELECT abs(id)%3,abs(id)%2,abs(id)%5, abs(id), colid * 10000 
    From master.sys.syscolumns, (select top 10 * from master.dbo.spt_values)a 
+0

Thx. Testuję to podejście w moim aktualnym zestawie danych, aby sprawdzić, czy liczba wierszy jest zrównoleglona. Na małym podzbiorze wyglądało obiecująco. – JayRu

1

Niektóre myślenia lateralnego: Jeśli trzeba te dane często i/lub szybko, a dane źródłowe zestaw nie zmienia się często (przy dość wysokich wartościach "często"), czy mógłbyś wstępnie obliczyć te wartości i zapisać je w formie wstępnie zgrupowanej tabeli?

(Tak, to jest demonormalization, ale jeśli trzeba wydajność ponad wszystko inne, to jest warte rozważenia.)

+1

Chciałem powiedzieć "denormalizacja" tam. Szczery. –

+0

Wierzę, że :). Niestety, nie widzę tutaj jeszcze etapu wstępnej agregacji. W tym przykładzie rozmiary populacji są rozmieszczone w całym zestawie wymiarów. Dla każdego zestawu wymiarów muszę znaleźć medianę wielkości populacji. Jedyną preagregacją, jaką mogę wymyślić, jest zastąpienie poszczególnych wymiarów identyfikatorem, tak więc partycjonowanie, grupowanie i łączenie odbywa się na mniejszej liczbie kolumn (może być naprawdę tego warta). – JayRu

Powiązane problemy