Czy w PostgreSQL jest funkcja agregująca first () bezpieczna dla typu?

21

Ponowne napisanie pełnego pytania

Szukam funkcji agregującej First ().

Tutaj znalazłem coś, co prawie działa:

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
);

Problem polega na tym, że gdy kolumna varchar (n) przechodzi przez pierwszą funkcję (), jest konwertowana na prosty varchar (bez rozmiaru). Próbuję zwrócić zapytanie w funkcji jako ZWRACAĆ SETOF anyelement, otrzymuję następujący błąd:

BŁĄD: struktura zapytania nie zgadza się z typem wyniku funkcji Estado de SQL: 42804 Detalhe: Zmienna postać zwracanego typu nie pasuje do oczekiwanej zmienności typu (40) w kolumnie 2. Kontekst: Funkcja PL / pgSQL vsr_table_at_time (dowolny element, znacznik czasu bez strefy czasowej ) wiersz 31 w ZWROTNEJ KWESTII

Na tej samej stronie wiki znajduje się link do wersji C funkcji , która zastąpiłaby powyższą. Nie wiem, jak go zainstalować, ale zastanawiam się, czy ta wersja mogłaby rozwiązać mój problem.

Czy w międzyczasie mogę zmienić powyższą funkcję, aby zwracała dokładnie ten sam typ kolumny wejściowej?

Alexandre Neto
źródło

Odpowiedzi:

18

DISTINCT ON()

Na marginesie, właśnie to DISTINCT ON()robi (nie należy mylić DISTINCT)

SELECT DISTINCT ON ( expression [, ...] ) utrzymuje tylko pierwszy wiersz każdego zestawu wierszy, w którym podane wyrażenia oceniają jako równe . Te DISTINCT ONwyrażenia są interpretowane według tych samych zasad jak w przypadku ORDER BY(patrz wyżej). Zauważ, że „pierwszy rząd” każdego zestawu jest nieprzewidywalny, chyba że ORDER BYzostanie użyty do zapewnienia, że ​​żądany rząd pojawi się pierwszy. Na przykład

Więc jeśli miałbyś pisać,

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

To jest skuteczne

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

Pod tym względem bierze się pierwszy z. Istnieją dwie ważne różnice,

  1. Możesz także wybrać inne kolumny bez dodatkowych kosztów agregacji.

    SELECT DISTINCT ON(x,y) z, k, r, t, v
    FROM foo;
    -- ORDER BY z, k, r, t, v;
  2. Ponieważ nie ma GROUP BYmożna nie używać (Real) agregaty z nim.

    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;

Nie zapomnij ORDER BY

Ponadto, chociaż nie pogrubiłem go, zrobię to teraz

Zauważ, że „pierwszy wiersz” każdego zestawu jest nieprzewidywalny, chyba że użyjesz ORDER BY, aby upewnić się, że żądany wiersz pojawi się pierwszy. Na przykład

Zawsze używaj ORDER BYzDISTINCT ON

Korzystanie z funkcji agregującej zestaw

Wyobrażam sobie, że wiele osób szuka first_value, uporządkowanych Set Zagregowane funkcji . Po prostu chciałem to tam wyrzucić. Wyglądałoby to tak, gdyby funkcja istniała:

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

Ale niestety możesz to zrobić.

SELECT a, b, percentile_disc(0) WITHIN GROUP (ORDER BY z)   
FROM foo
GROUP BY a,b;
Evan Carroll
źródło
1
Problem z tą odpowiedzią polega na tym, że działa ona tylko wtedy, gdy chcesz JEDNĄ agregację na liście wyboru, co nie wynika z pytania. Jeśli na przykład chcesz wybrać z jednej tabeli i znaleźć kilka uporządkowanych pierwszych wartości, DISTINCT ONnie zadziała w tym przypadku. To nie jest funkcja agregująca, w rzeczywistości filtrujesz dane, więc możesz to zrobić tylko raz.
DB140141,
6

Tak, odkryłem łatwy sposób korzystania z twojej skrzynki, używając niektórych funkcji w PostgreSQL 9.4+

Zobaczmy ten przykład:

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

Mam nadzieję, że ci to pomoże.

Mabu Kloesen
źródło
Problem z tym rozwiązaniem polega na tym, że nie działa on z DOMAINtypami danych lub innymi małymi wyjątkami. Jest również o wiele bardziej skomplikowany i czasochłonny, tworząc tablicę całego zestawu danych. Prostym rozwiązaniem byłoby utworzenie niestandardowego agregatu, ale jak dotąd nie znalazłem idealnego rozwiązania nawet z tym. Funkcje okien również są złe, ponieważ nie można ich używać w taki sam sposób, jak można używać agregatów (z instrukcjami FILTER lub w CROSS JOIN LATERAL)
AlexanderMP
5

Nie jest to bezpośrednia odpowiedź na twoje pytanie, ale powinieneś wypróbować funkcję first_valueokna. Działa to tak:

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

);

Następnie, jeśli chcesz pierwszy element w każdej cat(kategorii), zapytaj w ten sposób:

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

lub:

SELECT
    cat,
    first_value(date) OVER w
FROM
    test
WINDOW w AS (PARTITION BY cat ORDER BY date);
Ghislain Leveque
źródło
Niestety nie sądzę, że dotyczy to mojego przypadku użycia. Pierwsza wartość nie jest funkcją agregującą, pokazującą wszystkie rekordy o określonej wspólnej wartości (twój przykładowy kot), który jest oceniany jako pierwszy zgodnie z pewną kolejnością (twoja przykładowa data). Moja potrzeba jest inna. Muszę w tym samym wyborze agregować kilka kolumn, wybierając pierwszą wartość inną niż null. Oznacza to, że powinien generować pojedynczy rekord dla każdej kombinacji wartości w GROUP BY.
Alexandre Neto
2
Powyższe może być wykonana do pracy wyrzucanie odróżnieniu do zaprawy: select distinct x, first_value(y) over (partition by x), first_value(z) over (partition by x) from .... Prawdopodobnie nieefektywny, ale wystarczający, abym mógł zająć się prototypowaniem. Zdecydowanie jednak coś do odwiedzenia!
Max Murphy