Использование агрегатных функций осуществляется на вкладке

Группировка

Агрегатные функции

Агрегатные функции выполняют вычисления над значениями в наборе строк. В T-SQL имеются следующие агрегатные функции:

AVG : находит среднее значение

SUM : находит сумму значений

MIN : находит наименьшее значение

MAX : находит наибольшее значение

COUNT : находит количество строк в запросе

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

Все агрегатные функции за исключением COUNT(*) игнорируют значения NULL.

Функция Avg возвращает среднее значение на диапазоне значений столбца таблицы.

Пусть в базе данных у нас есть таблица товаров Products, которая описывается следующими выражениями:

Найдем среднюю цену товаров из базы данных:

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

80

Также мы можем применить фильтрацию. Например, найти среднюю цену для товаров какого-то определенного производителя:

И, кроме того, мы можем находить среднее значение для более сложных выражений. Например, найдем среднюю сумму всех товаров, учитывая их количество:

Count

Функция Count вычисляет количество строк в выборке. Есть две формы этой функции. Первая форма COUNT(*) подсчитывает число строк в выборке:

81

Вторая форма функции вычисляет количество строк по определенному столбцу, при этом строки со значениями NULL игнорируются:

Min и Max

Функции Min и Max возвращают соответственно минимальное и максимальное значение по столбцу. Например, найдем минимальную цену среди товаров:

Поиск максимальной цены:

Данные функции также игнорируют значения NULL и не учитывают их при подсчете.

Функция Sum вычисляет сумму значений столбца. Например, подсчитаем общее количество товаров:

82

Также вместо имени столбца может передаваться вычисляемое выражение. Например, найдем общую стоимость всех имеющихся товаров:

All и Distinct

Так как этот оператор неявно подразумевается при отсутствии DISTINCT, то его можно не указывать.

Источник

Язык SQL. Формирование запросов к базе данных

Применение агрегатных функций и вложенных запросов в операторе выбора

В SQL добавлены дополнительные функции, которые позволяют вычислять обобщенные групповые значения. Для применения агрегатных функций предполагается предварительная операция группировки. В чем состоит суть операции группировки? При группировке все множество кортежей отношения разбивается на группы, в которых собираются кортежи, имеющие одинаковые значения атрибутов, которые заданы в списке группировки.

Таблица 5.7. Агрегатные функции
Функция Результат
COUNT Количество строк или непустых значений полей, которые выбрал запрос
SUM Сумма всех выбранных значений данного поля
AVG Среднеарифметическое значение всех выбранных значений данного поля
MIN Наименьшее из всех выбранных значений данного поля
MAX Наибольшее из всех выбранных значений данного поля
R1
ФИО Дисциплина Оценка
Группа 1 Петров Ф. И. Базы данных 5
Сидоров К. А. Базы данных 4
Миронов А. В. Базы данных 2
Степанова К. Е. Базы данных 2
Крылова Т. С. Базы данных 5
Владимиров В. А. Базы данных 5
Группа 2 Сидоров К. А. Теория информации 4
Степанова К. Е. Теория информации 2
Крылова Т. С. Теория информации 5
Миронов А. В. Теория информации Null
Группа 3 Трофимов П. А. Сети и телекоммуникации 4
Иванова Е. А. Сети и телекоммуникации 5
Уткина Н. В. Сети и телекоммуникации 5
Группа 4 Владимиров В. А. Английский язык 4
Трофимов П. А. Английский язык 5
Иванова Е. А. Английский язык 3
Петров Ф. И. Английский язык 5

Например, можно вычислить количество студентов, сдававших экзамены по каждой дисциплине. Для этого надо выполнить запрос с группировкой по полю «Дисциплина» и вывести в качестве результата название дисциплины и количество строк в группе по данной дисциплине. Применение символа * в качестве аргумента функции COUNT означает подсчет всех строк в группе.

Дисциплина COUNT(*)
Базы данных 6
Теория информации 4
Сети и телекоммуникации 3
Английский язык 4

