วิธีการยกเว้นค่า null ใน array_agg เช่นใน string_agg โดยใช้ postgres?


101

ถ้าฉันใช้array_aggเพื่อรวบรวมชื่อฉันจะได้ชื่อของฉันคั่นด้วยลูกน้ำ แต่ในกรณีที่มีnullค่า null นั้นจะถูกนำมาเป็นชื่อในการรวมด้วย ตัวอย่างเช่น :

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,Philแทนที่จะเป็นเพียงแค่Larry,Phil(ใน 9.1.2 ของฉันมันแสดงให้เห็นNULL,Larry,Phil) เช่นเดียวกับในซอนี้

แต่ถ้าฉันใช้string_agg()มันจะแสดงเฉพาะชื่อ (โดยไม่มีเครื่องหมายจุลภาคหรือว่างเปล่า) เหมือนที่นี่

ปัญหาคือฉันPostgres 8.4ติดตั้งบนเซิร์ฟเวอร์แล้วและstring_agg()ใช้งานไม่ได้ มีวิธีใดบ้างที่จะทำให้ array_agg ทำงานคล้ายกับ string_agg ()?


ดูหัวข้อรายชื่ออีเมล PostgreSQL ในหัวข้อนี้มาก: postgresql.1045698.n5.nabble.com/…
Craig Ringer

ฉันขอโทษฉันไม่คิดว่าจะมีวิธีแก้ปัญหาในกระทู้นั้น ..
Daud

มีสองวิธีแก้ปัญหาในเธรดนั้น หนึ่งคือการสร้างฟังก์ชั่นและอื่น ๆ (แนะนำเพียงไม่แสดง) คือสิ่งที่ฉันตอบ
Clodoaldo Neto

@Clodoaldo - แถวทั้งหมดจะมี canonical ใน ('y', 'n') ... ดังนั้นประโยคที่ดูเหมือนจะซ้ำซ้อน ปัญหาคือภายในการจัดกลุ่มถ้าค่าของช่อง Canonical เป็น 'Y' และเรากำลังรวบรวม 'N' ก็จะมีการรวบรวมค่าว่างด้วย ..
Daud

ตกลง. ตอนนี้ฉันเข้าใจแล้ว. ตรวจสอบคำตอบการอัปเดต
Clodoaldo Neto

คำตอบ:


28

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

หรือง่ายกว่าและอาจถูกกว่าโดยใช้array_to_stringซึ่งกำจัด 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


ขอบคุณ. แต่ถ้าแบบสอบถามหลักส่งคืน 1,000 แถวแบบสอบถามย่อย 2 รายการ (โดยใช้ไม่ที่สุด) จะทำงานหนึ่งครั้งสำหรับแต่ละแถว .. จะทนต่อ NULL ได้ดีกว่าการดำเนินการแบบสอบถามที่เลือกเพิ่มเติม 2,000 รายการหรือไม่
Daud

@Daud เวอร์ชันใหม่ซึ่งอาจมีราคาถูกกว่า ใช้ผลลัพธ์การอธิบายของทั้งสองเพื่อให้แน่ใจ
Clodoaldo Neto

3
@Clodoaldo ถ้าคุณใช้array_to_string(array_agg(...))คุณก็อาจจะใช้string_agg.
Craig Ringer

1
@ Craig ปัญหาในคำถามคือ 8.4
Clodoaldo Neto

@Clodoaldo Gah เวอร์ชั่นเก่า. ขอบคุณ.
Craig Ringer

256

ด้วย postgresql-9.3 คุณสามารถทำได้

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;

อัปเดต : ด้วย 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;

5
มันใช้งานได้และรวดเร็วและสวยงามมันช่วยแก้ปัญหาที่คล้ายกับ OP เหตุผลในการอัปเกรดเป็น 9.3 สำหรับผู้ที่ยังไม่ได้ทำ +1
Pavel V.

12
9.4 นั้นสง่างามมากยิ่งขึ้น ทำงานเหมือนมีเสน่ห์
jmgarnier

2
ตัวแปร 9.4 นั้นดีกว่าเพราะสิ่งที่ฉันต้องกรองออกไปในกรณีของฉันคือโมฆะ
coladict

ฉันใช้เวอร์ชันที่อัปเดตก่อน แต่หลังจากนั้นก็รู้ว่าฉันจำเป็นต้องลบ Nulls และรายการที่ซ้ำกันดังนั้นจึงกลับไปที่คำแนะนำแรก เป็นข้อความค้นหาขนาดใหญ่ แต่เพื่อสร้างมุมมองที่เป็นรูปธรรมจึงไม่ใช่ปัญหาใหญ่
เผยแพร่คำถาม

12

