Di PostgreSQL, apakah ada fungsi agregat type-safe first ()?

21

Pertanyaan lengkap tulis ulang

Saya mencari fungsi agregat First ().

Di sini saya menemukan sesuatu yang hampir berfungsi:

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

Masalahnya adalah bahwa ketika kolom varchar (n) melewati fungsi first (), itu diubah menjadi varchar sederhana (tanpa ukuran). Mencoba mengembalikan kueri dalam fungsi sebagai RETURNS SETOF anyelement, saya mendapatkan kesalahan berikut:

GALAT: struktur kueri tidak cocok dengan tipe hasil fungsi Estado de SQL: 42804 Detalhe: Pengubahan karakter tipe yang dikembalikan tidak cocok dengan variasi tipe karakter yang diharapkan (40) di kolom 2. Konteks: Fungsi PL / pgSQL vsr_table_at_time (anyelement, timestamp tanpa zona waktu) ) baris 31 di KEMBALI QUERY

Di halaman wiki yang sama ada tautan ke Versi C dari fungsi yang akan menggantikan yang di atas. Saya tidak tahu cara menginstalnya, tetapi saya ingin tahu apakah versi ini dapat menyelesaikan masalah saya.

Sementara itu, apakah ada cara saya dapat mengubah fungsi di atas sehingga mengembalikan jenis kolom input yang sama persis?

Alexandre Neto
sumber

Jawaban:

18

DISTINCT ON()

Sama seperti catatan tambahan, inilah tepatnya yang DISTINCT ON()dilakukannya (jangan dikacaukan dengan DISTINCT)

SELECT DISTINCT ON ( expression [, ...] ) hanya menyimpan baris pertama dari setiap rangkaian di mana ekspresi yang diberikan sama . The DISTINCT ONekspresi diinterpretasikan menggunakan aturan yang sama seperti untuk ORDER BY(lihat di atas). Perhatikan bahwa "baris pertama" setiap set tidak dapat diprediksi kecuali ORDER BYdigunakan untuk memastikan bahwa baris yang diinginkan muncul terlebih dahulu. Sebagai contoh

Jadi jika Anda menulis,

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

Ini efektif

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

Dalam hal ini dibutuhkan yang pertama z. Ada dua perbedaan penting,

  1. Anda juga dapat memilih kolom lain tanpa biaya agregasi lebih lanjut ..

    SELECT DISTINCT ON(x,y) z, k, r, t, v
    FROM foo;
    -- ORDER BY z, k, r, t, v;
  2. Karena tidak ada GROUP BYAnda tidak dapat menggunakan agregat (nyata) dengannya.

    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;

Jangan lupa ORDER BY

Juga, sementara saya tidak berani maka saya akan sekarang

Perhatikan bahwa "baris pertama" dari setiap set tidak dapat diprediksi kecuali ORDER BY digunakan untuk memastikan bahwa baris yang diinginkan muncul terlebih dahulu. Sebagai contoh

Selalu gunakan ORDER BYdenganDISTINCT ON

Menggunakan Fungsi Agregat Teratur-Set

Saya membayangkan banyak orang mencari first_value, Fungsi Agregat Memerintahkan-Mengatur . Hanya ingin membuang itu di sana. Akan terlihat seperti ini, jika fungsinya ada:

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

Tapi, sayangnya Anda bisa melakukan ini.

SELECT a, b, percentile_disc(0) WITHIN GROUP (ORDER BY z)   
FROM foo
GROUP BY a,b;
Evan Carroll
sumber
1
Masalah dengan jawaban ini adalah bahwa itu hanya berfungsi jika Anda ingin SATU agregat dalam daftar pilih Anda, yang tidak tersirat oleh pertanyaan. Jika misalnya Anda ingin memilih dari satu tabel dan menemukan beberapa nilai pertama yang dipesan, DISTINCT ONtidak akan berfungsi dalam kasus ini. Ini bukan fungsi agregat, Anda sebenarnya memfilter data dan jadi Anda hanya bisa melakukannya sekali.
DB140141
6

Yay, saya sudah menemukan cara mudah dengan kasing Anda dengan menggunakan beberapa fitur di PostgreSQL 9.4+

Mari kita lihat contoh ini:

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

Saya harap ini akan membantu Anda dalam kasus Anda.

Mabu Kloesen
sumber
Masalahnya dengan solusi ini adalah tidak berfungsi dengan DOMAINtipe data, atau pengecualian kecil lainnya. Ini juga jauh lebih kompleks dan memakan waktu, membangun sebuah array dari seluruh kumpulan data. Solusi sederhananya adalah membuat agregat khusus, tetapi sejauh ini saya belum menemukan solusi ideal bahkan dengan itu. Fungsi jendela juga buruk, karena tidak dapat digunakan dengan cara yang sama seperti Anda dapat menggunakan agregat (dengan pernyataan FILTER, atau dalam CROSS JOIN LATERAL)
AlexanderMP
5

Bukan jawaban langsung untuk pertanyaan Anda tetapi Anda harus mencoba first_valuefungsi jendela. Ini berfungsi seperti ini:

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

);

Kemudian, jika Anda menginginkan item pertama di setiap cat(kategori) Anda akan meminta seperti itu:

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

atau:

SELECT
    cat,
    first_value(date) OVER w
FROM
    test
WINDOW w AS (PARTITION BY cat ORDER BY date);
Ghislain Leveque
sumber
Maaf, saya rasa ini tidak berlaku untuk use case saya. Nilai First_ bukan fungsi agregasi, yang menunjukkan semua rekaman dengan nilai umum tertentu (kucing contoh Anda) yang dievaluasi sebagai yang pertama menurut beberapa pesanan (tanggal contoh Anda). Kebutuhan saya berbeda. Saya perlu, dalam pemilihan yang sama, agregat beberapa kolom dengan memilih yang pertama bukan nilai nol. Artinya, harus menghasilkan catatan tunggal untuk masing-masing kombinasi nilai dalam GROUP BY.
Alexandre Neto
2
Di atas dapat dibuat untuk pekerjaan dengan melemparkan berbeda ke dalam campuran: select distinct x, first_value(y) over (partition by x), first_value(z) over (partition by x) from .... Mungkin tidak efisien tetapi cukup bagi saya untuk melanjutkan pembuatan prototipe. Pasti sesuatu untuk ditinjau kembali!
Max Murphy