web-dev-qa-db-ja.com

多くの結合の右側を配列に変換します

多対多の関係で結合を使用すると、結果は複数の行に分割されます。私がやりたいのは、結合の右側を配列に変換して、結果が1行になるようにすることです。

例は私がそれをよりよく説明します。

だから私はそれらの3つのテーブルを持っています:

CREATE TABLE items (
  id    serial primary key,
  title text
);

CREATE TABLE tags (
  id    serial primary key,
  title text
);

CREATE TABLE items_tags (
  item_id int references items(id),
  tag_id  int references tags(id),
  primary key (item_id, tag_id)
);

タグの付いたアイテムを選択するときは、次のようにします。

SELECT i.id, i.title, i.title
FROM items i
INNER JOIN items_tags it
ON it.item_id = i.id
INNER JOIN tags t
ON t.id = it.tag_id;

そして結果は次のようになります:

(1, "item n1", "sport")
(1, "item n1", "soccer")
(2, "item n2", "adventure")
(2, "item n2", "mountain climbing")
(2, "item n2", "sport")
(2, "item n2", "nature")

私が欲しいのはこれです:

(1, "item n1", ["sport", "soccer"])
(2, "item n2", ["adventure", "mountain climbing", "sport" , "nature"])
13
Ced

まず、クエリにタイプミスがあります。 3列目は_t.title_になります。明確にするためにエイリアスを追加しました:

_SELECT i.id, i.title AS item_title, t.title AS tag_title
FROM   items      i
JOIN   items_tags it ON it.item_id = i.id
JOIN   tags       t  ON t.id = it.tag_id;
_

余談ですが、「id」または「title」は通常、あまり区別されず、あまり有用な識別子ではありません。詳細はこちら:

クエリのすべてまたはほとんどのアイテムを実行する場合、通常、最初に集計してから結合する方がかなり高速です。

_SELECT id, i.title AS item_title, t.tag_array
FROM   items      i
JOIN  (  -- or LEFT JOIN ?
   SELECT it.item_id AS id, array_agg(t.title) AS tag_array
   FROM   items_tags it
   JOIN   tags       t  ON t.id = it.tag_id
   GROUP  BY it.item_id
   ) t USING (id);
_

_LEFT JOIN_で除外されるタグのないアイテムがある場合は、外部クエリで_INNER JOIN_を使用します。

結合before集約も、FROMリストに1つ以上の1:nテーブルがあるため、手に負えなくなります(この単純なケースではありません)。比較:

小さな選択の場合、ARRAYコンストラクタを使用したLATERAL結合を検討します:

_SELECT id, title AS item_title, t.tag_array
FROM   items i, LATERAL (  -- this is an implicit CROSS JOIN
   SELECT ARRAY (
      SELECT t.title
      FROM   items_tags it
      JOIN   tags       t  ON t.id = it.tag_id
      WHERE  it.item_id = i.id
      ) AS tag_array
   ) t;
_

ARRAYコンストラクタは常に行を生成するため(サブクエリが空の場合は空の配列で)、LEFT JOIN LATERAL (...) ON trueはここでは使用されません。

関連:

15

group by句を追加し、array_aggを使用する必要があります。

SELECT i.id, i.title, array_agg(i.title)
FROM items i
INNER JOIN items_tags it
ON it.item_id = i.id
INNER JOIN tags t
ON t.id = it.tag_id
GROUP BY i.id, i.title,
5
Evan Carroll