Если же мы хотим сосчитать количество сдавших экзамен по какой-либо дисциплине, то нам необходимо исключить неопределенные значения из исходного отношения перед группировкой. В этом случае запрос будет выглядеть следующим образом:

Дисциплина COUNT(*)
Базы данных 6
Теория информации 3
Сети и телекоммуникации 3
Английский язык 4

В этом случае строка со студентом

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

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

Обратившись снова к базе данных «Сессия» (таблицы R1, R2, R3 ), найдем количество успешно сданных экзаменов:

Дисциплина COUNT(*) AVR (Oцeнкa)
Базы данных 6 3.83
Теория информации 3 3.67
Сети и телекоммуникации 3 4.66
Английский язык 4 4.25

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

Правильной командой будет следующая:

Следующая команда будет запрещена:

Смысл данного запроса следующий: найти сумму остатков по каждому филиалу счетов, открытых 27 декабря 1999 года.

Источник

Использование агрегатных функций

Применимо к: yesSQL Server Analysis Services noAzure Analysis Services noPower BI Premium

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

В службах Analysis Services каждая созданная мера поддерживается функцией агрегирования, определяющей операцию меры. Предопределенные типы агрегирования включают Sum, Min, Max, Count, Distinct Count и некоторые другие более специализированные функции. Кроме того, если необходимы агрегаты на основании сложных или пользовательских формул, можно создавать вычисления многомерных выражений вместо готовых функций агрегирования. Например, для определения меры для процентного значения следует использовать многомерное выражение с вычисляемой мерой. См. статью Инструкция CREATE MEMBER (многомерные выражения).

Мерам, которые создаются с помощью мастера кубов, назначается тип агрегирования в качестве части определения меры. Тип агрегирования — всегда Sum, если исходный столбец содержит числовые данные. Sum назначается независимо от типа данных исходного столбца. Например, если используется мастер кубов для создания мер и извлечены все столбцы из таблицы фактов, все результирующие меры будут иметь агрегат Sum, даже если источник — столбец даты и времени. Всегда проверяйте предопределенный метод агрегирования мер, созданных с помощью мастера, чтобы убедиться в том, что функция подходит.

Можно назначить или изменить метод агрегирования в любом определения куба через SQL Server Data Tools — Business Intelligenceили с помощью многомерных выражений. Дополнительные инструкции см. в статье Создание мер и групп мер в многомерных моделях или Aggregate (многомерные выражения).

Агрегатные функции

Службы Analysis Services есть функции для агрегирования мер по измерениям, содержащимся в группах мер. Аддитивность агрегата определяет, как осуществляется статистическое вычисление меры по всем измерениям в кубе. Статистические функции подразделяются на три уровня аддитивности.

Аддитивный
Аддитивная мера, также называемая полностью аддитивной мерой, может быть статистически вычислена вдоль всех измерений в кубе, которые включены в группу мер, содержащую меру, без ограничений.

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

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

Дополнительные сведения об измерениях счетов см. в разделе Создание учетной записи Finance с измерением типа «родитель-потомок». AverageOfChildren Полуаддитивная Вычисляет среднее значений всех непустых дочерних элементов. FirstChild Полуаддитивная Получает значение первого дочернего элемента. LastChild Полуаддитивная Получает значение последнего дочернего элемента. FirstNonEmpty Полуаддитивная Получает значение первого непустого дочернего элемента. LastNonEmpty Полуаддитивная Получает значение последнего непустого дочернего элемента.

About Distinct Count Measures

Меры числа различных объектов обычно используются, чтобы определить, сколько различных элементов другого измерения самого низкого уровня приходится на каждый элемент некоторого измерения в таблице фактов. Например, в кубе «Продажи», как много различной продукции было приобретено каждым покупателем или группой покупателей. (Т. е. сколько различных элементов измерения «Продукция» самого низкого уровня приходится на каждый элемент измерения «Заказчики» в таблице фактов.) Или, например, в кубе Internet Site Visits: как много разных сайтов приходится на каждого посетителя или группу посетителей? (Т. е. сколько различных элементов измерения «Страницы» самого низкого уровня приходится на каждый элемент измерения «Посетители сайта» в таблице фактов.) В каждом из этих примеров элементы самого низкого уровня вторых измерений подсчитаны как мера числа различных объектов.

