2009-09-04 8 views
5

Estoy intentando configurar algunos datos para calcular varias medianas en SQL Server 2008, pero tengo un problema de rendimiento. En este momento, estoy usando este pattern ([otro ejemplo bottom). Sí, no estoy usando un CTE, pero usar uno no solucionará el problema que estoy teniendo de todos modos y el rendimiento es pobre porque las subconsultas row_numeros se ejecutan en serie, no en paralelo.Multiple Row_Number() Llamadas en una única consulta SQL

Aquí hay un ejemplo completo. Debajo del SQL explico más el problema.

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

El problema con esta consulta es que SQL Server ejecuta tanto de la "ROW__NUMBER() OVER ..." sub-consultas en serie, no en paralelo. Entonces, si tengo 10 de estos cálculos de ROW__NUMBER, los calculará uno después del otro y obtendré un crecimiento lineal, que huele mal. Tengo un sistema de 32 GB de 8 vías Estoy ejecutando esta consulta y me gustaría un poco de paralelismo. Estoy intentando ejecutar este tipo de consulta en una tabla de filas de 5,000,000.

Esto se puede ver mirando el plan de consulta y viendo los Sorts en la misma ruta de ejecución (mostrar el XML del plan de consulta no funcionaría muy bien en SO).

Entonces mi pregunta es esta: ¿Cómo puedo alterar esta consulta para que las consultas ROW_NUMBER se ejecuten en paralelo? ¿Existe una técnica completamente diferente que pueda usar para preparar los datos para cálculos de mediana múltiple?

+0

1, código suficiente para probarlo en mi sistema !! –

+0

+1, porque no sabía que podía usar las cláusulas OVER fuera de las funciones de clasificación, tampoco en SQL 2005. ¡Woot! –

+0

Philip: Para las funciones Aggregate normales, solo la cláusula PARTITION BY, no la parte ORDER BY :-( – RBarryYoung

Respuesta

2

Cada ROW_NUMBER requiere que las filas se ordenen primero. Dado que sus dos RN tienen diferentes condiciones ORDER BY, la consulta debe producir el resultado, luego ordenarlo para primeros RN (puede ordenarse ya), producir el RN, luego ordenarlo para el segundo RN y producir el segundo resultado de RN. Simplemente no hay polvo mágico de duendes que pueda materializar el valor de un número de fila sin contar dónde está la fila en el orden requerido.

+0

Entiendo que no hay polvo mágico de duendes disponible, hay una escasez en todo el mundo :) Sé que no se puede entender lo que el RN es sin pedirlo por primera vez. ¿Cómo puedo configurarlo para que ordene diferentes formas en paralelo para calcular la RN? ¿Existe una técnica para dividirla en múltiples consultas y luego unir los conjuntos de resultados? No estoy casado con el uso del estilo RN, por lo que cualquier idea constructiva sería apreciada. No puedo ser la primera persona en el mundo que quiere tomar un conjunto de datos y calcular múltiples medianas a la vez de manera eficiente. Para hacer eso, los datos deben ordenarse de diferentes maneras. – JayRu

+0

Es realmente difícil con row_numbers en más de 8 pedidos diferentes y con partición por requisitos. Incluso con subconsultas que * pueden * ser paralelizadas, es poco probable que lo hagan. Las opciones de Paralele están disponibles como una opción para particionar la ejecución de una sola operación, como un escaneo de tabla, no para dividir múltiples subconsultas diferentes. Revisaría los requisitos y reconsideraría la necesidad de todos los row_numbers ... –

+0

Desafortunadamente, calcular una mediana requiere que los datos estén ordenados en orden. Row_Number simplemente te dice cómo se ordenaron estos datos para un campo determinado. Thx por la ayuda hasta ahora ... – JayRu

2

No estoy seguro de que pueda paralelizar esto, porque necesita hacer escaneos no particionados (población vs millas cuadradas). Conflictarán con cada uno en el disco, por lo que tiene que tener todo en la memoria al menos una vez, primero y luego podría ser elegible para la paralelización, si es lo suficientemente grande.

En cualquier caso, el siguiente realiza de forma significativa (40%) más rápido para mí:

;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 

Además, los mismos tipos hay que dejarse paralelizados una vez que llegan lo suficientemente grande. Sin embargo, necesitará filas de prueba al menos 100.000 antes de que eso suceda.


OK, sí, consigo paralelismo después de lo cargo suficiente con esta declaración:

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. Estoy probando este enfoque en mi conjunto de datos actual para ver si los recuentos de filas están paralelizados. En un pequeño subconjunto parecía prometedor. – JayRu

1

Algunos pensamiento lateral: Si necesita estos datos a menudo y/o rápidamente, y los datos subyacentes el conjunto no cambia con frecuencia (para valores razonablemente altos de "con frecuencia"), ¿podría precalcular cualquiera de estos valores y almacenarlos en algún tipo de tabla agregada previamente?

(Sí, esto es demonormalization, pero si usted necesita un rendimiento por encima de todo, vale la pena considerar.)

+1

Quise decir "desnormalización" allí. Honesto. –

+0

Te creo :). Desafortunadamente, no veo un paso previo a la agregación aquí, sin embargo. En este ejemplo, los tamaños de población se distribuyen en un conjunto de dimensiones. Para cada conjunto de dimensiones, necesito encontrar el valor mediano del tamaño de la población. La única preagregación en la que puedo pensar es reemplazar las dimensiones individuales con un identificador para que la partición, la agrupación y la unión se realicen en menos columnas (podría valer la pena realmente). – JayRu

Cuestiones relacionadas