多个Row_Number()调用单个SQL查询

时间:2009-09-04 16:43:15

标签: sql sql-server tsql

我正在尝试设置一些数据来计算SQL Server 2008中的多个中位数,但我遇到了性能问题。现在,我正在使用这个pattern([另一个例子bottom)。是的,我没有使用CTE,但使用一个CTE无法解决我遇到的问题而且性能很差,因为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行表上运行此类查询。

我可以通过查看查询计划并在同一个执行路径中查看Sorts来告诉它这样做(显示查询计划的XML在SO上不能很好地工作)。

所以我的问题是:如何更改此查询以便并行执行ROW_NUMBER查询?是否有一种完全不同的技术可用于为多个中值计算准备数据?

3 个答案:

答案 0 :(得分:2)

每个ROW_NUMBER都要求先排序行。由于您的两个RN具有不同的ORDER BY条件,因此查询必须生成结果,然后对第一个RN进行排序(它可能已经被排序),生成RN,然后为第二个RN订购它并生成第二个RN结果。根本没有任何神奇的小精灵粉尘可以实现行号值而不计算行在所需顺序中的位置。

答案 1 :(得分:2)

我不确定它是否可以并行化,因为它需要进行非分区(wrt population vs square miles)扫描。它们会与磁盘上的每个磁盘发生冲突,因此它必须至少将所有内容都放入内存中,首先它可能有资格进行并行化,如果它足够大的话。

无论如何,以下内容对我来说表现得更快(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行。


好的,是的,在我用这句话加载之后我得到了并行性:

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

答案 2 :(得分:1)

一些横向思考:如果您经常和/或快速地需要这些数据,并且基础数据集不会经常更改(对于“频繁”的合理高值),您是否可以预先计算这些值中的任何值并将其存储在某种形式的预聚合表?

(是的,这是规范化,但如果你需要表现超过一切,那就值得考虑。)