PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Utilisation de row_to_json() avec des jointures imbriquées

Mise à jour :dans PostgreSQL 9.4, cela s'améliore beaucoup avec l'introduction de to_json , json_build_object , json_object et json_build_array , bien qu'il soit verbeux en raison de la nécessité de nommer explicitement tous les champs :

select
        json_build_object(
                'id', u.id,
                'name', u.name,
                'email', u.email,
                'user_role_id', u.user_role_id,
                'user_role', json_build_object(
                        'id', ur.id,
                        'name', ur.name,
                        'description', ur.description,
                        'duty_id', ur.duty_id,
                        'duty', json_build_object(
                                'id', d.id,
                                'name', d.name
                        )
                )
    )
from users u
inner join user_roles ur on ur.id = u.user_role_id
inner join role_duties d on d.id = ur.duty_id;

Pour les anciennes versions, lisez la suite.

Ce n'est pas limité à une seule ligne, c'est juste un peu douloureux. Vous ne pouvez pas attribuer d'alias aux types de lignes composites à l'aide de AS , vous devez donc utiliser une expression de sous-requête avec alias ou CTE pour obtenir l'effet :

select row_to_json(row)
from (
    select u.*, urd AS user_role
    from users u
    inner join (
        select ur.*, d
        from user_roles ur
        inner join role_duties d on d.id = ur.duty_id
    ) urd(id,name,description,duty_id,duty) on urd.id = u.user_role_id
) row;

produit, via http://jsonprettyprint.com/ :

{
  "id": 1,
  "name": "Dan",
  "email": "[email protected]",
  "user_role_id": 1,
  "user_role": {
    "id": 1,
    "name": "admin",
    "description": "Administrative duties in the system",
    "duty_id": 1,
    "duty": {
      "id": 1,
      "name": "Script Execution"
    }
  }
}

Vous voudrez utiliser array_to_json(array_agg(...)) quand vous avez une relation 1:plusieurs, au fait.

La requête ci-dessus devrait idéalement pouvoir s'écrire :

select row_to_json(
    ROW(u.*, ROW(ur.*, d AS duty) AS user_role)
)
from users u
inner join user_roles ur on ur.id = u.user_role_id
inner join role_duties d on d.id = ur.duty_id;

... mais le ROW de PostgreSQL le constructeur n'accepte pas AS alias de colonne. Malheureusement.

Heureusement, ils optimisent la même chose. Comparez les forfaits :

  • La version de la sous-requête imbriquée ; contre
  • Ce dernier imbriqué ROW version du constructeur avec les alias supprimés pour qu'il s'exécute

Étant donné que les CTE sont des barrières d'optimisation, reformuler la version de la sous-requête imbriquée pour utiliser des CTE chaînés (WITH expressions) peuvent ne pas fonctionner aussi bien et ne donneront pas le même plan. Dans ce cas, vous êtes en quelque sorte coincé avec des sous-requêtes imbriquées laides jusqu'à ce que nous obtenions des améliorations à row_to_json ou un moyen de remplacer les noms de colonne dans un ROW constructeur plus directement.

Quoi qu'il en soit, en général, le principe est que là où vous voulez créer un objet json avec des colonnes a, b, c , et vous aimeriez pouvoir simplement écrire la syntaxe illégale :

ROW(a, b, c) AS outername(name1, name2, name3)

vous pouvez à la place utiliser des sous-requêtes scalaires renvoyant des valeurs de type ligne :

(SELECT x FROM (SELECT a AS name1, b AS name2, c AS name3) x) AS outername

Ou :

(SELECT x FROM (SELECT a, b, c) AS x(name1, name2, name3)) AS outername

De plus, gardez à l'esprit que vous pouvez composer json valeurs sans guillemets supplémentaires, par ex. si vous mettez la sortie d'un json_agg dans un row_to_json , le json_agg interne le résultat ne sera pas cité sous forme de chaîne, il sera incorporé directement en tant que json.

par exemple. dans l'exemple arbitraire :

SELECT row_to_json(
        (SELECT x FROM (SELECT
                1 AS k1,
                2 AS k2,
                (SELECT json_agg( (SELECT x FROM (SELECT 1 AS a, 2 AS b) x) )
                 FROM generate_series(1,2) ) AS k3
        ) x),
        true
);

la sortie est :

{"k1":1,
 "k2":2,
 "k3":[{"a":1,"b":2}, 
 {"a":1,"b":2}]}

Notez que le json_agg produit, [{"a":1,"b":2}, {"a":1,"b":2}] , n'a pas été échappé à nouveau, comme text serait.

Cela signifie que vous pouvez composer json pour construire des lignes, vous n'avez pas toujours besoin de créer des types composites PostgreSQL extrêmement complexes, puis d'appeler row_to_json sur la sortie.