ในการแก้ปัญหาทั่วไปเกี่ยวกับการลบ null จากการรวมอาร์เรย์มีสองวิธีหลักในการโจมตีปัญหา: การทำ array_agg (ไม่ดีที่สุด (array_agg (x)) หรือการสร้างการรวมที่กำหนดเอง

แบบแรกคือแบบฟอร์มที่แสดงด้านบน :

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

ที่สอง:

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

การเรียกคนที่สองนั้น (โดยธรรมชาติ) ดูดีกว่าครั้งแรกเล็กน้อย:

เลือก array_agg_notnull (v) จาก x;


12

หากคุณกำลังมองหาคำตอบที่ทันสมัยสำหรับคำถามทั่วไปเกี่ยวกับวิธีการลบ NULL ออกจากอาร์เรย์นั่นคือ:

array_remove(your_array, NULL)

ฉันอยากรู้เกี่ยวกับประสิทธิภาพเป็นพิเศษและต้องการเปรียบเทียบสิ่งนี้กับทางเลือกที่ดีที่สุด:

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
;

การทดสอบ pgbench พิสูจน์แล้ว (ด้วยความมั่นใจสูง) ว่าarray_remove () เร็วกว่าสองเท่าเล็กน้อย ฉันได้ทดสอบตัวเลขความแม่นยำสองเท่าด้วยขนาดอาร์เรย์ที่หลากหลาย (10, 100 และ 1,000 องค์ประกอบ) และค่า NULL แบบสุ่มที่อยู่ระหว่าง


นอกจากนี้ยังเป็นที่น่าสังเกตว่าสามารถใช้เพื่อลบช่องว่าง (''! = NULL) แต่พารามิเตอร์ที่สองยอมรับanyelementและเนื่องจากเป็นไปได้มากว่าคุณจะระบุช่องว่างด้วยสตริงลิเทอรัลตรวจสอบให้แน่ใจว่าได้แคสต์ไปยังแบบฟอร์มที่คุณต้องการโดยปกติจะไม่ใช่อาร์เรย์

ตัวอย่างเช่น:

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

ถ้าคุณลอง:

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

จะถือว่า '' คือ TEXT [] (array) และจะทำให้เกิดข้อผิดพลาดนี้:

ข้อผิดพลาด: ลิเทอรัลอาร์เรย์ผิดรูปแบบ: ""


@VivekSinha คุณใช้ postgres รุ่นอะไร? ฉันเพิ่งทดสอบคำค้นหาของคุณและได้ผลลัพธ์เป็น "{1,2,3}" สำหรับฉัน ฉันใช้ 12.1
Alexi Theodore

อาฉันเห็น @ alexi-theodore เกิดอะไรขึ้นในตอนท้ายของฉัน ฉันใช้โปรแกรมควบคุม postgres ที่กำหนดเอง + แก้ไข เมื่อฉันสอบถามโดยตรงในคอนโซลฉันจะเห็นผลลัพธ์ที่ถูกต้อง! ขออภัยในความสับสน ลบความคิดเห็นก่อนหน้าและโหวตคำตอบ!
Vivek Sinha

อาจเป็นประโยชน์ที่จะทราบว่า array_remove ได้รับการสนับสนุนตั้งแต่ 9.3
Anatoly Rugalev

9

ฉันกำลังเพิ่มสิ่งนี้แม้ว่าเธรดนี้จะค่อนข้างเก่า แต่ฉันก็พบกับเคล็ดลับที่เป็นระเบียบซึ่งทำงานได้ดีกับอาร์เรย์ขนาดเล็ก ทำงานบน Postgres 8.4+ โดยไม่มีไลบรารีหรือฟังก์ชันเพิ่มเติม

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

array_to_string()วิธีจริงได้รับการกำจัดของ nulls


3

ตามที่ได้แนะนำไว้ในความคิดเห็นคุณสามารถเขียนฟังก์ชันเพื่อแทนที่ null ในอาร์เรย์ได้อย่างไรก็ตามตามที่ระบุไว้ในเธรดที่เชื่อมโยงกับในความคิดเห็นประเภทนี้จะเอาชนะประสิทธิภาพของฟังก์ชันการรวมหากคุณต้องสร้างการรวม แยกออกแล้วรวมอีกครั้ง

ฉันคิดว่าการเก็บค่าว่างในอาร์เรย์เป็นเพียงคุณสมบัติ (อาจไม่เป็นที่ต้องการ) ของ Array_Agg คุณสามารถใช้แบบสอบถามย่อยเพื่อหลีกเลี่ยงสิ่งนี้:

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


ขอบคุณ. แต่ฉันต้องการ 'case' เพื่อจัดการแถวภายในกลุ่มที่กำหนดและการสืบค้นย่อยจะไม่มีประสิทธิภาพที่นั่น
Daud

0

มันง่ายมากก่อนอื่นให้สร้างตัวดำเนินการ- (ลบ) ใหม่สำหรับข้อความ [] :

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

และเพียงแค่ลบอาร์เรย์ [null]:

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

นั่นคือทั้งหมด:

{Y, N}


2
array_agg(x) FILTER (WHERE x is not null)ดูเหมือนง่ายกว่ามาก: dbfiddle.uk/…และคุณไม่จำเป็นต้องใช้ฟังก์ชันของตัวเองจริงๆคุณสามารถใช้array_remove() dbfiddle.uk/…
a_horse_with_no_name

-6

คำถามที่ใหญ่กว่าคือทำไมต้องดึงคอมโบผู้ใช้ / กลุ่มทั้งหมดพร้อมกัน รับประกันว่า UI ของคุณไม่สามารถจัดการกับข้อมูลทั้งหมดได้ การเพิ่มเพจให้กับข้อมูลขนาดใหญ่ก็เป็นความคิดที่ไม่ดีเช่นกัน ให้ผู้ใช้ของคุณกรองชุดก่อนที่จะเห็นข้อมูล ตรวจสอบให้แน่ใจว่าชุดตัวเลือก JOIN ของคุณอยู่ในรายการเพื่อให้สามารถกรองประสิทธิภาพได้หากต้องการ บางครั้งคำค้นหา 2 รายการทำให้ผู้ใช้มีความสุขมากขึ้นหากทั้งคู่รวดเร็ว

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.