×
Мы обрабатываем cookies, чтобы сделать наш сайт удобнее и персонализированнее для вас. Подробнее: политика использования «cookies» и «политики конфиденциальности».

Для самостоятельной настройки ознакомьтесь с инструкцией

Дополнительные настройки cookies в браузерах

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

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

Инструкция по отключению cookies
Принять
Настроить
Отклонить

ДОКУМЕНТАЦИЯ

Выберите версию, форк и язык для СУБД Digital Q.DataBase, чтобы прочитать или скачать всю документацию.
Техподдержка
Документация
Диасофт
Авторские права © 2016–2025 ООО "Диасофт Экосистема"
Скачать всю документацию:

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

5.1.12.1. Режим скользящего агрегата
5.1.12.2. Полиморфные и вариативные агрегаты
5.1.12.3. Агрегаты на упорядоченных наборах
5.1.12.4. Частичная агрегация
5.1.12.5. Вспомогательные функции для агрегатов

Агрегатные функции в Digital Q.DataBase определяются в терминах значений состояния (state values) и функций перехода состояния (state transition functions). То есть агрегат работает, используя значение состояния, которое обновляется по мере обработки каждой последующей входной строки. Чтобы определить новую агрегатную функцию, выбирается тип данных для значения состояния, начальное значение состояния и функция перехода состояния. Функция перехода принимает предыдущее значение состояния и входные значения агрегата для текущей строки и возвращает новое значение состояния. Также может быть указана финальная функция (final function) на случай, если желаемый результат агрегата отличается от данных, которые необходимо хранить в текущем значении состояния. Финальная функция принимает конечное значение состояния и возвращает то, что требуется в качестве результата агрегата. В принципе, функции перехода и финальные функции — это обычные функции, которые также могут использоваться вне контекста агрегата. (На практике из соображений производительности часто бывает полезно создавать специализированные функции перехода, которые могут работать только при вызове в составе агрегата.)

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

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

CREATE AGGREGATE sum (complex)
(
    sfunc = complex_add,
    stype = complex,
    initcond = '(0,0)'
);

который мы могли бы использовать так:

SELECT sum(a) FROM test_complex;

   sum
-----------
 (34,53.9)

(Обратите внимание, что мы полагаемся на перегрузку функций: существует более одной агрегатной функции с именем sum, но Digital Q.DataBase может определить, какой вид суммы применим к столбцу типа complex.)

Приведенное выше определение sum вернет ноль (начальное значение состояния), если нет входных значений, отличных от null. Возможно, в этом случае мы захотим вернуть null — стандарт SQL ожидает, что sum будет вести себя именно так. Мы можем сделать это, просто опустив фразу initcond, чтобы начальное значение состояния было null. Обычно это означало бы, что sfunc должна будет проверять входное значение состояния на null. Но для sum и некоторых других простых агрегатов, таких как max и min, достаточно вставить первое входное значение, отличное от null, в переменную состояния, а затем начать применять функцию перехода со второго входного значения, отличного от null. Digital Q.DataBase сделает это автоматически, если начальное значение состояния равно null, а функция перехода помечена как «strict» (т. е. не вызывается для входов null).

Еще одна особенность поведения по умолчанию для «строгой» (strict) функции перехода заключается в том, что предыдущее значение состояния сохраняется без изменений всякий раз, когда встречается входное значение null. Таким образом, значения null игнорируются. Если вам нужно другое поведение для входов null, не объявляйте свою функцию перехода как строгую; вместо этого напишите ее код так, чтобы она проверяла входы на null и делала все необходимое.

avg (среднее значение) — более сложный пример агрегата. Ему требуются две части накопленного состояния: сумма входных значений и количество входных значений. Конечный результат получается путем деления этих величин. Среднее значение обычно реализуется с использованием массива в качестве значения состояния. Например, встроенная реализация avg(float8) выглядит так:

CREATE AGGREGATE avg (float8)
(
    sfunc = float8_accum,
    stype = float8[],
    finalfunc = float8_avg,
    initcond = '{0,0,0}'
);

Примечание

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

