In PostgreSQL, esiste una funzione aggregata first () di tipo sicuro?


21

Riscrittura completa della domanda

Sto cercando una funzione aggregata First ().

Qui ho trovato qualcosa che quasi funziona:

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

Il problema è che quando una colonna varchar (n) passa attraverso la prima funzione (), viene convertita in varchar semplice (senza dimensioni). Tentando di restituire la query in una funzione come RETURNS SETOF anyelement, ottengo il seguente errore:

ERRORE: la struttura della query non corrisponde al tipo di risultato della funzione Estado de SQL: 42804 Detalhe: la variazione del carattere di tipo restituito non corrisponde alla variazione del carattere di tipo previsto (40) nella colonna 2. Contesto: funzione PL / pgSQL vsr_table_at_time (anyelement, timestamp senza fuso orario ) linea 31 a RESTITUISCI QUERY

Nella stessa pagina wiki è presente un collegamento a una versione C della funzione che sostituisce la precedente. Non so come installarlo, ma mi chiedo se questa versione possa risolvere il mio problema.

Nel frattempo, c'è un modo per modificare la funzione sopra in modo che restituisca esattamente lo stesso tipo della colonna di input?

Risposte:


18

DISTINCT ON()

Proprio come una nota a margine, questo è esattamente ciò che DISTINCT ON()fa (da non confondere DISTINCT)

SELECT DISTINCT ON ( expression [, ...] ) mantiene solo la prima riga di ogni set di righe in cui le espressioni fornite valutano uguali . Le DISTINCT ONespressioni sono interpretate usando le stesse regole di ORDER BY(vedi sopra). Si noti che la "prima riga" di ciascun set è imprevedibile, a meno che non ORDER BYvenga utilizzata per garantire che la riga desiderata venga visualizzata per prima. Per esempio

Quindi se dovessi scrivere,

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

È efficace

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

In quello ci vuole il primo z. Ci sono due differenze importanti,

  1. Puoi anche selezionare altre colonne senza alcun costo di ulteriore aggregazione.

    SELECT DISTINCT ON(x,y) z, k, r, t, v
    FROM foo;
    -- ORDER BY z, k, r, t, v;
  2. Perché non c'è GROUP BYsi può non utilizzare (reali) aggregati con esso.

    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;

Non dimenticare ORDER BY

Inoltre, anche se non l'ho audace, allora lo farò ora

Si noti che la "prima riga" di ogni set è imprevedibile a meno che non venga utilizzato ORDER BY per garantire che la riga desiderata venga visualizzata per prima. Per esempio

Usa sempre un ORDER BYconDISTINCT ON

Utilizzo di una funzione aggregata set ordinato

Immagino un sacco di persone sono alla ricerca di first_value, funzioni ordinata Set aggregate . Volevo solo buttarlo là fuori. Sarebbe così, se esistesse la funzione:

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

Ma purtroppo puoi farlo.

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

1
Il problema con questa risposta è che funziona solo se si desidera UN aggregato nell'elenco di selezione, che non è implicito nella domanda. Se ad esempio si desidera selezionare da una tabella e trovare diversi primi valori ordinati, DISTINCT ONin questo caso non funzionerà. Non è una funzione aggregata, stai effettivamente filtrando i dati e quindi puoi farlo solo una volta.
DB140141,

6

Sì, ho scoperto un modo semplice con il tuo caso usando alcune funzionalità di PostgreSQL 9.4+

Vediamo questo esempio:

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

Spero che ti possa aiutare nel tuo caso.


Il problema con questa soluzione è che non funziona con DOMAINtipi di dati o altre piccole eccezioni. È anche molto più complesso e richiede tempo, costruendo un array dell'intero set di dati. La soluzione semplice sarebbe quella di creare un aggregato personalizzato, ma finora non ho trovato la soluzione ideale anche con quello. Anche le funzioni della finestra sono cattive, dal momento che non possono essere utilizzate nello stesso modo in cui è possibile utilizzare gli aggregati (con istruzioni FILTER o in CROSS JOIN LATERAL)
AlexanderMP

5

Non è una risposta diretta alla tua domanda, ma dovresti provare la first_valuefunzione finestra. Funziona così:

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

);

Quindi, se si desidera il primo elemento in ciascuna cat(categoria), verrà eseguita una query simile:

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

o:

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

Siamo spiacenti, non penso che questo valga per il mio caso d'uso. First_value non è una funzione di aggregazione, che mostra tutti i record di con un certo valore comune (il tuo esempio di gatto) che viene valutato come il primo in base a un ordine (la tua data di esempio). Il mio bisogno è diverso. Devo, nella stessa selezione, raggruppare diverse colonne scegliendo il primo valore non nullo. Cioè, dovrebbe generare un singolo record per ciascuna delle combinazioni di valori in GROUP BY.
Alexandre Neto,

2
Quanto sopra può essere fatto per il lavoro gettando distinta nel mix: select distinct x, first_value(y) over (partition by x), first_value(z) over (partition by x) from .... Probabilmente inefficiente ma abbastanza per me andare avanti con la prototipazione. Sicuramente qualcosa da rivedere!
Max Murphy,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.