Чи існує в PostgreSQL безпечна для типу перша () сукупна функція?


21

Повне запитання перепишіть

Я шукаю першу () сукупну функцію.

Тут я знайшов щось, що майже працює:

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) проходить через першу функцію (), вона перетворюється на простий вархар (без розміру). Намагаючись повернути запит у функцію як НАЗАД НА ЗВ'ЯЗКУ будь-якого елемента, я отримую таку помилку:

ПОМИЛКА: структура запиту не відповідає типу результату функції Estado de SQL: 42804 Детальніше: Повернений символ типу, що змінюється, не відповідає очікуваному символу типу (40) у стовпці 2. Контекст: PL / pgSQL функція vsr_table_at_time (будь-який елемент, часова мітка без часового поясу ) рядок 31 у ПОВЕРНЕННІ ЗАПИТАННЯ

На цій же сторінці вікі є посилання на 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

Крім того, поки я не смілив, тоді буду зараз

Зауважте, що "перший рядок" кожного набору є непередбачуваним, якщо не використовується ЗАМОВЛЕННЯ BY для того, щоб бажаний рядок з'явився першим. Наприклад

Завжди використовуйте ORDER BYсDISTINCT ON

Використання впорядкованої функції сукупності

Я думаю, що багато людей шукають first_value, упорядковані-встановлені агреговані функції . Просто хотів це викинути. Це виглядало б так, якби функція існувала:

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 не є функцією агрегації, що показує всі записи з певним загальним значенням (ваш приклад кішка), який оцінюється як перший за певним порядком (дата прикладу). Моя потреба інша. Мені потрібно в тому ж виділенні узгодити кілька стовпців, вибравши перше ненуле значення. Тобто, він повинен вивести один запис для кожної комбінації значень у групі BY.
Олександр Нето

2
Вище , може бути зроблено , щоб працювати, кидаючи різні в суміш: select distinct x, first_value(y) over (partition by x), first_value(z) over (partition by x) from .... Напевно, неефективно, але мені достатньо, щоб почати розробляти прототипи. Однозначно щось переглянути, хоча!
Макс Мерфі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.