postgres를 사용하여 string_agg에서와 같이 array_agg에서 null 값을 제외하는 방법은 무엇입니까?


96

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() 와 같이 이름 만 표시됩니다 (빈 쉼표 또는 null 제외) .

문제는 내가 Postgres 8.4서버에 설치했고 string_agg()거기에서 작동하지 않는다는 것입니다. array_agg를 string_agg ()와 유사하게 작동시키는 방법이 있습니까?


: 많은이 주제에이 PostgreSQL의 메일 링리스트 스레드를 참조하십시오 postgresql.1045698.n5.nabble.com/...
크레이그 벨소리

나는, 그 스레드에서 솔루션이 미안 생각하지 않는다 있어요 ..
다우드

해당 스레드에는 두 가지 솔루션이 있습니다. 하나는 함수를 만드는 것이고 다른 하나는 내가 대답 한 것입니다.
Clodoaldo Neto

@Clodoaldo-모든 행은 ( 'y', 'n')에 정식으로 표시되므로 where 절은 중복되는 것 같습니다. 문제는 그룹 내에서 표준 필드의 값이 'Y'이고 'N'을 수집하면 null도 수집된다는 것입니다.
Daud

확인. 이제 알았습니다. 업데이트 답변을 확인하십시오.
Clodoaldo Neto

답변:


28

SQL 바이올린

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_stringnull을 제거하는 사용하면 더 간단하고 저렴할 수 있습니다 .

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 바이올린


감사. 그러나 기본 쿼리가 1000 개의 행을 반환하면 두 개의 하위 쿼리 (unnest 사용)가 각 행에 대해 한 번씩 실행됩니다. 2000 개의 추가 선택 쿼리를 실행하는 것보다 NULL을 허용하는 것이 더 낫습니까?
Daud

@Daud 더 저렴할 수있는 새 버전. 두 가지 모두의 설명 출력을 확인하십시오.
Clodoaldo Neto

3
@Clodoaldo array_to_string(array_agg(...))사용하는 경우 string_agg.
Craig Ringer

1
질문의 문제를 @Craig은 8.4입니다
클로도 알도 네토

@Clodoaldo Gah, 이전 버전. 감사.
Craig Ringer 2012 년

248

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 2015-09-08

2
제 경우에는 필터링해야하는 것이 null이기 때문에 9.4 변형이 훨씬 더 좋습니다.
coladict 2017 년

업데이트 된 버전을 먼저 사용했지만 Null과 중복을 제거해야한다는 것을 깨달았으므로 첫 번째 제안으로 돌아갔습니다. 큰 쿼리이지만 구체화 된 뷰를 생성하는 것이므로 큰 문제는 아닙니다.
Relequestual

12

배열 집합체에서 null을 제거하는 일반적인 문제를 해결하는 데는 array_agg (unnest (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 = '{}'
);

두 번째 호출은 (자연스럽게) 첫 번째 것보다 조금 더 멋지게 보입니다.

x에서 array_agg_notnull (v)을 선택하십시오.


9

이 스레드가 꽤 오래되었지만 이것을 추가하고 있지만 작은 배열에서 아주 잘 작동하는이 깔끔한 트릭을 만났습니다. 추가 라이브러리 나 기능없이 Postgres 8.4 이상에서 실행됩니다.

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

array_to_string()메서드는 실제로 null을 제거합니다.


9

배열에서 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 및 1000 요소)와 그 사이에 임의의 NULL을 사용하여 배정 밀도 숫자에 대한 테스트를 수행했습니다.


@VivekSinha 어떤 버전의 postgres를 사용하고 있습니까? 방금 귀하의 쿼리를 테스트 한 결과 "{1,2,3}"가 나왔습니다. 12.1을 사용하고 있습니다.
Alexi Theodore

아, 내 끝에서 무슨 일이 일어나고 있는지 @ alexi-theodore가 보입니다. 사용자 지정 + 수정 된 postgres 드라이버를 사용하고있었습니다. 콘솔에서 직접 쿼리하면 올바른 출력을 볼 수 있습니다! 혼란스러워서 죄송합니다. 이전 댓글 및 찬성 답변 삭제!
Vivek Sinha

3

주석에서 제안했듯이 배열의 null을 대체하는 함수를 작성할 수 있지만 주석에 연결된 스레드에서도 지적했듯이 이러한 종류는 집계를 만들어야하는 경우 집계 함수의 효율성을 떨어 뜨립니다. , 분할 한 다음 다시 집계합니다.

배열에 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

매우 간단합니다. 우선 text []에 대한 새 -(빼기) 연산자를 만듭니다 .

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}


1
array_agg(x) FILTER (WHERE x is not null): 훨씬 쉽게 보인다 dbfiddle.uk/... 당신이 정말로 자신의 기능을 필요로하지 않으며, 당신은 간단하게 사용할 수 있습니다 array_remove() dbfiddle.uk/...
a_horse_with_no_name

-6

더 큰 질문은 모든 사용자 / 그룹 콤보를 한 번에 끌어 오는 이유입니다. UI가 모든 데이터를 처리 할 수 ​​없음을 보장했습니다. 너무 큰 데이터에 페이징을 추가하는 것도 좋지 않습니다. 사용자가 데이터를보기 전에 세트를 필터링하도록하십시오. 원하는 경우 성능을 필터링 할 수 있도록 JOIN 옵션 세트가 목록에 있는지 확인하십시오. 때로는 두 개의 쿼리가 둘 다 빠르면 사용자를 더 행복하게 만듭니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.