PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Usando row_to_json() com junções aninhadas


Atualização:No PostgreSQL 9.4 isso melhora muito com a introdução do to_json , json_build_object , json_object e json_build_array , embora seja detalhado devido à necessidade de nomear todos os campos explicitamente:
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 versões mais antigas, continue lendo.

Não se limita a uma única linha, é apenas um pouco doloroso. Você não pode criar alias para tipos de linha compostos usando AS , então você precisa usar uma expressão de subconsulta com alias ou CTE para obter o efeito:
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;

produz, 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"
    }
  }
}

Você vai querer usar array_to_json(array_agg(...)) quando você tem um relacionamento 1:muitos, btw.

A consulta acima deve, idealmente, ser escrita 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;

... mas a ROW do PostgreSQL construtor não aceita AS aliases de coluna. Infelizmente.

Felizmente, eles otimizam o mesmo. Compare os planos:
  • A versão da subconsulta aninhada; vs
  • O último aninhado ROW versão do construtor com os aliases removidos para que seja executado

Como os CTEs são cercas de otimização, reformular a versão da subconsulta aninhada para usar CTEs encadeados (WITH expressões) pode não funcionar tão bem e não resultará no mesmo plano. Nesse caso, você está preso a subconsultas aninhadas feias até obtermos algumas melhorias em row_to_json ou uma maneira de substituir os nomes das colunas em um ROW construtor mais diretamente.

De qualquer forma, em geral, o princípio é que onde você deseja criar um objeto json com colunas a, b, c , e você gostaria de escrever a sintaxe ilegal:
ROW(a, b, c) AS outername(name1, name2, name3)

em vez disso, você pode usar subconsultas escalares que retornam valores digitados por linha:
(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

Além disso, lembre-se de que você pode compor json valores sem cotação adicional, por ex. se você colocar a saída de um json_agg dentro de um row_to_json , o json_agg interno result não será citado como uma string, ele será incorporado diretamente como json.

por exemplo. no exemplo arbitrário:
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
);

a saída é:
{"k1":1,
 "k2":2,
 "k3":[{"a":1,"b":2}, 
 {"a":1,"b":2}]}

Observe que o json_agg produto, [{"a":1,"b":2}, {"a":1,"b":2}] , não foi escapado novamente, pois text seria.

Isso significa que você pode compor json para construir linhas, você nem sempre precisa criar tipos compostos PostgreSQL extremamente complexos e chamar row_to_json na saída.