postgresを使用してstring_aggのようにarray_aggでnull値を除外する方法


95

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
Craig Ringer '29

私は、私は...そのスレッド内の溶液があると思いません申し訳ありません
ダウド

そのスレッドには2つのソリューションがあります。1つは関数を作成することであり、もう1つ(表示されないことをお勧めします)は私が回答したものです。
Clodoaldo Neto

@Clodoaldo-すべての行は( 'y'、 'n')で正規形になるので、where句は冗長であるようです。...その後、nullがあまりにも収集され、標準的なフィールドの値が「Y」である場合、問題は、そのグループ内にある、と私たちは「Nさんを集めている
ダウド

OK。今それを手に入れました。アップデートの回答を確認してください。
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_stringこれを使用すると、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フィドル


ありがとう。しかし、メインクエリが1000行を返す場合、2つのサブクエリ(unnestを使用)は各行に対して1回実行されます。
ダウド

@Daudより安価な新しいバージョン。両方の説明出力を確認してください。
Clodoaldo Neto

3
@Clodoaldo使用しているarray_to_string(array_agg(...))場合は、使用することもできますstring_agg
クレイグリンガー

1
@Craig質問の問題は8.4です
Clodoaldo Neto

@Clodoaldo Gah、古いバージョン。ありがとう。
クレイグリンガー、

244

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
私の場合にフィルターで取り除く必要があるのはnullであるため、9.4バリアントはさらに優れています。
coladict

私は最初に更新されたバージョンを使用しましたが、Nullと重複を削除する必要があることに気付いたため、最初の提案に戻りました。これは大きなクエリですが、マテリアライズドビューを作成するため、大きな問題ではありません。
Relequestual 2018

12

配列集合体からnullを削除するという一般的な問題を解決するには、問題を攻撃する主な方法として、array_agg(unnest(array_agg(x))を実行する方法とカスタム集合体を作成する方法の2つがあります。

1つ目は上記の形式です

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

2番目の呼び出しは、最初の呼び出しよりも(当然)少し見栄えが良くなります。

xからarray_agg_notnull(v)を選択します。


9

このスレッドはかなり古いにもかかわらず、これを追加していますが、小さな配列で非常にうまく機能するこの巧妙なトリックに遭遇しました。追加のライブラリや関数なしでPostgres 8.4以降で実行されます。

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

このarray_to_string()メソッドは実際にはnullを取り除きます。


8

配列から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()の速度が2倍を少し上回っていることを(高い信頼度で)証明しました。さまざまな配列サイズ(10、100、1000要素)とその間のランダムなNULLを使用して、倍精度の数値でテストを行いました。


@VivekSinhaどのバージョンのpostgresを使用していますか?クエリをテストしたところ、「{1,2,3}」が返されました。12.1を使用しています。
アレクシセオドア

ああ、私は@ 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


ありがとう。しかし、特定のグループ内の行を処理するために「ケース」が必要であり、サブクエリはそこで効率的ではありません
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}


array_agg(x) FILTER (WHERE x is not null)はるかに簡単に思えます:dbfiddle.uk/…そして実際には独自の関数は必要ありませんarray_remove() 。dbfiddle.uk
…を

-6

より大きな問題は、なぜすべてのユーザー/グループのコンボを一度にプルするのかということです。UIがそのすべてのデータを処理できないことを保証します。大きすぎるデータにページングを追加することも悪い考えです。ユーザーにデータを表示する前にセットをフィルターに掛けさせます。必要に応じてパフォーマンスをフィルタリングできるように、JOINオプションセットがリストに含まれていることを確認してください。2つのクエリが高速である場合、2つのクエリがユーザーを幸せにすることがあります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.