2009-09-04 6 views
5

Я пытаюсь установить некоторые данные для вычисления нескольких медианов в SQL Server 2008, но у меня проблема с производительностью. Прямо сейчас, я использую это pattern ([еще один пример bottom). Да, я не использую CTE, но использование одного из них не будет устранять проблему, которая у меня есть, и производительность невысока, потому что подзапросы row_number выполняются серийно, а не параллельно.Несколько запросов Row_Number() в одном запросе SQL

Вот полный пример. Ниже 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 ...» подзапросы в последовательный, а не параллельно. Поэтому, если у меня будет 10 из этих вычислений ROW__NUMBER, они будут вычислять их один за другим, и я получаю линейный рост, который воняет. У меня есть 8-полосная 32-гигабайтная система. Я запускаю этот запрос, и мне бы хотелось, чтобы какой-то параллелизм. Я пытаюсь запустить этот тип запроса в таблице из 5 000 000 строк.

Я могу сказать, что это можно сделать, посмотрев план запроса и увидев Сорта в одном и том же пути выполнения (отображение XML-плана запроса не будет работать хорошо на SO).

Итак, мой вопрос заключается в следующем: как я могу изменить этот запрос так, чтобы запросы ROW_NUMBER выполнялись параллельно? Существует ли совершенно другой метод, который я могу использовать для подготовки данных для нескольких медианных вычислений?

+0

+1, достаточно код, чтобы попробовать его на моей системе !! –

+0

+1, потому что я не знал, что вы можете использовать предложения OVER вне функций ранжирования - и в SQL 2005 тоже не меньше. Woot! –

+0

Philip: для обычных функций агрегации только предложение PARTITION BY, но не часть ORDER BY :-( – RBarryYoung

ответ

2

Для каждого ROW_NUMBER требуется сортировка строк в первую очередь. Поскольку ваши два RN имеют разные условия ORDER BY, запрос должен давать результат, затем заказывать его для первых RN (он может быть уже упорядочен), создать RN, затем заказать его для второго RN и произвести второй результат RN. Там просто нет волшебной пыли pixie, которая может материализовать значение номера строки без учета того, где строка находится в необходимом порядке.

+0

Я понимаю, что нет никакой волшебной пыли пыльцы, есть всемирная нехватка. :) Я знаю, что он не может понять, что RN не имеет, сначала заказывая его. Как я могу настроить его, чтобы он заказывал разные способы параллельно вычислять RN? Есть ли способ разбить его на несколько запросов, а затем присоединиться к наборам результатов? Я не женат на использовании стиля RN, поэтому любая конструктивная идея будет оценена по достоинству. Я не могу быть первым человеком в мире, который хочет взять набор данных и вычислить сразу несколько медианов! Для этого данные должны быть отсортированы по-разному. – JayRu

+0

Действительно сложно с row_numbers более 8 разных заказов и с разделом по требованиям. Даже с подзапросами, которые * могут * быть парализованными, вряд ли они будут. Доступны опции Paralele, позволяющие разделять выполнение одной операции, например, сканирование таблицы, а не для разделения нескольких разных подзапросов. Я бы пересмотрел требования и пересмотрел необходимость во всех row_numbers ... –

+0

К сожалению, для вычисления медианы требуется сортировка данных по порядку. В Row_Number просто указывается, как эти данные были отсортированы для заданного поля. Thx за помощь до сих пор ... – JayRu

2

Я не уверен, что он может распараллелить это, потому что ему нужно делать нераспределенные (опросы населения против квадратных миль) сканирование. Они будут конфликтовать с каждым на диске, поэтому он должен сначала получить все в памяти, по крайней мере, один раз, а затем он может иметь право распараллеливать, если он достаточно большой.

В любом случае, следующий выполняет значительно (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 
+0

Thx. Я проверяю этот подход на своих фактических данных, установленных теперь, чтобы увидеть, распараллеливаются ли строки. На небольшом подмножестве это выглядело многообещающим. – JayRu

1

Некоторые латерального мышления: Если вам нужны эти данные часто и/или быстро, и лежащие в основе данных набор не изменяется часто (для разумно высоких значений «часто»), можете ли вы предварительно рассчитать любое из этих значений и сохранить их в какой-либо форме предварительно агрегированной таблицы?

(Да, это demonormalization, но если вам нужна производительность над всем остальным, это стоит рассматривать.)

+1

Я хотел сказать «денормализацию». Честный. –

+0

Я тебе верю :). К сожалению, я не вижу здесь стадии предварительной агрегации. В этом примере размеры населения распределены по множеству измерений. Для каждого набора измерений мне нужно найти медианное значение размера популяции. Единственная предварительная агрегация, о которой я могу думать, - это заменить отдельные измерения идентификатором, чтобы разбиение, группировка и объединение выполнялись на меньшем количестве столбцов (возможно, это действительно того стоило). – JayRu

Смежные вопросы