Нашли ошибку или опечатку? Выделите текст и нажмите

Поменять цветовую

гамму сайта?

Поменять
Обновления сайта
и новые разделы

Рекомендовать в Google +1

Оптимизация аналитических функций в T-SQL

85
Исходник базы данных

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

Оптимизация функций распределения рангов

Функции распределения рангов это PERCENT_RANK и CUME_DIST. Как вы помните, функция PERCENT_RANK вычисляется как (rk - 1) / (nr - 1), где rk — ранг строки, а nr — число строк в секции. Вычисление строк в секции предусматривает использование итератора Table Spool, о чем говорилось ранее. Для вычисления ранга применяется итератор Sequence Project. План, вычисляющий PERCENT_RANK, просто включает оба итератора. В качестве примера посмотрите на такой запрос:

SELECT testid, studentid, score,
  PERCENT_RANK() OVER(PARTITION BY testid ORDER BY score) AS percentrank
FROM Stats.Scores;

План этого запроса:

План для функции PERCENT_RANK

В первой части данные считываются и сегментируются по testid. Затем одна по одной строки секции записываются во временную таблицу, которая затем считывается дважды — сначала для вычисления числа строк (nr), а затем для получения строк подробных данных. После этого строки подробных данных их агрегаты соединяются. Затем итераторы Segment и Sequence Project используются для вычисления ранга (rk). Наконец, итератор Compute Scalar вычисляет результат функции PERCENT_RANK как (rk - 1)/(nr - 1).

Что касается CUME_DIST, то эта функция вычисляет np/nr; где nr — то же, что и раньше (число строк в секции), а np — число строк, которые предшествуют или находятся на одном уровне с текущей строкой. В качестве примера посмотрите на такой запрос:

SELECT testid, studentid, score,
  CUME_DIST()    OVER(PARTITION BY testid ORDER BY score) AS cumedist
FROM Stats.Scores;

План этого запроса:

План выполнения функции CUME_DIST

Первая часть, которая вычисляет nr, совпадает с планом PERCENT_RANK. Вторая часть чуть сложнее. Для вычисления np сервер SQL Server должен забежать вперед текущей строки. Также в плане есть два итератора Segment — первый сегментирует строки по элементу сегментирования (testid), а второй сегментирует по элементам сегментирования и упорядочения (testid и score). Однако вместо итератора Sequence Project используются новые итераторы Window Spool и Stream Aggregate iterators в «быстром» режиме для вычисления числа строк, которые предшествуют или находятся на одном уровне с текущей строкой. Наконец итератор Compute Scalar вычисляет значение CUME_DIST как np/nr.

Оптимизация функций обратного распределения

Оптимизация функций обратного распределения PERCENTILE_CONT и PERCENTILE_DISC сложнее, чем функций распределения рангов. Я начну с PERCENTILE_DISC. Посмотрите на следующий запрос:

SELECT testid, score,
  PERCENTILE_DISC(0.5) WITHIN GROUP(ORDER BY score) OVER(PARTITION BY testid) AS percentiledisc
FROM Stats.Scores;

План этого запроса:

План для функции PERCENTILE_DISC

План выполняет следующие операции:

  • Первый набор из восьми итераторов в нижней правой части отвечает за вычисление числа строк для каждой строки в соответствующей секции testid. В плане это число называется PartitionSizeN.

  • Последующие итераторы Segment и Sequence Project вычисляют номер строки в секции testid при упорядочении по оценкам. В плане этот номер называется RowNumberN.

  • Первый итератор Compute Scalar вычисляет номер строки, содержащей процентиль данной секции. При этом применяется такое выражение (упрощено): CeilingTargetRowN = ceiling(@pct * PartitionSize1013), где @pct является входным значением процента для функции (в нашем случае 0,5).

  • Второй итератор Compute Scalar вычисляет выражение, которое называется PartialSumN. Это выражение возвращает нужный процентиль, если номер текущего столбца (RowNumberN) равен значению MIN(1, CeilingTargetRowN), в противном случае возвращается NULL. Проще говоря, PartialSumN принимает полезное значение, только если является нужным процентилем, а в противном случае возвращает NULL.

  • В последней части надо извлечь из каждой секции не равный NULL процентиль (PartialSumN) и связать его со строками подробных данных. Для этого в плане снова используется итератор Table Spool. План сегментирует данные по testid и сохраняется строки текущей секции в спуле — по одной секции за раз. Затем план два раза считывает спул — сначала для получение не равных NULL процентилей с помощью агрегата MAX(PartialSumN) (результат называется PercentileResultN), а затем для получения подробных данных. После этого план соединяет подробные данные и агрегаты.

  • Последняя часть - проверка размера секции. Если он нулевой, возвращается NULL, а в противном случае — PercentileResultN.

Что касается функции PERCENTILE_CONT, для обсуждения плана я воспользуюсь следующим запросом:

SELECT testid, score,
  PERCENTILE_CONT(0.5) WITHIN GROUP(ORDER BY score) OVER(PARTITION BY testid) AS percentilecont
FROM Stats.Scores;

План этого запроса показан на рисунке ниже:

План для функции PERCENTILE_CONT

Как видите, общая структура плана похожа на функцию PERCENTILE_DISC. Однако есть ряд различий. Одно отличие заключается в итераторах Compute Scalar, которые находятся справа, сразу за вычислением числа строк, а второе отличие — второй итератор Stream Aggregate. Я начну с итераторов Compute Scalar:

  • Первый итератор Compute Scalar вычисляется номер целевой строки, включая дробную часть: TargetRowN = 1 + @pct * (PartitionSizeN - 1).

  • Второй итератор Compute Scalar вычисляет минимум и максимум для TargetRowN, присваивая им соответственно имена FloorTargetRowN и CeilingTargetRowN.

  • Третий итератор Compute Scalar вычисляет выражение, которое называется PartialSumN. Если интерполяция не требуется, PartialSumN возвращает значение процентиля, если текущая строка является целевой, или 0 в противном случае. Если интерполяция нужна, PartialSumN возвращает часть интерполированного значения, если текущая строка является минимумом или максимумом для целевой строки. В противном случае возвращается ноль.

Другое отличие плана от PERCENTILE_DISC заключается в том, что в итераторе Stream Aggregate используется агрегат SUM, а не MAX. Причина в том, что на этот раз может потребоваться более одного элемента, а также в том, что может возникнуть необходимость суммировать части, используемые для вычисления интерполированного значения.

Итак, в этих нескольких статьях я рассказал об оптимизации оконных функций SQL Server. Мне пришлось представить много подробной информации, но я надеюсь, что вы в ней не утонули. Особый интерес представляют новый оптимизированный итератор Window Spool iterator и улучшенный итератор Stream Aggregate, а также оптимизированные таблицы в памяти, которые в этих итераторах используются. Остались некоторые странности в оптимизации, в частности те, что относятся к по-видимому ненужным сортировкам, но я надеюсь, что ситуация улучшится в будущем. Достичь совершенства невозможно, но нужно к нему стремиться. В любом случае, в сравнении с другими методами получения аналогичных результатов, SQL Server обеспечивает очень эффективную работу оконных функций.

Далее мы поговорим о практическом использовании оконных функций и в некоторых случаях сравним полученные решения с более традиционными методами, чтобы увидеть, насколько более эффективны новые функции и функциональность.

Пройди тесты
x

Чат для изучающих C# Подписывайтесь!