私の問題については、1つの写真に多くのタグとコメントも含まれるスキーマがあります。そのため、すべてのコメントとタグを必要とするクエリがある場合、行を一緒に乗算します。したがって、1つの写真に2つのタグと13のコメントがある場合、その1つの写真に対して26行が取得されます。
_SELECT
tag.name,
comment.comment_id
FROM
photo
LEFT OUTER JOIN comment ON comment.photo_id = photo.photo_id
LEFT OUTER JOIN photo_tag ON photo_tag.photo_id = photo.photo_id
LEFT OUTER JOIN tag ON photo_tag.tag_id = tag.tag_id
_
ほとんどの場合これで問題ありませんが、_GROUP BY
_に続いてjson_agg(tag.*)
にすると、最初のタグのコピーが13個、2番目のタグのコピーが13個得られることを意味します。
_SELECT json_agg(tag.name) as tags
FROM
photo
LEFT OUTER JOIN comment ON comment.photo_id = photo.photo_id
LEFT OUTER JOIN photo_tag ON photo_tag.photo_id = photo.photo_id
LEFT OUTER JOIN tag ON photo_tag.tag_id = tag.tag_id
GROUP BY photo.photo_id
_
代わりに、次のように「郊外」と「都市」のみの配列が必要です。
_ [
{"tag_id":1,"name":"suburban"},
{"tag_id":2,"name":"city"}
]
_
json_agg(DISTINCT tag.name)
はできますが、これは行全体をjsonにしたい場合にのみタグ名の配列を作成します。 json_agg(DISTINCT ON(tag.name) tag.*)
を使いたいのですが、明らかに有効なSQLではありません。
どうすればPostgresの集約関数内で_DISTINCT ON
_をシミュレートできますか?
中央テーブルがあり、それをテーブルAの多くの行に左結合し、さらにテーブルBの多くの行に左結合したい場合、行を複製するというこれらの問題が発生します。特に注意しないと、COUNT
やSUM
などの集約関数がスローされます。そのため、各写真用タグと各写真用コメントを別々に作成し、それらを結合する必要があると思います。
WITH tags AS (
SELECT photo.photo_id, json_agg(row_to_json(tag.*)) AS tags
FROM photo
LEFT OUTER JOIN photo_tag on photo_tag.photo_id = photo.photo_id
LEFT OUTER JOIN tag ON photo_tag.tag_id = tag.tag_id
GROUP BY photo.photo_id
),
comments AS (
SELECT photo.photo_id, json_agg(row_to_json(comment.*)) AS comments
FROM photo
LEFT OUTER JOIN comment ON comment.photo_id = photo.photo_id
GROUP BY photo.photo_id
)
SELECT COALESCE(tags.photo_id, comments.photo_id) AS photo_id,
tags.tags,
comments.comments
FROM tags
FULL OUTER JOIN comments
ON tags.photo_id = comments.photo_id
編集: CTEなしですべてを本当に結合したい場合、これは正しい結果を与えるように見えます:
SELECT photo.photo_id,
to_json(array_agg(DISTINCT tag.*)) AS tags,
to_json(array_agg(DISTINCT comment.*)) AS comments
FROM photo
LEFT OUTER JOIN comment ON comment.photo_id = photo.photo_id
LEFT OUTER JOIN photo_tag on photo_tag.photo_id = photo.photo_id
LEFT OUTER JOIN tag ON photo_tag.tag_id = tag.tag_id
GROUP BY photo.photo_id
最も安価で簡単なDISTINCT
操作は、最初に「プロキシ相互結合」で行を乗算しないことです。最初に集約、then join。見る:
仮定実際にはテーブル全体を取得するのではなく、一度に1つまたはいくつかの選択した写真だけを取得します。集約された詳細、最もエレガントでおそらく最速の方法はLATERAL
サブクエリを使用することです:
_SELECT *
FROM photo p
CROSS JOIN LATERAL (
SELECT json_agg(c) AS comments
FROM comment c
WHERE photo_id = p.photo_id
) c1
CROSS JOIN LATERAL (
SELECT json_agg(t) AS tags
FROM photo_tag pt
JOIN tag t USING (tag_id)
WHERE pt.photo_id = p.photo_id
) t
WHERE p.photo_id = 2; -- arbitrary selection
_
これは、JSON配列に個別に集約されたcomment
およびtag
からwhole rowsを返します。行は、あなたの試みのような乗算ではありませんが、ベーステーブルにあるのと同じくらい「異なる」だけです。
ベースデータ内の重複をさらに折りたたむには、以下を参照してください。
ノート:
LATERAL
およびjson_agg()
にはPostgres9.3以降が必要です。
json_agg(c)
はjson_agg(c.*)
の短縮形です。
json_agg()
のような集約関数は常に行を返すため、_LEFT JOIN
_は必要ありません。
通常、列のサブセットのみが必要です-- 少なくとも冗長な_photo_id
_を除く:
_SELECT *
FROM photo p
CROSS JOIN LATERAL (
SELECT json_agg(json_build_object('comment_id', comment_id
, 'comment', comment)) AS comments
FROM comment
WHERE photo_id = p.photo_id
) c
CROSS JOIN LATERAL (
SELECT json_agg(t) AS tags
FROM photo_tag pt
JOIN tag t USING (tag_id)
WHERE pt.photo_id = p.photo_id
) t
WHERE p.photo_id = 2;
_
json_build_object()
はPostgresで導入されました9.4。 ROW
コンストラクターは列名を保持しないため、以前のバージョンでは面倒でした。ただし、一般的な回避策があります。
また、JSONキー名を自由に選択できるため、列名に固執する必要はありません。
すべての行を返すには、これがより効率的です。
_SELECT p.*
, COALESCE(c1.comments, '[]') AS comments
, COALESCE(t.tags, '[]') AS tags
FROM photo p
LEFT JOIN (
SELECT photo_id
, json_agg(json_build_object('comment_id', comment_id
, 'comment', comment)) AS comments
FROM comment c
GROUP BY 1
) c1 USING (photo_id)
LEFT JOIN LATERAL (
SELECT photo_id , json_agg(t) AS tags
FROM photo_tag pt
JOIN tag t USING (tag_id)
GROUP BY 1
) t USING (photo_id);
_
十分な行を取得すると、LATERAL
サブクエリよりも安くなります。 Postgres9.3 +で動作します。
結合条件のUSING
句に注意してください。このようにして、_SELECT *
_の重複する列を取得することなく、外部クエリで_photo_id
_を便利に使用できます。ここで_SELECT *
_を使用しなかったのは、削除された答えがあなたが望むことを示しているからです[〜#〜] null [〜#〜]の代わりに空のJSON配列タグ/コメントなし。
データ型json
には等値演算子がないため、単にjson_agg(DISTINCT json_build_object(...))
することはできません。見る:
さまざまな優れた方法があります。
_SELECT *
FROM photo p
CROSS JOIN LATERAL (
SELECT json_agg(to_json(c1.comment)) AS comments1
, json_agg(json_build_object('comment', c1.comment)) AS comments2
, json_agg(to_json(c1)) AS comments3
FROM (
SELECT DISTINCT c.comment -- folding dupes here
FROM comment c
WHERE c.photo_id = p.photo_id
-- ORDER BY comment -- any particular order?
) c1
) c2
CROSS JOIN LATERAL (
SELECT jsonb_agg(DISTINCT t) AS tags -- demonstrating jsonb_agg
FROM photo_tag pt
JOIN tag t USING (tag_id)
WHERE pt.photo_id = p.photo_id
) t
WHERE p.photo_id = 2;
_
_comments1
_、_comments2
_、_comments3
_(冗長)およびtags
の4つの異なる手法を示します。
db <> fiddle here
古い SQL Fiddle Postgres 9.3にバックパッチ
古い SQL Fiddle Postgres 9.6の場合
コメントで述べたように、json_aggは行をオブジェクトとしてシリアル化しませんが、渡す値のJSON配列を構築します。あなたは必要になるでしょう row_to_json
行をJSONオブジェクトに変換してからjson_agg
配列への集約を実行するには:
SELECT json_agg(DISTINCT row_to_json(comment)) as tags
FROM
photo
LEFT OUTER JOIN comment ON comment.photo_id = photo.photo_id
LEFT OUTER JOIN photo_tag ON photo_tag.photo_id = photo.photo_id
LEFT OUTER JOIN tag ON photo_tag.tag_id = tag.tag_id
GROUP BY photo.photo_id