web-dev-qa-db-fra.com

Construire un objet JSON à partir de données de relation un-à-plusieurs dans une seule requête?

J'ai une base de données PostgreSQL 9.5.3 avec des tables comme celle-ci:

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)

Un container peut pointer vers n'importe quel nombre de things (sans doublons), et un thing peut être pointé par n'importe quel nombre de containers.

Il peut y avoir un grand nombre de conteneurs et d'autres choses (cela dépend du nombre de clients que nous avons). Chaque conteneur ne contiendrait probablement que 1 à 10 éléments. Nous interrogerons seulement environ 20 conteneurs à la fois, max. Un conteneur peut être vide et j'ai besoin de récupérer un tableau vide.

J'ai besoin de construire des objets json qui représentent des conteneurs, quelque chose comme ceci:

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

Cela fonctionne bien, mais je le fais en utilisant deux requêtes:

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

Je crée ensuite de façon procédurale le tableau "thingIds" pour chaque conteneur.

Plus tard, j'ai trouvé une solution avec des sous-requêtes corrélées qui fonctionne bien pour moi en ce moment.

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

Existe-t-il une meilleure façon de procéder, peut-être en utilisant une jointure d'une manière ou d'une autre?
Il semble qu'ils génèrent toujours un seul ensemble de lignes, ce qui signifierait la duplication des données container pour chaque thing qui est pointé.

5
antsyawn

Si les conteneurs peuvent être vides, la solution actuellement acceptée ne fonctionne pas pour vous. Il doit s'agir d'une jointure externe pour conserver les lignes sans correspondance - pour obtenir des résultats équivalents aux sous-requêtes corrélées que vous utilisez dans votre violon :

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.

Avec plus de quelques lignes par conteneur (vous en avez mentionné 20), il est généralement plus rapide d'agréger avant vous rejoignez:

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.

Ou vous pouvez combiner le constructeur ARRAY que vous avez trouvé avec 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;

Peut-être encore plus vite.

SQL Fiddle. (Extension @ @ a_horse's fiddle .)

Notez que le résultat pour les conteneurs vides est subtilement différent dans les trois requêtes ci-dessus:

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

4.

Dans Postgres 9.5 (puisque vous l'utilisez), vous pouvez également travailler avec jsonb et ses fonctionnalités et une sous-requête en moins:

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>);

Alternativement:

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

Une jointure (sur container_id) fonctionnera très bien dans cette situation. Oui, vous obtiendrez plusieurs lignes avec des données de conteneur dupliquées.

Si votre langage de codage prend en charge un ORM (par exemple MyBatis ou Hibernate pour Java), la duplication redondante des champs de conteneur sera gérée pour vous. L'ORM renverra une liste de conteneurs avec chaque conteneur ayant une liste d'ID. La production de JSON à partir d'un tel graphique d'objet devrait être triviale.

Avec les bases de données modernes, il ne devrait pas y avoir de performances mesurables en raison de la duplication des données.

1
kiwiron

Join + 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

Cela ressemble à un bon ajustement pour les fonctions JSON de Postgres:

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

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

1

Si quelqu'un a besoin de coder json Row to Whole table lié par colonne (id). C'est la voie à suivre:

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

Le résultat est donc que chaque ligne de main_table a une colonne (subrows_json) avec des données de ligne codées json de sub_table, jointes par main_table.id-> sub_table.parent_id J'espère que cela aide les autres.

0
vencedor