bagaimana cara mengecualikan nilai null di array_agg seperti di string_agg menggunakan postgres?

103

Jika saya menggunakan array_agguntuk mengumpulkan nama, saya mendapatkan nama saya dipisahkan dengan koma, tetapi jika ada nullnilai, null itu juga diambil sebagai nama dalam agregat. Sebagai contoh :

SELECT g.id,
       array_agg(CASE WHEN g.canonical = 'Y' THEN g.users ELSE NULL END) canonical_users,
       array_agg(CASE WHEN g.canonical = 'N' THEN g.users ELSE NULL END) non_canonical_users
FROM groups g
GROUP BY g.id;

itu kembali ,Larry,Philbukan hanya Larry,Phil(di 9.1.2 saya, itu menunjukkan NULL,Larry,Phil). seperti di biola ini

Sebaliknya, jika saya menggunakan string_agg(), ini hanya menunjukkan nama (tanpa koma kosong atau null) seperti di sini

Masalahnya adalah saya telah Postgres 8.4menginstal di server, dan string_agg()tidak berfungsi di sana. Apakah ada cara untuk membuat array_agg berfungsi mirip dengan string_agg ()?

Daud
sumber
Lihat utas milis PostgreSQL ini tentang banyak topik ini: postgresql.1045698.n5.nabble.com/…
Craig Ringer
Maaf, saya rasa tidak ada solusi di utas itu ..
Daud
Ada dua solusi di utas itu. Salah satunya adalah untuk membuat fungsi dan yang lainnya (hanya disarankan tidak ditampilkan) adalah yang saya jawab.
Clodoaldo Neto
@Clodoaldo - semua baris akan memiliki kanonik di ('y', 'n') ... jadi klausa where tampaknya berlebihan. Masalahnya adalah di dalam pengelompokan, jika nilai bidang kanonik adalah 'Y', dan kita mengumpulkan 'N, maka nol dikumpulkan juga ..
Daud
Baik. Sekarang saya paham. Periksa jawaban pembaruan.
Clodoaldo Neto

Jawaban:

29

SQL Fiddle

select
    id,
    (select array_agg(a) from unnest(canonical_users) a where a is not null) canonical_users,
    (select array_agg(a) from unnest(non_canonical_users) a where a is not null) non_canonical_users
from (
    SELECT g.id,
           array_agg(CASE WHEN g.canonical = 'Y' THEN g.users ELSE NULL END) canonical_users,
           array_agg(CASE WHEN g.canonical = 'N' THEN g.users ELSE NULL END) non_canonical_users
    FROM groups g
    GROUP BY g.id
) s

Atau, lebih sederhana dan mungkin lebih murah, menggunakan array_to_stringyang menghilangkan nulls:

SELECT
    g.id,
    array_to_string(
        array_agg(CASE WHEN g.canonical = 'Y' THEN g.users ELSE NULL END)
        , ','
    ) canonical_users,
    array_to_string(
        array_agg(CASE WHEN g.canonical = 'N' THEN g.users ELSE NULL END)
        , ','
    ) non_canonical_users
FROM groups g
GROUP BY g.id

SQL Fiddle

Clodoaldo Neto
sumber
Terima kasih. Tetapi jika kueri utama mengembalikan 1000 baris, maka 2 subkueri (menggunakan tidak terestimasi) akan berjalan sekali untuk setiap baris .. Akankah lebih baik untuk mentolerir NULL daripada mengeksekusi 2000 kueri pemilihan tambahan?
Daud
@Daud Versi baru yang bisa lebih murah. Ambil hasil penjelasan dari keduanya untuk memastikan.
Clodoaldo Neto
3
@Clodoaldo Jika Anda menggunakan array_to_string(array_agg(...))Anda mungkin juga menggunakan string_agg.
Craig Ringer
1
@Craig Masalah dalam pertanyaannya adalah 8,4
Clodoaldo Neto
@Clodoaldo Gah, versi lama. Terima kasih.
Craig Ringer
263

Dengan postgresql-9.3 seseorang dapat melakukan ini;

SELECT g.id,
   array_remove(array_agg(CASE WHEN g.canonical = 'Y' THEN g.users ELSE NULL END), NULL) canonical_users,
   array_remove(array_agg(CASE WHEN g.canonical = 'N' THEN g.users ELSE NULL END), NULL) non_canonical_users
