Pilih kolom di dalam json_agg

21

Saya punya pertanyaan seperti:

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;

Bagaimana saya bisa memilih kolom bjadi saya tidak punya b.item_iddi objek JSON?

Saya telah membaca tentang ROW, tetapi mengembalikan objek JSON seperti:

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

Saya perlu memetakan kembali objek JSON setelah diambil untuk mencocokkan kunci kolom yang tepat. Saya ingin menghindari itu dan tetap menggunakan nama kolom asli.

Yanick Rochon
sumber

Jawaban:

50

Sayangnya, tidak ada ketentuan dalam sintaks SQL untuk mengatakan "semua kolom kecuali kolom yang satu ini" . Anda dapat mencapai tujuan Anda dengan mengeja daftar kolom yang tersisa dalam ekspresi tipe baris :

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;

Itu pendek untuk bentuk yang lebih eksplisit: . ROW(b.col1, b.col2, b.col3)

Namun, nama kolom tidak dipertahankan dalam ekspresi tipe baris. Anda mendapatkan nama kunci umum dalam objek JSON dengan cara ini. Saya melihat 3 opsi untuk mempertahankan nama kolom asli:

1. Pilih tipe yang terdaftar

Pilih tipe baris yang terkenal (terdaftar). Suatu tipe terdaftar untuk setiap tabel atau tampilan yang ada atau dengan CREATE TYPEpernyataan eksplisit . Anda mungkin menggunakan tabel sementara untuk solusi ad-hoc (berlaku selama durasi sesi):

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. Gunakan subselect

Gunakan subselect untuk membangun tabel turunan dan referensi tabel secara keseluruhan . Ini juga membawa nama kolom. Ini lebih bertele-tele, tetapi Anda tidak perlu tipe terdaftar:

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()dalam Postgres 9.4 atau lebih baru

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;

Terkait:

Mirip untuk jsonbmasing-masing fungsi jsonb_agg()dan jsonb_build_object().

Untuk Postgres 9.5 atau yang lebih baru, lihat juga jawaban a_horse dengan varian sintaks baru yang lebih pendek: Postgres menambahkan operator minus -untukjsonb mengatakan "semua kunci kecuali kunci yang satu ini" .
Karena Postgres 10 "kecuali beberapa kunci" diimplementasikan dengan operator yang sama dengan text[]mlt seperti - operan 2 berkomentar.

Erwin Brandstetter
sumber
1
> atau beberapa tombol Perhatikan bahwa json (b) -text [] tersedia mulai dari 10.
mlt
Solusi 3 bekerja untuk saya seperti pesona!
Luiz Fernando da Silva
17

Mulai dengan 9.6 yang bisa Anda gunakan -untuk menghapus kunci dari 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)akan mengonversi seluruh baris dan - 'item_id'kemudian akan menghapus kunci dengan nama item_idhasil yang kemudian dikumpulkan.

seekor kuda tanpa nama
sumber
Fitur-fitur baru ini tampaknya menjadi harapan OP. Saya menambahkan tautan ke jawaban saya.
Erwin Brandstetter
Ketika saya mencoba varian subselect, saya mendapatkan kesalahan terkait dengan json_aggfungsi:function json_agg(record) does not exist
fraxture
@fraxture: maka Anda tidak menggunakan Postgres 9.6
a_horse_with_no_name
Memang itulah masalahnya. Apakah ada cara untuk memfilter kolom di v9.2?
fraxture
8

Anda sebenarnya dapat melakukannya tanpa grup dengan, menggunakan subqueries

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;

kembali

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

Artikel dari John Atten ini sangat menarik dan memiliki lebih banyak detail

redben
sumber
2

Saya telah menemukan bahwa yang terbaik untuk membuat JSON, kemudian mengumpulkannya. misalnya

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)

Perhatikan ini dapat dilakukan sebagai subquery jika Anda tidak menyukai CTE (atau memiliki masalah kinerja karena menggunakannya).

Perhatikan juga, jika Anda akan sering melakukan ini, mungkin bermanfaat untuk membuat fungsi untuk membungkus pasangan nilai kunci untuk Anda sehingga kode terlihat lebih bersih. Anda akan melewati fungsi Anda (misalnya) 'ecks', 'x'dan itu akan kembali "ecks": "x".

MikeM
sumber
1

Meskipun masih tidak ada cara untuk melakukan apa pun tentang memilih semua kolom kecuali satu bit, tetapi Anda dapat menggunakan json_agg(to_json(b.col_1, b.col_2, b.col_3 ...))untuk mendapatkan json array dari jsons masing-masing dalam format {"col_1":"col_1 value", ...}.

Jadi kueri akan terlihat seperti:

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;

dan akan mengembalikan baris sebagai:

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"...},...]
...

(Saya menggunakan Postgres 9.5.3 sekarang dan tidak 100% yakin kapan dukungan ini ditambahkan.)

David K
sumber
1

Anda bisa menggunakan json_build_objectseperti ini

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
sumber