Selecionar colunas dentro de json_agg

21

Eu tenho uma consulta como:

SELECT a.id, a.name, json_agg(b.*) as "item"
  FROM a
  JOIN b ON b.item_id = a.id
 GROUP BY a.id, a.name;

Como posso selecionar as colunas bpara não ter b.item_idno objeto JSON?

Eu li sobre ROW, mas ele retorna um objeto JSON como:

{"f1": "Foo", "f2": "Bar"}

Eu precisaria remapear o objeto JSON assim que ele for buscado para corresponder às chaves de coluna apropriadas. Eu gostaria de evitar isso e manter os nomes das colunas originais.

Yanick Rochon
fonte

Respostas:

50

Infelizmente, não há nenhuma disposição na sintaxe SQL para dizer "todas as colunas, exceto esta coluna" . Você pode atingir seu objetivo, digitando a lista restante de colunas em uma expressão do tipo linha :

SELECT a.id, a.name
     , json_agg((b.col1, b.col2, b.col3)) AS item
FROM   a
JOIN   b ON b.item_id = a.id
GROUP  BY a.id, a.name;

Isso é curto para a forma mais explícita: . ROW(b.col1, b.col2, b.col3)

No entanto, os nomes das colunas não são preservados nas expressões do tipo de linha. Você obtém nomes de chaves genéricos no objeto JSON dessa maneira. Vejo três opções para preservar os nomes das colunas originais:

1. Transmitir para o tipo registrado

Transmitir para um tipo de linha conhecido (registrado). Um tipo é registrado para cada tabela ou exibição existente ou com uma CREATE TYPEdeclaração explícita . Você pode usar uma tabela temporária para uma solução ad-hoc (vida útil durante a sessão):

CREATE TEMP TABLE x (col1 int, col2 text, col3 date);  -- use adequate data types!

SELECT a.id, a.name
     , json_agg((b.col1, b.col2, b.col3)::x) AS item
FROM   a
JOIN   b ON b.item_id = a.id
GROUP  BY a.id, a.name;

2. Use uma subseleção

Use uma subseleção para construir uma tabela derivada e referencie a tabela como um todo . Isso também carrega nomes de colunas. É mais detalhado, mas você não precisa de um tipo registrado:

SELECT a.id, a.name
     , json_agg((SELECT x FROM (SELECT b.col1, b.col2, b.col3) AS x)) AS item
FROM   a
JOIN   b ON b.item_id = a.id
GROUP  BY a.id, a.name;

3. json_build_object()no Postgres 9.4 ou posterior

SELECT a.id, a.name
     , json_agg(json_build_object('col1', b.col1, 'col2', b.col2, 'col3', b.col3)) AS item
FROM   a
JOIN   b ON b.item_id = a.id
GROUP  BY a.id, a.name;

Palavras-chave:

Semelhante para jsonbcom as respectivas funções jsonb_agg()e jsonb_build_object().

Para Postgres 9.5 ou posterior, veja também a resposta de a_horse com uma nova variante de sintaxe mais curta: O Postgres adicionou o operador menos -parajsonb dizer "todas as chaves, exceto essa chave" .
Desde o Postgres 10, "exceto várias chaves", é implementado com o mesmo operador usando text[]o 2º operando - como o mlt comentou.

Erwin Brandstetter
fonte
1
> ou várias teclas Observe que json (b) -text [] está disponível a partir de 10.
mlt
A solução 3 funcionou para mim como um encanto!
Luiz Fernando da Silva
17

A partir do 9.6, você pode simplesmente usar -para remover uma chave de um JSONB:

SELECT a.id, a.name, jsonb_agg(to_jsonb(b) - 'item_id') as "item"
FROM a
  JOIN b ON b.item_id = a.id
GROUP BY a.id, a.name;

to_jsonb(b)converterá a linha inteira e - 'item_id'removerá a chave com o nome item_idcujo resultado será agregado.

um cavalo sem nome
fonte
Esses novos recursos parecem ser o que o OP estava esperando. Eu adicionei um link para minha resposta.
Erwin Brandstetter 14/03
Quando tentei a variante de subseleção, recebi um erro relacionado à json_aggfunção:function json_agg(record) does not exist
fraxture
@fraxture: então você não está usando o Postgres 9.6
a_horse_with_no_name
Na verdade, esse era o problema. Existe alguma maneira de filtrar colunas na v9.2?
fraxture
8

Você pode realmente fazer isso sem grupo, usando subconsultas

SELECT 
  a.id, a.name, 
  ( 
    SELECT json_agg(item)
    FROM (
      SELECT b.c1 AS x, b.c2 AS y 
      FROM b WHERE b.item_id = a.id
    ) item
  ) AS items
FROM a;

retorna

{
  id: 1,
  name: "thing one",
  items:[
    { x: "child1", y: "child1 col2"},
    { x: "child2", y: "child2 col2"}
  ]
}

Este artigo de John Atten é realmente interessante e tem mais detalhes

redben
fonte
2

Descobri que é melhor criar o JSON e agregá-lo. por exemplo

with base as (
select a, b, ('{"ecks":"' || to_json(x) || '","wai":"' || to_json(y) || '","zee":"' || to_json(z) || '"}"')::json c
) select (a, b, array_to_json(array_agg(c)) as c)

Observe que isso pode ser feito como uma subconsulta se você não gostar de CTEs (ou tiver problemas de desempenho por causa do uso).

Observe também que, se você fizer muito isso, pode ser benéfico criar uma função para agrupar os pares de valores-chave para você, para que o código pareça mais limpo. Você passaria sua função (por exemplo) 'ecks', 'x'e ela retornaria "ecks": "x".

MikeM
fonte
1

Embora ainda não haja maneira de fazer nada sobre a seleção de todas as colunas, exceto um bit, mas você pode usar json_agg(to_json(b.col_1, b.col_2, b.col_3 ...))para obter uma matriz json de jsons, cada uma no formato {"col_1":"col_1 value", ...}.

Portanto, a consulta seria algo como:

SELECT a.id, a.name, json_agg(to_json(b.col_1,b.col_2,b.col_3...)) as item
  FROM a
  JOIN b ON b.item_id = a.id
GROUP BY a.id, a.name;

e retornaria linhas como:

id, name, item
8, the_name, [{"col_1":"value_1","col_2":"value_2","col_3":"value_3"...}, {"col_1":"value_1.2","col_2":"value_2.2","col_3":"value_3.2"...},...]
9, the_next_name, [{"col_1":"value_1.3","col_2":"value_2.3","col_3":"value_3.3"...},   {"col_1":"value_1.4","col_2":"value_2.4","col_3":"value_3.4"...},...]
...

(Estou no Postgres 9.5.3 agora e não tenho 100% de certeza quando esse suporte foi adicionado.)

David K
fonte
1

Você pode usar json_build_objectassim

SELECT 
  a.id, 
  a.name,
  json_agg(json_build_object('col1', b.col1, 'col2', b.col2) AS item
FROM a
JOIN b ON b.item_id = a.id
GROUP BY a.id, a.name;
Tan Duong
fonte