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.