web-dev-qa-db-ja.com

1つのクエリで1対多の関係データからJSONオブジェクトを作成しますか?

私はこのようなテーブルを備えたPostgreSQL 9.5.3 DBを持っています。

container
    id: uuid (pk)
    ... other data

thing
    id: uuid (pk)
    ... other data

container_thing
    container_id: uuid (fk)
    thing_id: uuid (fk)
    primary key (container_id, thing_id)

containerは任意の数のthings(重複なし)を指すことができ、thingは任意の数のcontainersで指すことができます。

コンテナや物が多数ある場合があります(私たちが持っている顧客の数によって異なります)。各コンテナには1〜10個のアイテムしか含まれていない可能性があります。一度にクエリできるのは、最大で約20のコンテナのみです。コンテナは空にすることができ、空の配列を取得する必要があります。

次のようなコンテナを表すjsonオブジェクトを構築する必要があります。

{
    "id": "d7e1bc6b-b659-432d-b346-29f3a530bfa9",
    ... other data
    "thingIds": [
        "4e3ad81b-f2b5-4220-8e0e-e9d53c80a214",
        "f26f49e5-76b4-4363-9ffe-9654ba0b0f0d"
    ]
}

これは正常に機能していますが、2つのクエリを使用して実行しています。

select * from "container" where "id" in (<list of container ids>)
select * from "container_thing" where "container_id" in (<list of container ids>)

次に、各コンテナの「thingIds」配列を手続き的に構築します。

後で私は 相関するサブクエリを使用したソリューション を見つけましたが、現時点ではうまく機能しています。

select *, array(select thing_id from container_thing where container_id = c.id) as "thingIds"
from container c;

おそらく何らかの方法で結合を使用して、これを行うより良い方法はありますか?
常に単一の行セットを生成するようです。これは、ポイントされているすべてのcontainerthingデータを複製することを意味します。

5
antsyawn

コンテナが空になる可能性がある場合、現在受け入れられているソリューションは機能しません。一致せずに行を保持するには、外部結合である必要があります-使用している相関サブクエリと同等の結果を得るには フィドルで

select *, array(select thing_id from container_thing where container_id = container.id) as "thingIds"
from container

1。

SELECT to_json(sub) AS container_with_things
FROM  (
   SELECT c.*, json_agg(thing_id) AS "thingIds"
   FROM   container c
   LEFT   JOIN container_thing ct ON  ct.container_id = c.id
   WHERE  c.id IN (<list of container ids>)
   GROUP  BY c.id
   ) sub;

2。

コンテナーあたりの行数が20を超える場合(20と記述)、通常は、結合する前に集計する方が高速です

SELECT to_json(sub) AS container_with_things
FROM  (
   SELECT c.*, ct."thingIds"
   FROM   container c
   LEFT   JOIN (
      SELECT container_id AS id, json_agg(thing_id) AS "thingIds"
      FROM   container_thing
      WHERE  container_id IN (<list of container ids>) -- repeat condition
      GROUP  BY 1
      ) ct USING (id)
   WHERE  c.id IN (<list of container ids>)
   ) sub;

3。

または、見つかった ARRAYコンストラクタLEFT JOIN LATERALと組み合わせることができます。

SELECT to_json(sub) AS container_with_things
FROM  (
   SELECT c.*, ct."thingIds"
   FROM   container c
   LEFT   JOIN LATERAL (
      SELECT ARRAY (
         SELECT thing_id
         FROM   container_thing
         WHERE  container_id = c.id
         -- ORDER  BY thing_id  -- optional order for deterministic results
         ) AS "thingIds"
      ) ct ON true
   WHERE  c.id IN (<list of container ids>)
   ) sub;

まだ速いかもしれません。

SQL Fiddle。 (Extending @ a_horse's fiddle 。)

空のコンテナの結果は、上記の3つのクエリでは微妙に異なることに注意してください。

  1. "thingIds":[null]
  2. "thingIds":null
  3. "thingIds":[]

4。

Postgres9.5では(使用しているため) jsonbとその機能 および1つ少ないサブクエリ:

SELECT jsonb_set(to_jsonb(c), '{thingIds}', "thingIds") AS container_with_things
FROM   container c
LEFT   JOIN (
   SELECT container_id AS id, jsonb_agg(thing_id) AS "thingIds"
   FROM   container_thing
   WHERE  container_id IN (<list of container ids>) -- repeat condition
   GROUP  BY 1
   ) ct USING (id)
WHERE  c.id IN (<list of container ids>);

または:

SELECT to_jsonb(c) || jsonb_build_object('thingIds', "thingIds") AS container_with_things
FROM   ... 
11

(container_idでの)結合は、この状況で非常にうまく機能します。はい、コンテナデータが重複した複数の行を取得します。

コーディング言語がORM(MyBatisやHibernate for Javaなど)をサポートしている場合、オブジェクトフィールドの冗長な複製が処理されます。 ORMはコンテナーのリストを返し、各コンテナーにはIDのリストがあります。このようなオブジェクトグラフからJSONを生成することは簡単です。

最新のデータベースでは、データの重複が原因で測定可能なパフォーマンスヒットが発生することはありません。

1
kiwiron

結合+ string_agg()

select '{"id": "' || cast (c.id as varchar(36)) ||', "thingIds": ["'
   || string_agg(cast(ct.thing_id as varchar(36)),'","') 
   || '"]}'
from container c
join container_thing ct on c.id=ct.container_id
group by c.id;
1
Serg

これはPostgresのJSON関数に適しているように見えます。

select to_json(x)
from (
  select c.*, json_agg(ct.thing_id) as "thingIds"
  from container_thing ct
    join container c on ct.container_id = c.id
  group by c.id
) x

SQLFiddleの例: http://sqlfiddle.com/#!15/cd9992/1

列(ID)でリンクされたエンコードされた行からテーブル全体へのJSONが必要な場合。これは行く方法です:

SELECT main_table.*, subVirt.subrows_json
FROM   main_table
LEFT   JOIN (
  SELECT parent_id, json_agg(row_to_json(sub_table)) AS subrows_json
  FROM  sub_table
  GROUP  BY 1
  ) subVirt ON subVirt.parent_id = main_table.id

その結果、main_tableの各行には、sub_tableのjsonエンコードされた行データを含む列(subrows_json)があり、main_table.id-> sub_table.parent_idで結合されます。

0
vencedor