làm thế nào để loại trừ các giá trị null trong array_agg như trong string_agg bằng cách sử dụng postgres?


96

Nếu tôi sử dụng array_aggđể thu thập tên, tôi sẽ đặt tên của mình được phân tách bằng dấu phẩy, nhưng trong trường hợp có nullgiá trị, giá trị rỗng đó cũng được lấy làm tên trong tổng hợp. Ví dụ :

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;

nó trả về ,Larry,Philthay vì chỉ Larry,Phil(trong 9.1.2 của tôi, nó hiển thị NULL,Larry,Phil). như trong này fiddle

Thay vào đó, nếu tôi sử dụng string_agg(), nó chỉ hiển thị cho tôi các tên (không có dấu phẩy hoặc dấu trống) như ở đây

Vấn đề là tôi đã Postgres 8.4cài đặt trên máy chủ, và string_agg()không hoạt động ở đó. Có cách nào để làm cho array_agg hoạt động tương tự như string_agg () không?


Xem này PostgreSQL mailing list thread về nhiều chủ đề này: postgresql.1045698.n5.nabble.com/...
Craig Ringer

Tôi xin lỗi, tôi không nghĩ rằng có một giải pháp trong chủ đề đó ..
Daud

Có hai giải pháp trong chủ đề đó. Một là để tạo một chức năng và cái kia (chỉ đề xuất không hiển thị) là một trong những tôi đã trả lời.
Clodoaldo Neto 29/10/12

@Clodoaldo - tất cả các hàng sẽ có canonical in ('y', 'n') ... vì vậy mệnh đề where có vẻ là thừa. Vấn đề là bên trong một nhóm, nếu giá trị của trường chuẩn là 'Y' và chúng tôi đang thu thập 'N, thì giá trị rỗng cũng sẽ được thu thập ..
Daud 29/10/12

Đồng ý. Bây giờ tôi đã hiểu nó. Kiểm tra câu trả lời cập nhật.
Clodoaldo Neto 29/10/12

Câu trả lời:


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

Hoặc, đơn giản hơn và có thể rẻ hơn, bằng cách sử dụng array_to_stringloại bỏ null:

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


Cảm ơn. Nhưng nếu (các) truy vấn chính trả về 1000 hàng, thì 2 truy vấn phụ (sử dụng unnest) sẽ chạy một lần cho mỗi hàng .. Liệu việc dung nạp NULL có tốt hơn việc thực hiện 2000 truy vấn chọn bổ sung không?
Daud 29/10/12

@Daud Phiên bản mới có thể rẻ hơn. Lấy đầu ra giải thích của cả hai để chắc chắn.
Clodoaldo Neto 29/10/12

3
@Clodoaldo Nếu bạn đang sử dụng, array_to_string(array_agg(...))bạn cũng có thể sử dụng string_agg.
Craig Ringer

1
@Craig Các vấn đề trong câu hỏi là 8.4
Clodoaldo Neto

@Clodoaldo Gah, các phiên bản cũ. Cảm ơn.
Craig Ringer

248

Với postgresql-9.3 người ta có thể làm điều này;

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;

Cập nhật : với 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
Điều này hoạt động nhanh và thanh lịch, nó đã giải quyết cho tôi một vấn đề tương tự như OP. Một lý do để nâng cấp lên 9.3 cho những người chưa làm điều đó. +1
Pavel V.

12
9,4 thậm chí còn thanh lịch hơn. Hoạt động như một sự quyến rũ
jmgarnier

2
Biến thể 9.4 thậm chí còn tốt hơn, bởi vì những gì tôi cần lọc đi trong trường hợp của mình là các giá trị rỗng.
coladict

Tôi đã sử dụng phiên bản cập nhật đầu tiên, nhưng sau đó nhận ra rằng tôi cần phải loại bỏ Null và các bản sao, vì vậy quay lại đề xuất đầu tiên. Đó là một truy vấn lớn, nhưng nó là để tạo ra một cái nhìn cụ thể hóa, vì vậy không phải là một vấn đề lớn.
Bản phát hành

12