FROM groups g 
GROUP BY g.id;

Perbarui : dengan postgresql-9.4;

SELECT g.id,
   array_agg(g.users) FILTER (WHERE g.canonical = 'Y') canonical_users,
   array_agg(g.users) FILTER (WHERE g.canonical = 'N') non_canonical_users
FROM groups g 
GROUP BY g.id;
Dale O'Brien
sumber
5
Ini bekerja dan cepat dan elegan, ini memecahkan masalah yang mirip dengan OP. Alasan untuk mengupgrade ke 9.3 bagi mereka yang belum melakukannya. +1
Pavel V.
12
9.4 bahkan lebih elegan. Bekerja seperti pesona
jmgarnier
2
Varian 9.4 bahkan lebih baik, karena yang perlu saya filter dalam kasus saya adalah nulls.
coladict
Saya menggunakan versi yang diperbarui terlebih dahulu, tetapi kemudian menyadari bahwa saya perlu menghapus Null dan duplikat, jadi kembali ke saran pertama. Ini pertanyaan yang besar, tetapi ini untuk membuat tampilan terwujud, jadi bukan masalah besar.
Relequest
15

Jika Anda mencari jawaban modern untuk pertanyaan umum tentang cara menghapus NULL dari array , itu adalah:

array_remove(your_array, NULL)

Saya secara khusus ingin tahu tentang kinerja dan ingin membandingkannya dengan alternatif terbaik:

CREATE OR REPLACE FUNCTION strip_nulls(
    IN array_in ANYARRAY
)
RETURNS anyarray AS
'
SELECT
    array_agg(a)
FROM unnest(array_in) a
WHERE
    a IS NOT NULL
;
'
LANGUAGE sql
;

Melakukan uji pgbench membuktikan (dengan keyakinan tinggi) bahwa array_remove () sedikit lebih dari dua kali lebih cepat . Saya melakukan pengujian pada angka presisi ganda dengan berbagai ukuran larik (10, 100, dan 1000 elemen) dan NULL acak di antaranya.


Perlu juga dicatat bahwa ini dapat digunakan untuk menghapus blank (''! = NULL). Tetapi parameter kedua menerima anyelement, dan karena kemungkinan besar mereka Anda akan menunjukkan kosong dengan literal string, pastikan untuk mentransmisikannya ke formulir yang Anda inginkan, biasanya non-array.

Sebagai contoh:

select array_remove(array['abc', ''], ''::text);

Jika kamu mencoba:

select array_remove(array['abc', ''], '');

itu akan berasumsi bahwa '' adalah TEXT [] (array) dan akan menampilkan kesalahan ini:

GALAT: format literal salah: ""

Alexi Theodore
sumber
@VivekSinha versi postgres apa yang Anda gunakan? Saya baru saja menguji kueri Anda dan hasilnya menjadi "{1,2,3}" untuk saya. Saya menggunakan 12.1.
Alexi Theodore
Ah, saya melihat @ alexi-theodore apa yang terjadi di akhir saya. Saya menggunakan driver postgres kustom + yang dimodifikasi. Ketika saya menanyakan langsung di konsol, saya dapat melihat hasil yang benar! Maaf atas kebingungannya. Menghapus komentar sebelumnya dan jawaban yang diberi suara positif!
Vivek Sinha
Mungkin akan membantu untuk mencatat bahwa array_remove didukung sejak 9.3
Anatoly Rugalev
12

