web-dev-qa-db-ja.com

Postgresは、結合テーブルのarray_aggに対して[]ではなく[null]を返します

Postgresでいくつかのオブジェクトとそのタグを選択しています。スキーマは非常にシンプルで、3つのテーブルがあります。

オブジェクトid

タグ付け_id | object_id | tag_id_

タグ_id | tag_

_array_agg_を使用してタグを1つのフィールドに集約して、次のようにテーブルを結合しています。

_SELECT objects.*,
    array_agg(tags.tag) AS tags,
FROM objects
LEFT JOIN taggings ON objects.id = taggings.object_id
LEFT JOIN tags ON tags.id = taggings.tag_id
_

ただし、オブジェクトにタグがない場合、Postgresはこれを返します。

_[ null ]
_

空の配列の代わりに。 タグがないときに空の配列を返すにはどうすればよいですか?nullタグが返されないことを再確認しました。

集合ドキュメント 「必要に応じて、合体機能を使用してゼロまたは空の配列をヌルに置き換えることができます」と言います。 COALESCE(ARRAY_AGG(tags.tag)) as tagsを試しましたが、それでもnullの配列を返します。私は2番目のパラメーターをさまざまなもの(COALESCE(ARRAY_AGG(tags.tag), ARRAY())など)にしようとしましたが、それらはすべて構文エラーになります。

37
Andy Ray

別のオプションはarray_remove(..., NULL)9.3で導入 )if tags.tagNOT NULL(それ以外の場合は、配列にNULL値を保持することもできますが、その場合は、単一の既存のNULLタグとNULLタグを区別できませんによる LEFT JOIN):

SELECT objects.*,
     array_remove(array_agg(tags.tag), NULL) AS tags,
FROM objects
LEFT JOIN taggings ON objects.id = taggings.object_id
LEFT JOIN tags ON tags.id = taggings.tag_id

タグが見つからない場合、空の配列が返されます。

29
Thomas Perl

9.4以降では、集約関数呼び出しを制限して、特定の基準に一致する行のみを続行できます:array_agg(tags.tag) filter (where tags.tag is not null)

15

ドキュメントでは、ゼロ行を集約するとnull値が得られると述べており、COALESCEの使用に関する注意事項はこの特定のケースに対処しています。

LEFT JOINの動作方法により、これはクエリには適用されません。一致する行をzeroが検出されると、oneヌルで満たされた行(および1つのヌル行の集合は、1つのヌル要素を持つ配列です)。

出力で[NULL][]に盲目的に置き換えたいと思うかもしれませんが、それからタグのないオブジェクトタグ付きオブジェクトを区別する能力を失いますtags.tagはnullです。アプリケーションロジックや整合性の制約により、この2番目のケースが許可されない場合がありますが、それが何とか忍び込んだ場合にnullタグを抑制しない理由です。

結合条件の反対側のフィールドがnullであるかどうかをチェックすることにより、タグのないオブジェクトを識別できます(または、一般に、LEFT JOINが一致しなかった場合に通知します)。だからあなたの場合、単に置き換える

array_agg(tags.tag)

CASE
  WHEN taggings.object_id IS NULL
  THEN ARRAY[]::text[]
  ELSE array_agg(tags.tag)
END
13
Nick Barnes

私はこれがそれを行うことがわかった:

COALESCE(ARRAY_AGG(tags.tag), ARRAY[]::TEXT[])

...仮定して tags.tagはテキストタイプです。

Postgresの古いバージョンでこれが機能しないかどうかはわかりませんが、ver。 9.6そしてそれは動作しているようで、CASE WHEN x IS NULL... GROUP BY...以前に提供されたソリューション。

3
user9645

ドキュメントには、NULLを含む配列が返されると書かれています。それを空の配列に変換したい場合、ちょっとした魔法をかける必要があります:

SELECT objects.id,
    CASE WHEN length((array_agg(tags.tag))[1]) > 0
    THEN array_agg(tags.tag) 
    ELSE ARRAY[]::text[] END AS tags
FROM objects
LEFT JOIN taggings ON objects.id = taggings.object_id
LEFT JOIN tags ON tags.id = taggings.tag_id
GROUP BY 1;

これは、タグがtextタイプ(またはそのバリアントのいずれか)であると想定しています。必要に応じてキャストを変更します。

ここでのコツは、[NULL]配列の最初の(そして唯一の)要素の長さが0であるため、tagsからデータが返された場合は集約を返し、そうでない場合は空の配列を作成します正しいタイプ。

ちなみに、coalesce()の使用に関するドキュメントの記述は少し不器用です。つまり、結果としてNULLが必要ない場合は、coalesce()を使用してそれを0または選択した他の出力に変換します。ただし、これを配列の代わりにarray elementsに適用する必要があります。これは、あなたの場合、解決策を提供しません。

1
Patrick