2009-09-04 32 views
5

我試圖設置一些數據來計算SQL Server 2008中的多箇中值,但我遇到了性能問題。現在,我使用pattern([另一個示例爲bottom)。是的,我沒有使用CTE,但使用一個不會解決我反正的問題,性能很差,因爲row_number子查詢以串行方式運行,而不是並行運行。在單個SQL查詢中調用多個Row_Number()函數

下面是一個完整的例子。在SQL下面,我更多地解釋這個問題。

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

與此查詢的問題是,SQL Server執行雙方的「ROW__NUMBER()OVER ......」子查詢串行,不能並行。所以如果我有這些ROW__NUMBER計算中的10個,它會一個接一個地計算出它們,並且我得到線性增長,這很糟糕。我有一個8路32GB系統,我正在運行這個查詢,我會喜歡一些並行性。我試圖在5,000,000行的表上運行這種類型的查詢。

我可以通過查看查詢計劃並在相同的執行路徑中查看排序(顯示查詢計劃的XML在SO上無法正常工作)來告訴其執行此操作。

所以我的問題是這樣的:我如何改變這個查詢,以便ROW_NUMBER查詢是並行執行的?是否有一種完全不同的技術可用於準備多箇中位數計算的數據?

+0

+1,足夠的代碼試試我的系統上! – 2009-09-04 17:19:28

+0

+1,因爲我不知道你可以在排名函數之外使用OVER子句 - 在SQL 2005中也是如此。活泉! – 2009-09-04 18:13:03

+0

Philip:對於普通的集合函數,儘管只有PARTITION BY子句,而不是ORDER BY部分:-( – RBarryYoung 2009-09-04 19:30:42

回答

2

每個ROW_NUMBER需要先對行進行排序。由於你的兩個RN具有不同的ORDER BY條件,所以查詢必須產生結果,然後爲第一個RN(它可能已經訂購了)命令它,產生RN,然後爲第二個RN命令它併產生第二個RN結果。根本沒有任何魔術精靈可以實現行號值,而不必計算該行在所需順序中的位置。

+0

據我所知,沒有可用的魔法精靈塵埃,世界各地的短缺。:) 我知道,它無法弄清楚RN是不是先訂購它。我怎樣才能設置它,讓它以不同的方式並行計算RN?有沒有一種技術可以將其分解爲多個查詢,然後加入結果集? 我沒有結婚使用RN風格,所以任何建設性的想法,將不勝感激。我不能成爲世界上第一個想要獲取一組數據並一次高效計算多箇中位數的人!爲此,數據必須以不同的方式排序。 – JayRu 2009-09-04 17:07:54

+0

對8個不同順序的row_numbers和需求分區確實很難。即使使用子查詢可能會被平分,也不太可能。 Paralele選項可作爲分區執行單個操作的選項,如表掃描,而不用於拆分多個不同的子查詢。我將重新審視這些要求並重新考慮是否需要所有row_number ... – 2009-09-04 17:51:15

+0

不幸的是,計算中位數需要按順序對數據進行排序。 Row_Number只是簡單地告訴你如何爲給定的字段排序數據。 Thx幫助到目前爲止... – JayRu 2009-09-04 19:41:26

2

我不確定它是否可以並行化,因爲它需要做非分區(wrt人口vs平方英里)掃描。它們將與磁盤上的每個磁盤發生衝突,因此它必須至少將所有內容都存儲到內存中,然後纔可以進行並行化(如果足夠大)。

在任何情況下,下面的(40%)對我更快顯著執行:

;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 

而且,自己應該得到的並行各種各樣一旦他們變得足夠大。儘管如此,您還需要至少100,000個測試行。


OK,是的,我得到的並行後,我這個說法加載它足夠:

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。我現在在我的實際數據集上測試這種方法,以查看行數是否並行化。在一小部分看起來很有希望。 – JayRu 2009-09-04 19:46:15

1

一些橫向思維:如果你經常和/或很快需要這個數據,和基礎數據設置不會頻繁變化(對於「頻繁」的相當高的值),您是否可以預先計算這些值中的任何一個並將它們存儲在某種形式的預彙總表中?

(是的,這是demonormalization,但如果你需要在一切的表現,這是值得考慮的。)

+1

我的意思是在那裏說「反規範化」。誠實。 – 2009-09-04 18:17:16

+0

我相信你:)。不幸的是,我沒有在這裏看到一個預先聚合的步驟。在這個例子中,人口規模分佈在一組維度上。對於每一組維度,我需要找到人口規模的中值。我能想到的唯一預集合是用一個標識符替換單個維度,這樣分區,分組和連接就可以在更少的列上完成(可能真的值得)。 – JayRu 2009-09-04 19:54:08