Вызовы агрегатных функций в SQL позволяют использовать опции DISTINCT и ORDER BY, которые управляют тем, какие строки подаются в функцию перехода агрегата и в каком порядке. Эти опции реализуются «за кулисами» и не являются заботой вспомогательных функций агрегата.

Для получения более подробной информации см. описание команды CREATE AGGREGATE.

5.1.12.1. Режим скользящего агрегата #

Агрегатные функции могут опционально поддерживать режим скользящего агрегата (moving-aggregate mode), который обеспечивает существенно более быстрое выполнение агрегатных функций внутри окон с движущимися начальными точками рамок (frames). (Информацию об использовании агрегатных функций в качестве оконных см. в Раздел 1.3.5 и Раздел 2.1.2.8.) Основная идея заключается в том, что в дополнение к обычной «прямой» функции перехода агрегат предоставляет обратную функцию перехода (inverse transition function), которая позволяет удалять строки из текущего значения состояния агрегата, когда они выходят из рамки окна. Например, агрегат sum, использующий сложение в качестве прямой функции перехода, использовал бы вычитание в качестве обратной функции перехода. Без обратной функции перехода механизм оконных функций должен пересчитывать агрегат с нуля каждый раз, когда начальная точка рамки сдвигается, в результате чего время выполнения пропорционально количеству входных строк, умноженному на среднюю длину рамки. С обратной функцией перехода время выполнения пропорционально только количеству входных строк.

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

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

CREATE AGGREGATE sum (complex)
(
    sfunc = complex_add,
    stype = complex,
    initcond = '(0,0)',
    msfunc = complex_add,
    minvfunc = complex_sub,
    mstype = complex,
    minitcond = '(0,0)'
);

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

Прямой функции перехода для режима скользящего агрегата не разрешено возвращать null в качестве нового значения состояния. Если обратная функция перехода возвращает null, это расценивается как указание на то, что обратная функция не может отменить вычисление состояния для этого конкретного входа, и поэтому вычисление агрегата будет выполнено заново с нуля для текущей начальной позиции рамки. Это соглашение позволяет использовать режим скользящего агрегата в ситуациях, когда встречаются редкие случаи, которые нецелесообразно исключать из текущего значения состояния. Обратная функция перехода может «спасовать» (punt) в этих случаях и при этом все равно выиграть в производительности, пока она может работать для большинства случаев. Например, агрегат, работающий с числами с плавающей запятой, может решить спасовать, когда из текущего значения состояния необходимо удалить входное значение NaN (не число).

При написании вспомогательных функций для скользящего агрегата важно быть уверенным, что обратная функция перехода может точно восстановить правильное значение состояния. В противном случае могут возникнуть видимые пользователю различия в результатах в зависимости от того, используется ли режим скользящего агрегата. Пример агрегата, для которого добавление обратной функции перехода кажется простым на первый взгляд, но где это требование не может быть выполнено — это sum по входам float4 или float8. Навное объявление sum(float8) могло бы быть таким:

CREATE AGGREGATE unsafe_sum (float8)
(
    stype = float8,
    sfunc = float8pl,
    mstype = float8,
    msfunc = float8pl,
    minvfunc = float8mi
);

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

