sql >> Base de Datos >  >> RDS >> PostgreSQL

Usar row_to_json() con uniones anidadas

Actualización:en PostgreSQL 9.4 esto mejora mucho con la introducción de to_json , json_build_object , json_object y json_build_array , aunque es detallado debido a la necesidad de nombrar todos los campos explícitamente:

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;

Para versiones anteriores, sigue leyendo.

No se limita a una sola fila, es solo un poco doloroso. No puede alias tipos de filas compuestas usando AS , por lo que debe usar una expresión de subconsulta con alias o CTE para lograr el efecto:

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;

produce, a través de 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"
    }
  }
}

Querrás usar array_to_json(array_agg(...)) cuando tienes una relación de 1:muchos, por cierto.

Idealmente, la consulta anterior debería poder escribirse como:

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;

... pero ROW de PostgreSQL el constructor no acepta AS alias de columna. Tristemente.

Afortunadamente, optimizan lo mismo. Compara los planes:

  • La versión de la subconsulta anidada; contra
  • El último ROW anidado versión del constructor con los alias eliminados para que se ejecute

Dado que los CTE son barreras de optimización, reformular la versión de la subconsulta anidada para usar CTE encadenados (WITH expresiones) pueden no funcionar tan bien y no darán como resultado el mismo plan. En este caso, está atascado con subconsultas anidadas feas hasta que obtengamos algunas mejoras en row_to_json o una forma de anular los nombres de las columnas en un ROW constructor más directamente.

De todos modos, en general, el principio es que donde quieras crear un objeto json con columnas a, b, c , y desearía poder escribir la sintaxis ilegal:

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

en su lugar, puede usar subconsultas escalares que devuelvan valores de tipo fila:

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

O:

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

Además, tenga en cuenta que puede componer json valores sin cotizaciones adicionales, p. si pones la salida de un json_agg dentro de un row_to_json , el json_agg interno El resultado no se citará como una cadena, se incorporará directamente como json.

p.ej. en el ejemplo arbitrario:

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 salida es:

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

Tenga en cuenta que json_agg producto, [{"a":1,"b":2}, {"a":1,"b":2}] , no se ha vuelto a escapar, como text sería.

Esto significa que puedes componer json para construir filas, no siempre tiene que crear tipos compuestos de PostgreSQL enormemente complejos y luego llamar a row_to_json en la salida.