私はこのようなテーブルを備えた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
は任意の数のthing
s(重複なし)を指すことができ、thing
は任意の数のcontainer
sで指すことができます。
コンテナや物が多数ある場合があります(私たちが持っている顧客の数によって異なります)。各コンテナには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;
おそらく何らかの方法で結合を使用して、これを行うより良い方法はありますか?
常に単一の行セットを生成するようです。これは、ポイントされているすべてのcontainer
のthing
データを複製することを意味します。
コンテナが空になる可能性がある場合、現在受け入れられているソリューションは機能しません。一致せずに行を保持するには、外部結合である必要があります-使用している相関サブクエリと同等の結果を得るには フィドルで :
select *, array(select thing_id from container_thing where container_id = container.id) as "thingIds" from container
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;
コンテナーあたりの行数が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;
または、見つかった 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つのクエリでは微妙に異なることに注意してください。
"thingIds":[null]
"thingIds":null
"thingIds":[]
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 ...
(container_idでの)結合は、この状況で非常にうまく機能します。はい、コンテナデータが重複した複数の行を取得します。
コーディング言語がORM(MyBatisやHibernate for Javaなど)をサポートしている場合、オブジェクトフィールドの冗長な複製が処理されます。 ORMはコンテナーのリストを返し、各コンテナーにはIDのリストがあります。このようなオブジェクトグラフからJSONを生成することは簡単です。
最新のデータベースでは、データの重複が原因で測定可能なパフォーマンスヒットが発生することはありません。
結合+ 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;
これは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で結合されます。