2009-09-04 15 views
5

Ich versuche, einige Daten zu berechnen, mehrere Mediane in SQL Server 2008 zu berechnen, aber ich habe ein Leistungsproblem. Im Moment verwende ich diese pattern ([ein anderes Beispiel bottom). Ja, ich verwende kein CTE, aber die Verwendung von einem wird das Problem, das ich sowieso habe, nicht beheben, und die Leistung ist schlecht, weil die row_number-Teilabfragen seriell und nicht parallel ablaufen.Mehrere Row_Number() Aufrufe in einer einzelnen SQL-Abfrage

Hier ist ein vollständiges Beispiel. Unter dem SQL erkläre ich das Problem mehr.

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

Das Problem bei dieser Abfrage ist, dass SQL Server sowohl des ausführt "ROW__NUMBER() OVER ..." Unterabfragen in seriell, nicht parallel. Wenn ich also 10 dieser ROW__NUMBER-Berechnungen habe, berechnet sie sie nacheinander und ich bekomme lineares Wachstum, was stinkt. Ich habe ein 8-Wege-32GB-System, auf dem ich diese Abfrage ausführe, und ich würde etwas Parallelität lieben. Ich versuche, diese Art von Abfrage auf einer 5.000.000 Row-Tabelle auszuführen.

Ich kann es tun, indem ich auf den Abfrageplan schaue und die Sortierungen im selben Ausführungspfad sehe (die Anzeige des XML des Abfrageplans würde auf SO nicht wirklich gut funktionieren).

Meine Frage ist also: Wie kann ich diese Abfrage so ändern, dass die ROW_NUMBER-Abfragen parallel ausgeführt werden? Gibt es eine völlig andere Technik, die ich verwenden kann, um die Daten für mehrere Median-Berechnungen vorzubereiten?

+0

+1, genug, um Code auf meinem System, um zu versuchen !! –

+0

+1, weil ich nicht wusste, dass Sie OVER-Klauseln außerhalb von Ranking-Funktionen verwenden könnten - auch in SQL 2005 nicht weniger. Woot! –

+0

Philip: Für die normalen Aggregatfunktionen sollte nur die PARTITION BY-Klausel, nicht jedoch der ORDER BY-Teil :-( – RBarryYoung

Antwort

2

Für jede ROW_NUMBER müssen die Zeilen zuerst sortiert werden. Da Ihre beiden RNs unterschiedliche ORDER BY-Bedingungen haben, muss die Abfrage das Ergebnis erzeugen, dann für die ersten RNs (es kann bereits orderred sein), die RN erzeugen, dann für die zweite RN bestellen und das zweite RN-Ergebnis erzeugen. Es gibt einfach keinen magischen Pixie-Staub, der einen Zeilennummernwert materialisieren kann, ohne zu zählen, wo die Zeile in der erforderlichen Reihenfolge ist.

+0

Ich verstehe, dass es keinen magischen Pixie Staub gibt, es gibt eine weltweite Knappheit :) Ich weiß, dass es nicht herausfinden kann, was die RN ist, bevor sie es bestellt. Wie kann ich es einrichten, so dass es verschiedene Möglichkeiten parallel zur Berechnung der RN bestellt? Gibt es eine Technik, um es in mehrere Abfragen zu brechen und dann die Ergebnismengen zu verbinden? Ich bin nicht verheiratet mit dem RN-Stil, so dass jede konstruktive Idee geschätzt werden würde. Ich kann nicht die erste Person auf der Welt sein, die eine Datenmenge aufnehmen und mehrere Mediane gleichzeitig effizient berechnen möchte! Um dies zu tun, müssen die Daten auf verschiedene Arten sortiert werden. – JayRu

+0

Ist wirklich hart mit Row_numbers über 8 verschiedenen Bestellungen und mit Partition nach Anforderungen. Selbst mit Unterabfragen, die paralelisiert werden können, ist es unwahrscheinlich. Paralele-Optionen stehen als Option zur Verfügung, um die Ausführung einer einzelnen Operation, z. B. eine Tabellensuche, zu partitionieren und nicht mehrere Unterabfragen zu teilen. Ich würde die Anforderungen nochmals überprüfen und die Notwendigkeit für alle row_numbers überdenken ... –

+0

Leider erfordert die Berechnung eines Medians, dass die Daten in Reihenfolge sortiert werden. Die Row_Number sagt einfach nur, wie diese Daten für ein bestimmtes Feld sortiert wurden. Danke für die Hilfe bis jetzt ... – JayRu

2

Ich bin mir nicht sicher, ob es das parallelisieren kann, weil es nicht partitionierte Scans (mit Bevölkerung vs Quadratmeilen) tun muss. Sie werden mit jedem auf der Festplatte in Konflikt geraten, also muss er alles mindestens einmal in den Speicher bekommen und dann kann es für die Parallelisierung geeignet sein, wenn es groß genug ist.

In jedem Fall führt die folgenden deutlich (40%) für mich schneller:

;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 

Auch sollten die Sorten selbst parallelisiert erhalten, sobald sie groß genug bekommen. Sie brauchen Testzeilen mindestens 100.000 bevor das passieren kann.


OK, yep, ich Parallelität, nachdem ich es laden genug mit dieser Aussage:

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. Ich teste diesen Ansatz jetzt an meinem tatsächlichen Datensatz, um zu sehen, ob die Zeilenanzahl parallelisiert ist. Auf einer kleinen Untergruppe sah es vielversprechend aus. – JayRu

1

Einige Querdenken: Wenn Sie diese Daten oft und/oder schnell, und die zu Grunde liegenden Daten set ändert sich nicht häufig (für relativ hohe Werte von "häufig"), könnten Sie einen dieser Werte vorberechnen und in einer Form einer voraggregierten Tabelle speichern?

(Yep, das ist demonormalization, aber wenn Sie die Leistung über alles andere brauchen, dann ist es eine Überlegung wert.)

+1

Ich wollte dort "Denormalisierung" sagen. Ehrlich. –

+0

Ich glaube dir :). Leider sehe ich hier jedoch keinen Voraggregationsschritt. In diesem Beispiel sind die Populationsgrößen über eine Reihe von Dimensionen verteilt. Für jede Menge von Dimensionen muss ich den Medianwert der Populationsgröße finden. Die einzige Voraggregation, die ich mir vorstellen kann, besteht darin, die einzelnen Dimensionen durch einen Bezeichner zu ersetzen, damit die Partitionierung, Gruppierung und Verknüpfung in weniger Spalten erfolgt (was sich wirklich lohnt). – JayRu