Есть ли в PostgreSQL агрегатная функция first-) безопасной для типов?

21

Полный вопрос переписать

Я ищу агрегатную функцию First ().

Здесь я нашел то, что почти работает:

CREATE OR REPLACE FUNCTION public.first_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE sql IMMUTABLE STRICT AS $$
        SELECT $1;
$$;

-- And then wrap an aggregate around it
CREATE AGGREGATE public.first (
        sfunc    = public.first_agg,
        basetype = anyelement,
        stype    = anyelement
);

Проблема в том, что когда столбец varchar (n) проходит через функцию first (), он преобразуется в простой varchar (без размера). Пытаясь вернуть запрос в функции как RETURNS SETOF anyelement, я получаю следующую ошибку:

ОШИБКА: структура запроса не соответствует типу результата функции. Estado de SQL: 42804 Подробная информация: Изменение символа возвращаемого типа не соответствует ожидаемому изменению символа типа (40) в столбце 2. Контекст: функция PL / pgSQL vsr_table_at_time (anyelement, отметка времени без часового пояса) ) строка 31 в RETURN QUERY

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

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

Александр Нето
источник

Ответы:

18

DISTINCT ON()

Как примечание, это именно то, что DISTINCT ON()делает (не путать с DISTINCT)

SELECT DISTINCT ON ( expression [, ...] ) сохраняет только первую строку каждого набора строк, где заданные выражения оцениваются как равные . Эти DISTINCT ONвыражения интерпретируются с использованием тех же правил, что и для ORDER BY(смотрите выше). Обратите внимание, что «первая строка» каждого набора является непредсказуемой, если только ORDER BYона не используется для того, чтобы требуемая строка появлялась первой. Например

Так что, если вы должны были написать,

SELECT myFirstAgg(z)
FROM foo
GROUP BY x,y;

Это эффективно

SELECT DISTINCT ON(x,y) z
FROM foo;
-- ORDER BY z;

В том, что занимает первое z. Есть два важных различия,

  1. Вы также можете выбрать другие столбцы без дополнительной агрегации.

    SELECT DISTINCT ON(x,y) z, k, r, t, v
    FROM foo;
    -- ORDER BY z, k, r, t, v;
  2. Потому что нет, GROUP BYвы не можете использовать (реальные) агрегаты с ним.

    CREATE TABLE foo AS
    SELECT * FROM ( VALUES
      (1,2,3),
      (1,2,4),
      (1,2,5)
    ) AS t(x,y,z);
    
    SELECT DISTINCT ON (x,y) z, sum(z)
    FROM foo;
    
    -- fails, as you should expect.
    SELECT DISTINCT ON (x,y) z, sum(z)
    FROM foo;
    
    -- would not otherwise fail.
    SELECT myFirstAgg(z), sum(z)
    FROM foo
    GROUP BY x,y;

Не забывай ORDER BY

Кроме того, хотя я не смел, тогда я буду сейчас

Обратите внимание, что «первая строка» каждого набора непредсказуема, если только ORDER BY не используется, чтобы гарантировать, что желаемая строка появляется первой. Например

Всегда используйте ORDER BYсDISTINCT ON

Использование функции упорядоченного множества

Я полагаю , многие люди ищут first_value, упорядоченными-Set агрегатные функции . Просто хотел выбросить это там. Это выглядело бы так, если бы существовала функция:

SELECT a, b, first_value() WITHIN GROUP (ORDER BY z)    
FROM foo
GROUP BY a,b;

Но, увы, вы можете сделать это.

SELECT a, b, percentile_disc(0) WITHIN GROUP (ORDER BY z)   
FROM foo
GROUP BY a,b;
Эван Кэрролл
источник
1
Проблема с этим ответом состоит в том, что он работает, только если вы хотите, чтобы в вашем списке выбора было ОДНО агрегат, что не подразумевается под вопросом. Если, например, вы хотите выбрать из одной таблицы и найти несколько упорядоченных первых значений, DISTINCT ONв этом случае работать не будет. Это не агрегатная функция, вы на самом деле фильтруете данные и можете сделать это только один раз.
DB140141,
6

Да, я нашел простой способ с вашим делом, используя некоторые функции в PostgreSQL 9.4+

Давайте посмотрим на этот пример:

select  (array_agg(val ORDER BY i))[1] as first_value_orderby_i,
    (array_agg(val ORDER BY i DESC))[1] as last_value_orderby_i,
    (array_agg(val))[1] as last_value_all,
    (array_agg(val))[array_length(array_agg(val),1)] as last_value_all
   FROM (
        SELECT i, random() as val
        FROM generate_series(1,100) s(i)
        ORDER BY random()
    ) tmp_tbl

Я надеюсь, что это поможет вам в вашем случае.

Мабу Клосен
источник
Проблема этого решения в том, что оно не работает с DOMAINтипами данных или другими небольшими исключениями. Это также намного сложнее и занимает больше времени, создавая массив всего набора данных. Простым решением было бы создание пользовательского агрегата, но до сих пор я не нашел идеального решения даже с этим. Оконные функции также плохие, так как их нельзя использовать так же, как вы могли бы использовать агрегаты (с операторами FILTER или в CROSS JOIN LATERAL)
AlexanderMP
5

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

CREATE TABLE test (
    id SERIAL NOT NULL PRIMARY KEY,
    cat TEXT,
    value VARCHAR(2)
    date TIMESTAMP WITH TIME ZONE

);

Затем, если вам нужен первый элемент в каждой cat(категории), вы будете делать запрос следующим образом:

SELECT
    cat,
    first_value(date) OVER (PARTITION BY cat ORDER BY date)
FROM
    test;

или:

SELECT
    cat,
    first_value(date) OVER w
FROM
    test
WINDOW w AS (PARTITION BY cat ORDER BY date);
Гислен Левек
источник
Извините, я не думаю, что это относится к моему случаю использования. First_value не является функцией агрегации, показывающей все записи с определенным общим значением (ваш пример cat), который оценивается как первый в некотором порядке (ваш пример даты). Моя потребность в другом. Мне нужно, в одном и том же выборе, объединить несколько столбцов, выбрав первое ненулевое значение. То есть он должен вывести одну запись для каждой из комбинаций значений в GROUP BY.
Александр Нето
2
Выше , может быть сделано , чтобы работать, бросая различны в смесь: select distinct x, first_value(y) over (partition by x), first_value(z) over (partition by x) from .... Вероятно, неэффективно, но достаточно для меня, чтобы продолжить прототипирование. Определенно что-то, чтобы вернуться, хотя!
Макс Мерфи