Этот вид анализа может не ограничиваться двумя измерениями. Мера числа различных объектов может быть разделена и срезана любыми сочетаниями измерений в кубе, включая измерение, которое содержит подсчитываемые элементы.

Мера числа различных объектов, которая подсчитывает элементы, основана на внешнем ключевом столбце в таблице фактов. (Т. е. свойство меры Исходный столбец определяет этот столбец.) Этот столбец соединяет столбец таблицы измерения, который определяет элементы, подсчитываемые мерой числа различных объектов.

Источник

Пользовательские агрегатные и оконные функции в PostgreSQL и Oracle

В этой статье мы посмотрим, как в двух системах создавать пользовательские агрегатные и оконные (в терминологии Oracle — аналитические) функции. Несмотря на различия в синтаксисе и в целом в подходе к расширяемости, механизм этих функций очень похож. Но и различия тоже имеются.

Надо признать, что собственные агрегатные и оконные функции встречается довольно редко. Оконные функции вообще по каким-то причинам традиционно относят к разряду «продвинутого» SQL и считают сложными для понимания и освоения. Тут бы разобраться с теми функциями, которые уже имеются в СУБД!

Зачем тогда вообще вникать в этот вопрос? Могу назвать несколько причин:

Агрегатные функции

Будем двигаться от простого к сложному, переключаясь между PostgreSQL и Oracle.

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

Итак, нам потребуется четыре составляющие:

PostgreSQL

Для хранения состояния нужно выбрать подходящий тип данных. Можно взять стандартный, а можно определить свой. Для функции, вычисляющей среднее, нужно отдельно суммировать значения и отдельно подсчитывать их количество. Поэтому создадим свой составной тип с двумя полями:

CREATE TYPE average_state AS (
accum numeric,
qty numeric
);

Теперь определим функцию для обработки очередного значения. В PostgreSQL она называется функцией перехода:

Кроме этого, мы выводим (RAISE NOTICE) параметры функции — это позволит нам увидеть, как выполняется работа. Старый добрый отладочный PRINT, нет ничего тебя лучше.

Следующая функция — возвращение финального значения:

«Финт ушами» с функцией trim нужен исключительно для аккуратности вывода: таким образом мы избавляемся от незначащих нулей, которые иначе будут загромождать экран и мешать восприятию. Примерно вот так:

SELECT 1::numeric / 2::numeric;
?column?
————————
0.50000000000000000000
(1 row)

В реальной жизни эти фокусы, конечно, не нужны.

И, наконец, определяем собственно агрегатную функцию. Для этого используется специальная команда CREATE AGGREGATE:

Можно пробовать. Почти все примеры в этой статье будут использовать простую таблицу с пятью строками: раз, два, три, четыре, пять. Таблицу создаем на лету функцией generate_series, незаменимым помощником генерации тестовых данных:

SELECT average(g.x) FROM generate_series(1,5) AS g(x);
NOTICE: 0(0) + 1
NOTICE: 1(1) + 2
NOTICE: 3(2) + 3
NOTICE: 6(3) + 4
NOTICE: 10(4) + 5
NOTICE: = 15(5)
average
———
3
(1 row)

Результат верный, а вывод функций позволяет проследить ход выполнения:

SELECT average(g.x) FROM generate_series(1,0) AS g(x);
NOTICE: = 0(0)
average
———

Oracle

В Oracle вся расширяемость обеспечивается механизмом Data Cartridge. Говоря по-простому, нам потребуется создать объектный тип, реализующий необходимый для агрегации интерфейс. Контекст естественным образом представляется атрибутами этого объекта.