SELECT
  unsafe_sum(x) OVER (ORDER BY n ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
FROM (VALUES (1, 1.0e20::float8),
             (2, 1.0::float8)) AS v (n,x);

Этот запрос возвращает 0 в качестве второго результата вместо ожидаемого ответа 1. Причиной является ограниченная точность значений с плавающей запятой: добавление 1 к 1e20 снова дает 1e20, и поэтому вычитание 1e20 из этого результата дает 0, а не 1. Обратите внимание, что это ограничение арифметики с плавающей запятой в целом, а не ограничение Digital Q.DataBase.

5.1.12.2. Полиморфные и вариативные агрегаты #

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

CREATE AGGREGATE array_accum (anycompatible)
(
    sfunc = array_append,
    stype = anycompatiblearray,
    initcond = '{}'
);

Здесь фактическим типом состояния для любого конкретного вызова агрегата является тип массива, имеющий фактический тип входных данных в качестве элементов. Поведение агрегата заключается в объединении (concatenation) всех входных данных в массив этого типа. (Примечание: встроенный агрегат array_agg предоставляет аналогичную функциональность с лучшей производительностью, чем было бы у этого определения.)

Вот вывод с использованием двух разных фактических типов данных в качестве аргументов:

SELECT attrelid::regclass, array_accum(attname)
    FROM pg_attribute
    WHERE attnum > 0 AND attrelid = 'pg_tablespace'::regclass
    GROUP BY attrelid;

   attrelid    |              array_accum
---------------+---------------------------------------
 pg_tablespace | {spcname,spcowner,spcacl,spcoptions}
(1 row)

SELECT attrelid::regclass, array_accum(atttypid::regtype)
    FROM pg_attribute
    WHERE attnum > 0 AND attrelid = 'pg_tablespace'::regclass
    GROUP BY attrelid;

   attrelid    |        array_accum
---------------+---------------------------
 pg_tablespace | {name,oid,aclitem[],text[]}
(1 row)

Обычно агрегатная функция с полиморфным типом результата имеет полиморфный тип состояния, как в приведенном выше примере. Это необходимо, потому что иначе финальная функция не может быть объявлена разумно: ей потребовался бы полиморфный тип результата, но без полиморфного типа аргумента, что CREATE FUNCTION отклонит на том основании, что тип результата не может быть выведен из вызова. Но иногда неудобно использовать полиморфный тип состояния. Самый распространенный случай — когда вспомогательные функции агрегата должны быть написаны на языке C, а тип состояния должен быть объявлен как internal, так как для него нет эквивалента на уровне SQL. Чтобы решить эту проблему, можно объявить финальную функцию как принимающую дополнительные «фиктивные» (dummy) аргументы, соответствующие входным аргументам агрегата. Такие фиктивные аргументы всегда передаются как значения null, так как при вызове финальной функции конкретное значение недоступно. Их единственное назначение — позволить связать тип результата полиморфной финальной функции с типами входных данных агрегата. Например, определение встроенного агрегата array_agg эквивалентно следующему:

CREATE FUNCTION array_agg_transfn(internal, anynonarray)
  RETURNS internal ...;
CREATE FUNCTION array_agg_finalfn(internal, anynonarray)
  RETURNS anyarray ...;

CREATE AGGREGATE array_agg (anynonarray)
(
    sfunc = array_agg_transfn,
    stype = internal,
    finalfunc = array_agg_finalfn,
    finalfunc_extra
);

Здесь опция finalfunc_extra указывает, что финальная функция получает, в дополнение к значению состояния, дополнительные фиктивные аргументы, соответствующие входным аргументам агрегата. Дополнительный аргумент anynonarray делает объявление array_agg_finalfn допустимым.

Агрегатную функцию можно заставить принимать переменное количество аргументов, объявив ее последний аргумент как массив VARIADIC почти так же, как и для обычных функций; см. Раздел 5.1.5.6. Функции перехода агрегата должны иметь тот же тип массива в качестве последнего аргумента. Функции перехода обычно также помечаются как VARIADIC, но это не является строгим требованием.

Примечание

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

SELECT myaggregate(a ORDER BY a, b, c) FROM ...

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

SELECT myaggregate(a, b, c ORDER BY a) FROM ...

Если myaggregate является вариативным, оба этих вызова могут быть совершенно корректными.

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

5.1.12.3. Агрегаты на упорядоченных наборах #

Агрегаты, которые мы описывали до сих пор, являются «обычными» агрегатами. Digital Q.DataBase также поддерживает агрегаты на упорядоченных наборах (ordered-set aggregates), которые отличаются от обычных агрегатов двумя ключевыми моментами. Во-первых, в дополнение к обычным агрегируемым аргументам, которые вычисляются один раз для каждой входной строки, агрегат на упорядоченном наборе может иметь «прямые» (direct) аргументы, которые вычисляются только один раз для всей операции агрегации. Во-вторых, синтаксис для обычных агрегируемых аргументов явно задает порядок сортировки для них. Агрегат на упорядоченном наборе обычно используется для реализации вычислений, зависящих от определенного порядка строк, например, ранга или процентиля, так что порядок сортировки является необходимым аспектом любого вызова. Например, встроенное определение percentile_disc эквивалентно:

CREATE FUNCTION ordered_set_transition(internal, anyelement)
  RETURNS internal ...;
CREATE FUNCTION percentile_disc_final(internal, float8, anyelement)
  RETURNS anyelement ...;

CREATE AGGREGATE percentile_disc (float8 ORDER BY anyelement)
(
    sfunc = ordered_set_transition,
    stype = internal,
    finalfunc = percentile_disc_final,
    finalfunc_extra
);

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

SELECT percentile_disc(0.5) WITHIN GROUP (ORDER BY income) FROM households;
 percentile_disc
-----------------
           50489

Здесь 0.5 — прямой аргумент; не было бы никакого смысла, если бы доля процентиля менялась от строки к строке.

В отличие от случая с обычными агрегатами, сортировка входных строк для агрегата на упорядоченном наборе не выполняется «за кулисами», а является обязанностью вспомогательных функций агрегата. Типичный подход к реализации заключается в хранении ссылки на объект «tuplesort» в значении состояния агрегата, подаче входящих строк в этот объект, а затем завершении сортировки и чтении данных в финальной функции. Такой дизайн позволяет финальной функции выполнять специальные операции, такие как вставка дополнительных «гипотетических» строк в данные для сортировки. В то время как обычные агрегаты часто могут быть реализованы с помощью вспомогательных функций, написанных на PL/pgSQL или другом языке PL, агрегаты на упорядоченных наборах обычно приходится писать на C, так как их значения состояния не могут быть определены как какой-либо тип данных SQL. (В приведенном выше примере обратите внимание, что значение состояния объявлено как тип internal — это типично.) Кроме того, поскольку финальная функция выполняет сортировку, невозможно продолжать добавление входных строк путем повторного выполнения функции перехода позже. Это означает, что финальная функция не является READ_ONLY; она должна быть объявлена в CREATE AGGREGATE как READ_WRITE или как SHAREABLE, если возможно, чтобы дополнительные вызовы финальной функции использовали уже отсортированное состояние.

Функция перехода состояния для агрегата на упорядоченном наборе получает текущее значение состояния плюс агрегируемые входные значения для каждой строки и возвращает обновленное значение состояния. Это то же самое определение, что и для обычных агрегатов, но обратите внимание, что прямые аргументы (если они есть) не передаются. Финальная функция получает последнее значение состояния, значения прямых аргументов, если они есть, и (если указано finalfunc_extra) значения null, соответствующие агрегируемым входам. Как и в случае с обычными агрегатами, finalfunc_extra полезна только в том случае, если агрегат является полиморфным; тогда дополнительные фиктивные аргументы необходимы для связи типа результата финальной функции с типами входных данных агрегата.

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

5.1.12.4. Частичная агрегация #

Опционально агрегатная функция может поддерживать частичную агрегацию. Идея частичной агрегации заключается в том, чтобы запускать функцию перехода состояния агрегата по разным подмножествам входных данных независимо, а затем объединять значения состояния, полученные из этих подмножеств, чтобы получить то же значение состояния, которое получилось бы при сканировании всех входных данных в одной операции. Этот режим может использоваться для параллельной агрегации, когда разные рабочие процессы сканируют разные части таблицы. Каждый рабочий процесс создает частичное значение состояния, и в конце эти значения состояния объединяются для получения окончательного значения состояния. (В будущем этот режим также может использоваться для таких целей, как объединение агрегаций по локальным и удаленным таблицам; но это еще не реализовано.)

Для поддержки частичной агрегации определение агрегата должно предоставлять функцию объединения (combine function), которая принимает два значения типа состояния агрегата (представляющих результаты агрегации по двум подмножествам входных строк) и выдает новое значение типа состояния, представляющее собой состояние после агрегации по комбинации этих наборов строк. Порядок входных строк из двух наборов относительно друг друга не определен. Это означает, что обычно невозможно определить полезную функцию объединения для агрегатов, чувствительных к порядку входных строк.

В качестве простых примеров, агрегаты MAX и MIN могут быть настроены на поддержку частичной агрегации путем указания функции объединения, аналогичной той же функции сравнения «большее из двух» или «меньшее из двух», которая используется в качестве их функции перехода. Агрегатам SUM в качестве функции объединения нужна просто функция сложения. (Опять же, это то же самое, что и их функция перехода, если только тип значения состояния не шире типа входных данных.)

Функция объединения обрабатывается почти так же, как функция перехода, которая случайно принимает значение типа состояния, а не базового входного типа, в качестве второго аргумента. В частности, правила работы с значениями null и строгими (strict) функциями аналогичны. Кроме того, если в определении агрегата указано ненулевое значение initcond, имейте в виду, что оно будет использоваться не только в качестве начального состояния для каждого сеанса частичной агрегации, но и в качестве начального состояния для функции объединения, которая будет вызываться для объединения каждого частичного результата в это состояние.

Если тип состояния агрегата объявлен как internal, функция объединения несет ответственность за то, чтобы ее результат был выделен в правильном контексте памяти для значений состояния агрегата. Это означает, в частности, что когда первый вход равен NULL, недопустимо просто возвращать второй вход, так как это значение будет находиться в неправильном контексте и не будет иметь достаточного времени жизни.

Когда тип состояния агрегата объявлен как internal, обычно также уместно, чтобы определение агрегата предоставляло функцию сериализации и функцию десериализации, которые позволяют копировать такое значение состояния из одного процесса в другой. Без этих функций параллельная агрегация не может быть выполнена, и будущие применения, такие как локальная/удаленная агрегация, вероятно, также не будут работать.

Функция сериализации должна принимать один аргумент типа internal и возвращать результат типа bytea, который представляет собой значение состояния, упакованное в плоский блок байтов. И наоборот, функция десериализации выполняет обратное преобразование. Она должна принимать два аргумента типов bytea и internal и возвращать результат типа internal. (Второй аргумент не используется и всегда равен нулю, но он необходим по соображениям безопасности типов.) Результат функции десериализации должен просто выделяться в текущем контексте памяти, так как, в отличие от результата функции объединения, он не является долгоживущим.

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

5.1.12.5. Вспомогательные функции для агрегатов #

Функция, написанная на C, может обнаружить, что она вызывается как вспомогательная функция агрегата, вызвав AggCheckCallContext, например:

if (AggCheckCallContext(fcinfo, NULL))

Одной из причин для такой проверки является то, что когда это условие истинно, первый вход должен быть временным значением состояния и, следовательно, может быть безопасно изменен «на месте» (in-place) вместо выделения новой копии. См. пример в int8inc(). (Хотя функциям перехода агрегата всегда разрешено изменять значение перехода на месте, финальным функциям агрегата это обычно не рекомендуется; если они это делают, такое поведение должно быть объявлено при создании агрегата. Подробнее см. в CREATE AGGREGATE.)

Второй аргумент AggCheckCallContext может использоваться для получения контекста памяти, в котором хранятся значения состояния агрегата. Это полезно для функций перехода, которые хотят использовать «расширенные» (expanded) объекты (см. Раздел 5.1.13.1) в качестве своих значений состояния. При первом вызове функция перехода должна вернуть расширенный объект, контекст памяти которого является дочерним по отношению к контексту состояния агрегата, а затем продолжать возвращать тот же расширенный объект при последующих вызовах. См. пример в array_append(). (array_append() не является функцией перехода какого-либо встроенного агрегата, но она написана так, чтобы вести себя эффективно при использовании в качестве функции перехода пользовательского агрегата.)

Другой вспомогательной процедурой, доступной для агрегатных функций, написанных на C, является AggGetAggref, которая возвращает узел разбора Aggref, определяющий вызов агрегата. Это в основном полезно для агрегатов на упорядоченных наборах, которые могут проверять подструктуру узла Aggref, чтобы узнать, какой порядок сортировки они должны реализовать. Примеры можно найти в файле orderedsetaggs.c в исходном коде Digital Q.DataBase.

Наверх
свяжитесь
с нами
контакты
Для прямой связи с нами вы можете использовать контакты ниже, либо оставить заявку через форму обратной связи, и мы обязательно свяжемся с вами

*поля обязательные к заполнению