Khi giải quyết câu hỏi chung về việc loại bỏ null khỏi tổng hợp mảng, có hai cách chính để tấn công vấn đề: thực hiện array_agg (unnest (array_agg (x)) hoặc tạo một tổng hợp tùy chỉnh.

Đầu tiên là dạng hiển thị ở trên :

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

Thư hai:

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

Gọi thứ hai là (tự nhiên) trông đẹp hơn một chút so với thứ nhất:

chọn array_agg_notnull (v) from x;


9

Tôi đang thêm điều này mặc dù chủ đề này khá cũ, nhưng tôi đã gặp phải thủ thuật gọn gàng này hoạt động khá tốt trên các mảng nhỏ. Nó chạy trên Postgres 8.4+ mà không có thư viện hoặc chức năng bổ sung.

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

Các array_to_string()phương pháp thực sự được thoát khỏi những null.


9

Nếu bạn đang tìm kiếm câu trả lời hiện đại cho câu hỏi chung về cách xóa NULL khỏi một mảng , đó là:

array_remove(your_array, NULL)

Tôi đặc biệt tò mò về hiệu suất và muốn so sánh điều này với giải pháp thay thế tốt nhất có thể:

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
;

Thực hiện kiểm tra pgbench đã chứng minh (với độ tin cậy cao) rằng array_remove () nhanh hơn một chút . Tôi đã thực hiện thử nghiệm của mình trên các số chính xác kép với nhiều kích thước mảng (10, 100 và 1000 phần tử) và các NULL ngẫu nhiên ở giữa.


@VivekSinha bạn đang sử dụng phiên bản postgres nào? Tôi vừa kiểm tra truy vấn của bạn và kết quả là "{1,2,3}" cho tôi. Tôi đang sử dụng 12.1.
Alexi Theodore

Ah, tôi thấy @ alexi-theodore những gì đang xảy ra ở cuối của tôi. Tôi đang sử dụng trình điều khiển postgres tùy chỉnh + sửa đổi. Khi tôi truy vấn trực tiếp trong bảng điều khiển, tôi có thể thấy đầu ra phù hợp! Xin lỗi về sự nhầm lẫn. Đã xóa bình luận trước và câu trả lời được ủng hộ!
Vivek Sinha

3

Như đã được đề xuất trong các nhận xét, bạn có thể viết một hàm để thay thế các null trong một mảng, tuy nhiên, như đã chỉ ra trong chuỗi được liên kết trong các nhận xét, loại này sẽ đánh bại hiệu quả của hàm tổng hợp nếu bạn phải tạo một tổng hợp , tách nó ra rồi tổng hợp lại.

Tôi nghĩ rằng việc giữ null trong mảng chỉ là một tính năng (có lẽ không mong muốn) của Array_Agg. Bạn có thể sử dụng truy vấn con để tránh điều này:

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


Cảm ơn. Nhưng tôi cần 'trường hợp' để xử lý hàng trong vòng một cho nhóm, và truy vấn con sẽ không hiệu quả có
Daud

0

Nó rất đơn giản, trước hết hãy tạo một toán tử - (dấu trừ) mới cho văn bản [] :

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

Và chỉ cần trừ mảng [null]:

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

Đó là tất cả:

{Y, N}


1
array_agg(x) FILTER (WHERE x is not null)dường như dễ dàng hơn nhiều: dbfiddle.uk/... và bạn không thực sự cần chức năng của riêng bạn, bạn chỉ có thể sử dụng array_remove() dbfiddle.uk/...
a_horse_with_no_name

-6

Một câu hỏi lớn hơn là tại sao lại kéo tất cả các tổ hợp người dùng / nhóm cùng một lúc. Đảm bảo rằng giao diện người dùng của bạn không thể xử lý tất cả dữ liệu đó. Thêm phân trang vào dữ liệu quá khổ cũng là một ý tưởng tồi. Yêu cầu người dùng của bạn lọc tập hợp trước khi họ xem dữ liệu. Đảm bảo bộ tùy chọn THAM GIA của bạn có trong danh sách để họ có thể lọc hiệu suất nếu họ muốn. Đôi khi 2 truy vấn khiến người dùng vui hơn nếu cả hai đều nhanh.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.