Dalam menyelesaikan pertanyaan umum untuk menghapus null dari agregat array, ada dua cara utama untuk mengatasi masalah: melakukan array_agg (tidak terkendali (array_agg (x)) atau membuat agregat kustom.

Yang pertama adalah dari bentuk yang ditunjukkan di atas :

SELECT 
    array_agg(u) 
FROM (
    SELECT 
        unnest(
            array_agg(v)
        ) as u 
    FROM 
        x
    ) un
WHERE 
    u IS NOT NULL;

Kedua:

/*
With reference to
http://ejrh.wordpress.com/2011/09/27/denormalisation-aggregate-function-for-postgresql/
*/
CREATE OR REPLACE FUNCTION fn_array_agg_notnull (
    a anyarray
    , b anyelement
) RETURNS ANYARRAY
AS $$
BEGIN

    IF b IS NOT NULL THEN
        a := array_append(a, b);
    END IF;

    RETURN a;

END;
$$ IMMUTABLE LANGUAGE 'plpgsql';

CREATE AGGREGATE array_agg_notnull(ANYELEMENT) (
    SFUNC = fn_array_agg_notnull,
    STYPE = ANYARRAY,
    INITCOND = '{}'
);

Memanggil yang kedua (secara alami) terlihat sedikit lebih baik daripada yang pertama:

pilih array_agg_notnull (v) dari x;

rorycl
sumber
9

Saya menambahkan ini meskipun utas ini cukup tua, tetapi saya mengalami trik rapi ini yang bekerja cukup baik pada array kecil. Ini berjalan di Postgres 8.4+ tanpa perpustakaan atau fungsi tambahan.

string_to_array(array_to_string(array_agg(my_column)))::int[]

The array_to_string()Metode sebenarnya menghilangkan nulls.

ced-b
sumber
3

Seperti yang telah disarankan dalam komentar, Anda dapat menulis fungsi untuk menggantikan null dalam array, namun seperti yang juga ditunjukkan di utas yang ditautkan di komentar, jenis ini mengalahkan efisiensi fungsi agregat jika Anda harus membuat agregat. , pisahkan, lalu kumpulkan lagi.

Saya pikir menjaga null dalam array hanyalah fitur (mungkin tidak diinginkan) dari Array_Agg. Anda dapat menggunakan subkueri untuk menghindari ini:

SELECT  COALESCE(y.ID, n.ID) ID,
        y.Users,
        n.Users
FROM    (   SELECT  g.ID, ARRAY_AGG(g.Users) AS Users
            FROM    Groups g
            WHERE   g.Canonical = 'Y'
            GROUP BY g.ID
        ) y
        FULL JOIN 
        (   SELECT  g.ID, ARRAY_AGG(g.Users) AS Users
            FROM    Groups g
            WHERE   g.Canonical = 'N'
            GROUP BY g.ID
        ) n
            ON n.ID = y.ID

SQL FIDDLE

GarethD
sumber
Terima kasih. Tetapi saya membutuhkan 'kasus' untuk menangani baris dalam pengelompokan tertentu, dan subkueri akan menjadi tidak efisien di sana
Daud
0

Ini sangat sederhana, pertama-tama buat operator - (minus) baru untuk teks [] :

CREATE OR REPLACE FUNCTION diff_elements_text
    (
        text[], text[] 
    )
RETURNS text[] as 
$$
    SELECT array_agg(DISTINCT new_arr.elem)
    FROM
        unnest($1) as new_arr(elem)
        LEFT OUTER JOIN
        unnest($2) as old_arr(elem)
        ON new_arr.elem = old_arr.elem
    WHERE old_arr.elem IS NULL
$$ LANGUAGE SQL IMMUTABLE;

CREATE OPERATOR - (
    PROCEDURE = diff_elements_text,
    leftarg = text[],
    rightarg = text[]
);

Dan cukup kurangi array [null]:

select 
    array_agg(x)-array['']
from
    (   select 'Y' x union all
        select null union all
        select 'N' union all
        select '' 
    ) x;

Itu saja:

{Y, T}

Miklos
sumber
2
array_agg(x) FILTER (WHERE x is not null)tampaknya jauh lebih mudah: dbfiddle.uk/… dan Anda tidak benar-benar membutuhkan fungsi Anda sendiri, Anda cukup menggunakan array_remove() dbfiddle.uk/…
a_horse_with_no_name
-6

Pertanyaan yang lebih besar adalah mengapa menarik semua kombo pengguna / grup sekaligus. Dijamin UI Anda tidak dapat menangani semua data itu. Menambahkan paging ke data yang terlalu besar juga merupakan ide yang buruk. Minta pengguna Anda untuk memfilter kumpulan sebelum mereka melihat data. Pastikan kumpulan opsi GABUNG Anda ada dalam daftar sehingga mereka dapat memfilter kinerja jika mereka mau. Terkadang 2 kueri membuat pengguna lebih bahagia jika keduanya cepat.

Michael
sumber