postgres kullanarak string_agg'deki gibi array_agg'deki boş değerler nasıl hariç tutulur?


103

array_aggİsimleri toplamak için kullanırsam , isimlerimi virgülle ayırırım, ancak bir nulldeğer olması durumunda , bu boş da toplamda bir isim olarak alınır. Örneğin :

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;

,Larry,Philsadece yerine geri dönüyor Larry,Phil(9.1.2'mde gösteriyor NULL,Larry,Phil). gibi bu keman

Ben kullanırsanız Bunun yerine, string_agg()bu bana gibi (boş virgül veya boş değerlere olmadan) sadece adlarını gösterir burada

Sorun şu ki Postgres 8.4, sunucuya yükledim ve string_agg()orada çalışmıyor. Array_agg işlevinin string_agg () ile benzer çalışmasını sağlamanın bir yolu var mı?


Bu konuyla ilgili bu PostgreSQL posta listesi başlığına bakın: postgresql.1045698.n5.nabble.com/…
Craig Ringer

Üzgünüm, bu konu için bir çözüm olduğunu sanmıyorum ..
Daud

Bu başlıkta iki çözüm var. Biri bir işlev oluşturmaktır ve diğeri (sadece gösterilmemiştir önerilmektedir) yanıtladığımdır.
Clodoaldo Neto

@Clodoaldo - tüm satırlar ('y', 'n') 'de kanonik olacaktır ... bu nedenle nerede cümlesi gereksiz görünüyor. Sorun şu ki, bir gruplama içinde, kanonik alanın değeri 'Y' ise ve '
N'leri

Tamam. Şimdi anladım. Güncelleme cevabını kontrol edin.
Clodoaldo Neto

Yanıtlar:


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

Veya daha basit ve daha ucuz olabilir, array_to_stringhangisi boşları ortadan kaldırır:

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


Teşekkürler. Ancak ana sorgu (lar) 1000 satır döndürürse, 2 alt sorgu (iç içe olmayan) her satır için bir kez çalışacaktır. NULL'ları tolere etmek, fazladan 2000 seçme sorgusu yürütmekten daha mı iyi olur?
Daud

@Daud Daha ucuz olabilecek yeni sürüm. Emin olmak için her ikisinin de açıklama çıktısını alın.
Clodoaldo Neto

3
@Clodoaldo Eğer kullanıyorsanız array_to_string(array_agg(...)), kullanabilirsiniz string_agg.
Craig Ringer

1
@Craig Sorudaki sorun 8.4
Clodoaldo Neto

@Clodoaldo Gah, eski versiyonlar. Teşekkürler.
Craig Ringer

261

Postgresql-9.3 ile bunu yapabilirsiniz;

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;

Güncelleme : postgresql-9.4 ile;

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;

5
Bu işe yarıyor ve hızlı ve zarif, bana OP'lere benzer bir sorunu çözdü. Henüz yapmayanlar için 9.3'e yükseltmek için bir neden. +1
Pavel V.

12
9.4 daha da zarif. Cazibe gibi çalışıyor
jmgarnier

2
9.4 varyantı daha da iyidir, çünkü benim durumumda filtrelemem gereken şey boş değerler.
coladict

Önce güncellenmiş sürümü kullandım, ancak daha sonra Boşlukları ve kopyaları kaldırmam gerektiğini fark ettim, bu yüzden ilk öneriye geri döndüm. Bu büyük bir sorgu, ancak somut bir görünüm oluşturmak için, bu yüzden büyük bir sorun değil.
Yayınlanma tarihi

14

Bir diziden NULL'un nasıl kaldırılacağına dair genel soruya modern bir cevap arıyorsanız , bu:

array_remove(your_array, NULL)

Performansı özellikle merak ediyordum ve bunu mümkün olan en iyi alternatifle karşılaştırmak istedim:

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
;

Bir pgbench testi yapmak, array_remove () 'un iki katından biraz daha hızlı olduğunu (büyük bir güvenle) kanıtladı . Testimi çeşitli dizi boyutları (10, 100 ve 1000 eleman) ve aralarında rastgele NULL bulunan çift duyarlıklı sayılar üzerinde yaptım.


Bunun boşlukları kaldırmak için kullanılabileceğini de belirtmek gerekir (''! = NULL). Ancak ikinci parametre kabul ederanyelement ve büyük olasılıkla bir dizge ile bir boşluk belirtiyor olacağınızdan, istediğiniz forma, genellikle dizi olmayan bir şekle dönüştürdüğünüzden emin olun.

Örneğin:

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

Eğer denersen:

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

'' metninin [] (dizi) olduğunu varsayacak ve şu hatayı atacaktır:

HATA: yanlış biçimlendirilmiş dizi değişmez değeri: ""


@VivekSinha postgres'in hangi sürümünü kullanıyorsunuz? Sorgunuzu az önce test ettim ve benim için "{1,2,3}" ile sonuçlandı. 12.1 kullanıyorum.
Alexi Theodore

Ah, @ alexi-theodore benim tarafımda neler olduğunu görüyorum. Özel + değiştirilmiş bir postgres sürücüsü kullanıyordum. Doğrudan konsolda sorguladığımda, doğru çıktıyı görebiliyorum! Karışıklık için üzgünüm. Önceki yorum silindi ve olumlu oy verildi!
Vivek Sinha

Muhtemelen array_remove'un 9.3
Anatoly Rugalev

12

Dizi kümelerinden boş değerleri kaldırma genel sorusunu çözerken, soruna saldırmanın iki ana yolu vardır: dizi_agg yapmak (unnest (dizi_agg (x))) veya özel bir toplam oluşturmak.

Birincisi yukarıda gösterilen formdadır :

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

İkinci:

/*
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 = '{}'
);

İkincisini aramak (doğal olarak) birincisinden biraz daha hoş görünmek:

x'ten array_agg_notnull (v) öğesini seçin;


9

Bu iş parçacığı oldukça eski olmasına rağmen bunu ekliyorum, ancak küçük dizilerde oldukça iyi çalışan bu düzgün numarayla karşılaştım. Ek kitaplıklar veya işlevler olmadan Postgres 8.4+ üzerinde çalışır.

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

array_to_string()Yöntem aslında boş değerlere kurtulur.


3

Yorumlarda önerildiği gibi, bir dizideki boş değerleri değiştirmek için bir işlev yazabilirsiniz, ancak yorumlarda bağlantılı iş parçacığında da belirtildiği gibi, bu tür bir toplama oluşturmak zorunda kalırsanız toplama işlevinin verimliliğini bozar. , bölün ve sonra tekrar toplayın.

Sanırım dizide null değerleri tutmak Array_Agg'in sadece (belki de istenmeyen) bir özelliği. Bunu önlemek için alt sorgular kullanabilirsiniz:

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


Teşekkürler. Ancak belirli bir gruplamadaki satırları işlemek için 'vakaya' ihtiyacım vardı ve alt sorgular orada verimsiz olurdu
Daud

0

Çok basit, her şeyden önce metin [] için yeni bir - (eksi) işleci oluşturun :

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

Ve basitçe [boş] dizisini çıkarın:

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

Bu kadar:

{E, H}


2
array_agg(x) FILTER (WHERE x is not null)çok daha kolay görünüyor: dbfiddle.uk/… ve gerçekten kendi işlevinize ihtiyacınız yok, array_remove() dbfiddle.uk/… 'i
a_horse_with_no_name

-6

Daha büyük bir soru, neden tüm kullanıcı / grup kombinasyonlarını aynı anda çekmektir. Kullanıcı arayüzünüzün tüm bu verileri işleyemeyeceğini garanti ediyoruz. Büyük boyutlu verilere sayfalama eklemek de kötü bir fikirdir. Kullanıcılarınızın verileri görmeden önce grubu filtrelemesini sağlayın. JOIN seçenek kümenizin listede olduğundan emin olun, böylece isterlerse performans için filtre uygulayabilirler. Bazen 2 sorgu, her ikisi de hızlıysa kullanıcıları daha mutlu eder.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.