CREATE OR REPLACE TYPE AverageImpl AS OBJECT(
accum number,
qty number,
STATIC FUNCTION ODCIAggregateInitialize (actx IN OUT AverageImpl)
RETURN number,
MEMBER FUNCTION ODCIAggregateIterate (self IN OUT AverageImpl, val IN number
RETURN number,
MEMBER FUNCTION ODCIAggregateMerge (self IN OUT AverageImpl, ctx2 IN AverageImpl)
RETURN number,
MEMBER FUNCTION ODCIAggregateTerminate (self IN OUT AverageImpl, returnValue OUT number, flags IN number)
RETURN number
);
/

Начальное значение контекста определяется здесь не константой, а отдельной (статической, то есть не привязанной к конкретному экземпляру объекта) функцией ODCIAggregateInitialize.

Функция, вызываемая для каждой строки — это ODCIAggregateIterate.

Результат возвращает функция ODCIAggregateTerminate, и ей, заметьте, передаются некие флаги, с которыми мы разберемся чуть позже.

Интерфейс включает еще одну обязательную функцию: ODCIAggregateMerge. Мы ее определим — куда ж деваться, — но разговор о ней пока отложим.

Теперь создадим тело объекта с реализацией перечисленных методов.

CREATE OR REPLACE TYPE BODY AverageImpl IS
STATIC FUNCTION ODCIAggregateInitialize (actx IN OUT AverageImpl)
RETURN number IS
BEGIN
actx := AverageImpl(0,0);
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateIterate (self IN OUT AverageImpl, val IN number)
RETURN number IS
BEGIN
dbms_output.put_line(self.accum||'(‘||self.qty||’) + ‘||val);
self.accum := self.accum + val;
self.qty := self.qty + 1;
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateMerge (self IN OUT AverageImpl, ctx2 IN AverageImpl)
RETURN number IS
BEGIN
dbms_output.put_line(self.accum||'(‘||self.qty||’) & ‘||ctx2.accum||'(‘||ctx2.qty||’)’);
self.accum := self.accum + ctx2.accum;
self.qty := self.qty + ctx2.qty;
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateTerminate (self IN OUT AverageImpl, returnValue OUT number, flags IN number)
RETURN number IS
BEGIN
dbms_output.put_line(‘= ‘||self.accum||'(‘||self.qty||’) flags:’||flags);
returnValue := CASE WHEN self.qty > 0 THEN self.accum / self.qty END;
RETURN ODCIConst.Success;
END;
END;
/

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

Trim-пляски вокруг возвращаемого значения не нужны: Oracle самостоятельно отрезает незначащие нули при выводе значения.

Обратите внимание, что все функции возвращают признак успешности выполнения (значение ODCIConst.Success), а смысловые значения передаются через параметры OUT и IN OUT (которые в PL/SQL никак не связаны с собственно возвращаемым значением, как в PL/pgSQL). В частности, любая функция, в том числе и ODCIAggregateTerminate, может изменять атрибуты своего объекта, ссылка на который передается ей в первом параметре (self).

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

CREATE OR REPLACE FUNCTION average(val number) RETURN number
AGGREGATE USING AverageImpl;
/

Проверяем. Для генерации значений используем идиоматическую конструкцию с рекурсивным запросом CONNECT BY level:

SELECT average(level) FROM dual CONNECT BY level
Можно обратить внимание на то, что вывод сообщений в PostgreSQL появляется до результата, а в Oracle — после. Это из-за того, что RAISE NOTICE работает асинхронно, а пакет dbms_output буферизует вывод.

Как мы видим, в функцию ODCIAggregateTerminate был передан нулевой флаг. Это означает, что контекст больше не требуется и его — при желании — можно забыть.

И проверка на пустом множестве:

SELECT average(rownum) FROM dual WHERE 1 = 0;
AVERAGE(ROWNUM)
—————

Оконные функции: OVER()

Хорошая новость: написанная нами агрегатная функция может без всяких изменений работать и как оконная (аналитическая).

Оконная функция отличается от агрегатной тем, что не сворачивает выборку в одну (агрегированную) строку, а вычисляется как бы отдельно для каждой строки. Синтаксически вызов оконной функции отличается наличием конструкции OVER с указанием рамки, которая определяет множество строк для обработки. В простейшем случае она так и записывается: OVER(), и это означает, что функция должна обработать все строки. Результат получается такой, как будто мы посчитали обычную агрегатную функцию и записали результат (один и тот же) напротив каждой строки выборки.

Иными словами, рамка статична и охватывает все строки:

PostgreSQL

SELECT g.x, average(g.x) OVER ()
FROM generate_series(1,5) as g(x);

NOTICE: 0(0) + 1
NOTICE: 1(1) + 2
NOTICE: 3(2) + 3
NOTICE: 6(3) + 4
NOTICE: 10(4) + 5
NOTICE: = 15(5)
x | average
—+———
1 | 3
2 | 3
3 | 3
4 | 3
5 | 3
(5 rows)

По выводу NOTICE видно, что все происходит точно так же, как и ранее при вычислении обычной агрегатной функции. Получив результат от функции average_final, PostgreSQL проставляет его в каждой строке.

Oracle

SELECT average(level) OVER() average
FROM dual CONNECT BY level
Неожиданно. Вместо того, чтобы вычислить результат один раз, Oracle вызывает функцию ODCIAggregateTerminate N+1 раз: сначала для каждой строки с флагом 1 (что означает, что контекст еще пригодится) и затем еще один раз в конце. Значение, полученное при последнем вызове, просто игнорируется.

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

Оконные функции: OVER(PARTITION BY)

Предложение PARTITION BY в определении рамки похоже на обычную агрегатную конструкцию GROUP BY. Оконная функция с указанием PARTITION BY вычисляется отдельно для каждой группы строк, и результат приписывается к каждой строке выборки.

В таком варианте рамка тоже статична, но для каждой группы она разная. Например, если определены две группы строк (с первой по вторую и с третьей по пятую), то рамку можно представить себе так:

PostgreSQL

SELECT g.x/3 part,
g.x,
average(g.x) OVER (PARTITION BY g.x/3)
FROM generate_series(1,5) as g(x);

NOTICE: 0(0) + 1
NOTICE: 1(1) + 2
NOTICE: = 3(2)
NOTICE: 0(0) + 3
NOTICE: 3(1) + 4
NOTICE: 7(2) + 5
NOTICE: = 12(3)
part | x | average
——+—+———
0 | 1 | 1.5
0 | 2 | 1.5
1 | 3 | 4
1 | 4 | 4
1 | 5 | 4
(5 rows)

Вычисление снова происходит последовательно, но теперь при переходе к другой группе строк состояние сбрасывается в начальное значение (initcond).

Oracle

SELECT trunc(level/3) part,
level,
average(level) OVER(PARTITION BY trunc(level/3)) average
FROM dual CONNECT BY level 0(0) + 2
2(1) + 1
= 3(2) flags:1
= 3(2) flags:1
0(0) + 4
4(1) + 5
9(2) + 3
= 12(3) flags:1
= 12(3) flags:1
= 12(3) flags:1
= 12(3) flags:0

Занятно, что Oracle решил переставить строки местами. Это может что-то сказать о деталях реализации, но в любом случае — имеет право.

Оконные функции: OVER(ORDER BY)

Если в определение рамки добавить предложение ORDER BY, указывающее порядок сортировки, функция начнет работать в режиме нарастания (для функции sum мы бы так и сказали — нарастающим итогом).

Для первой строки рамка будет состоять из одной этой строки; для второй — из первой и второй; для третьей — из первой, второй и третьей и так далее. Иными словами, в рамку будут входить строки с первой до текущей.

На самом деле, это можно ровно так и записать: OVER(ORDER BY… ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW), но, поскольку это многословие подразумеваются по умолчанию, его обычно опускают.

Итак, рамка перестает быть статичной: ее голова движется вниз, а хвост остается на месте:

PostgreSQL

SELECT g.x, average(g.x) OVER (ORDER BY g.x)
FROM generate_series(1,5) as g(x);

NOTICE: 0(0) + 1
NOTICE: = 1(1)
NOTICE: 1(1) + 2
NOTICE: = 3(2)
NOTICE: 3(2) + 3
NOTICE: = 6(3)
NOTICE: 6(3) + 4
NOTICE: = 10(4)
NOTICE: 10(4) + 5
NOTICE: = 15(5)
x | average
—+———
1 | 1
2 | 1.5
3 | 2
4 | 2.5
5 | 3
(5 rows)

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

Oracle

SELECT level, average(level) OVER(ORDER BY level) average
FROM dual CONNECT BY level
На этот раз обе системы работают одинаково.

Оконные функции: OVER(PARTITION BY ORDER BY)

Предложения PARTITION BY и ORDER BY можно комбинировать. Тогда внутри каждой группы строк функция будет работать в режиме нарастания, а при переходе от группы к группе состояние будет сбрасываться в начальное.

PostgreSQL

Oracle

Оконные функции со скользящей рамкой

Во всех примерах, которые мы посмотрели, рамка либо была статической, либо двигалась только ее голова (при использовании предложения ORDER BY). Это давало нам возможность вычислять состояние последовательно, добавляя к контексту строку за строкой.

Но рамку оконной функции можно задать и таким образом, что ее хвост тоже будет смещаться. В нашем примере это будет соответствовать понятию скользящего среднего. Например, указание OVER(ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) говорит о том, что для каждой строки результата будут усредняться текущее и два предыдущих значений.

Сможет ли вычисляться оконная функция в таком случае? Оказывается, сможет, правда неэффективно. Но, написав еще немного кода, можно улучшить ситуацию.

PostgreSQL

SELECT g.x,
average(g.x) OVER (ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
FROM generate_series(1,5) as g(x);

NOTICE: 0(0) + 1
NOTICE: = 1(1)
NOTICE: 1(1) + 2
NOTICE: = 3(2)
NOTICE: 3(2) + 3
NOTICE: = 6(3)
NOTICE: 0(0) + 2
NOTICE: 2(1) + 3
NOTICE: 5(2) + 4
NOTICE: = 9(3)
NOTICE: 0(0) + 3
NOTICE: 3(1) + 4
NOTICE: 7(2) + 5
NOTICE: = 12(3)
x | average
—+———
1 | 1
2 | 1.5
3 | 2
4 | 3
5 | 4
(5 rows)

Вплоть до третьей строки все идет хорошо, потому что хвост фактически не двигается: мы просто добавляем к уже имеющемуся контексту очередное значение. Но, поскольку мы не умеем убирать значение из контекста, для четвертой и пятой строк все приходится пересчитывать полностью, каждый раз возвращаясь к начальному состоянию.

Итак, было бы здорово иметь не только функцию добавления очередного значения, но и функцию удаления значения из состояния. И действительно, такую функцию можно создать:

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

Oracle

Тут ситуация аналогична. Созданный вариант аналитической функции работает, но неэффективно:

SELECT level,
average(level) OVER(ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) average
FROM dual CONNECT BY level 0(0) + 2
2(1) + 3
5(2) + 4
= 9(3) flags:1
0(0) + 3
3(1) + 4
7(2) + 5
= 12(3) flags:1
= 12(3) flags:0

Функция удаления значения из контекста определяется следующим образом:

Пересоздавать саму функцию не нужно. Проверим:

Параллельность

И PostgreSQL, и Oracle (Enterprise Edition) умеют вычислять агрегатные функции в параллельном режиме. При этом каждый из параллельных процессов выполняет свою часть работы, формируя промежуточное состояние. Затем основной процесс-координатор получает эти несколько состояний и должен объединить их в одно итоговое.

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

PostgreSQL

Функция выглядит следующим образом:

Поскольку мы убираем вывод, то отпадает и необходимость использовать процедурный язык — напишем функцию на чистом SQL:

CREATE TABLE t(n) AS SELECT generate_series(1,1000)::numeric;

С настройками по умолчанию PostgreSQL не построит параллельный план для такой таблицы — слишком она мала, — но его несложно уговорить:

SET parallel_setup_cost=0;
SET min_parallel_table_scan_size=0;

EXPLAIN(costs off) SELECT average(n) FROM t;
QUERY PLAN
——————————————
Finalize Aggregate
-> Gather
Workers Planned: 2
-> Partial Aggregate
-> Parallel Seq Scan on t

В плане запроса видим:

SELECT average(n) FROM t;
NOTICE: 0(0) & 281257(678)
NOTICE: 281257(678) & 127803(226)
NOTICE: 409060(904) & 91440(96)
NOTICE: = 500500(1000)
average
———
500.5
(1 row)

Почему функция average_combine вызывается три раза, а не два? Дело в том, что в PostgreSQL координирующий процесс тоже выполняет часть работы. Поэтому, хотя было запущено два рабочих процесса, реально работа выполнялась в трех. Один из них успел обработать 678 строк, другой 226 и третий — 96 (хотя эти цифры ничего не значат и при другом запуске могут отличаться).

Oracle

Если помните, функцию ODCIAggregateMerge мы уже написали в самом начале, поскольку в Oracle она является обязательной. Документация настаивает, что эта функция необходима не только для параллельной работы, но и для последовательной — хотя мне трудно понять, зачем (и на практике не приходилось сталкиваться с ее выполнением при последовательной обработке).

Все, что остается сделать — объявить функцию безопасной для параллельной работы:

CREATE OR REPLACE FUNCTION average(val number) RETURN number
PARALLEL_ENABLE
AGGREGATE USING AverageImpl;
/

Создаем таблицу:

CREATE TABLE t(n) AS SELECT to_number(level) FROM dual CONNECT BY level
Уговорить Oracle еще проще, чем PostgreSQL — достаточно написать хинт. Вот какой получается план (вывод сильно урезан для простоты):

EXPLAIN PLAN FOR SELECT /*+ PARALLEL(2) */ average(n) FROM t;
SELECT * FROM TABLE(dbms_xplan.display);

———————————
| Id | Operation |
———————————
| 0 | SELECT STATEMENT |
| 1 | SORT AGGREGATE |
| 2 | PX COORDINATOR |
| 3 | PX SEND QC (RANDOM) |
| 4 | SORT AGGREGATE |
| 5 | PX BLOCK ITERATOR |
| 6 | TABLE ACCESS FULL |
———————————

План также содержит:

Документация

Самое время привести ссылки на документацию, в том числе и на агрегатные и оконные функции, уже включенным в СУБД. Там можно найти много интересного.

Пример про округление копеек

И обещанный пример из жизни. Эту функцию я придумал, когда приходилось писать отчеты для бухгалтерии, работающей по РСБУ (правилам российского бухучета).

Самая простая задача, в которой возникает необходимость округления — распределение общих расходов (скажем, 100 рублей) на отделы (скажем, 3 штуки) по какому-то принципу (скажем, поровну):

WITH depts(name) AS (
VALUES (‘A’), (‘B’), (‘C’)
), report(dept,amount) AS (
SELECT name, 100.00 / count(*) OVER() FROM depts
)
SELECT dept, round(amount,2) FROM report;

dept | round
——+——-
A | 33.33
B | 33.33
C | 33.33
(3 rows)

Этот запрос показывает проблему: суммы надо округлять, но при этом теряется копейка. А РСБУ этого не прощает.

Задачу можно решать по-разному, но на мой вкус наиболее элегантный способ — оконная функция, которая работает в нарастающем режиме и берет всю борьбу с копейками на себя:

WITH depts(name) AS (
VALUES (‘A’), (‘B’), (‘C’)
), report(dept,amount) AS (
SELECT name, 100.00 / count(*) OVER() FROM depts
)
SELECT dept, round2(amount) OVER (ORDER BY dept) FROM report;

dept | round2
——+———
A | 33.33
B | 33.34
C | 33.33
(3 rows)

Состояние такой функции включает ошибку округления (r_error) и текущее округленное значение (amount). Функция обработки очередного значения увеличивает ошибку округления, и, если она уже превышает полкопейки, добавляет к округленной сумме копеечку:

Полный код функции приводить не буду: используя уже приведенные примеры написать ее не представляет сложности.

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

Источник

Adblock
detector