Format MySQL Satu-ke-Banyak ke JSON

Saya memiliki dua tabel MySQL:

User (id, name)
Sale (id, user, item)

Dimana Sale(user) adalah kunci asing ke User(id), jadi ini adalah hubungan satu-ke-banyak (satu pengguna dapat menghasilkan banyak penjualan).

Saya mencoba mengambil ini dari database dan mengembalikannya dalam format JSON untuk banyak pengguna, sehingga akan terlihat seperti ini:

[
  {
    "id": 1,
    "name": "User 1",
    "sales": [
      {
        "id": 1,
        "item": "t-shirt"
      },
      {
        "id": 2,
        "item": "jeans"
      }
    ]
  },
  {
    "id": 2,
    "name": "User 2",
    "sales": [
      {
        "id": 3,
        "item": "sweatpants"
      },
      {
        "id": 4,
        "item": "gloves"
      }
    ]
  }
]

Dimana entitas "penjualan" berada di dalam entitas "pengguna" yang sesuai.

Jadi pertanyaannya adalah, apa cara terbaik untuk menanyakan ini dari DB dan mengubahnya menjadi JSON? Saya dapat menjalankan kueri untuk semua Pengguna, lalu mengulangi masing-masing Pengguna dan menjalankan kueri untuk mendapatkan penjualan mereka, tetapi ini cukup lambat. Atau, saya bisa melakukan gabungan luar antara Pengguna dan Penjualan dan kemudian menguraikannya dalam kode ke dalam format JSON, tetapi ini mengirimkan informasi berlebih dari DB (termasuk seluruh kumpulan data Pengguna untuk setiap Penjualan) dan memerlukan perulangan semuanya di kode. Apakah ada cara mudah untuk melakukan ini? Saya menggunakan Python 3.7.


person Jordan    schedule 26.01.2019    source sumber


Jawaban (1)


Berikut adalah kueri SQL yang mungkin memenuhi kebutuhan Anda. Ini menggunakan fungsi agregat MySQL JSON_ARRAYAGG() untuk menghasilkan array objek JSON (yang dibuat menggunakan JSON_OBJECT()).

Pengelompokan tingkat menengah dilakukan dalam gabungan, untuk menghasilkan larik sales JSON setiap pengguna. Kemudian hasilnya dikumpulkan menjadi satu baris, dengan satu kolom yang berisi array objek JSON yang dihasilkan.

SELECT
  JSON_ARRAYAGG(JSON_OBJECT('id', u.id, 'name', u.name, 'sales', s.sales))
FROM
    user u
    LEFT JOIN (
        SELECT 
            user, 
            JSON_ARRAYAGG(JSON_OBJECT('id', id, 'item', item)) sales 
        FROM sale 
        GROUP BY user
    ) s ON s.user = u.id

Demo di DB Fiddle

Jika Anda membungkus nilai kembalian dengan JSON_PRETTY, hasilnya adalah sebagai berikut :

[
  {
    "id": 1,
    "name": "User 1",
    "sales": [
      {
        "id": 1,
        "item": "t-shirt"
      },
      {
        "id": 2,
        "item": "jeans"
      }
    ]
  },
  {
    "id": 2,
    "name": "User 2",
    "sales": [
      {
        "id": 3,
        "item": "sweatpants"
      },
      {
        "id": 4,
        "item": "gloves"
      }
    ]
  }
]

Edit : berikut adalah solusi (jelek) untuk MySQL ‹ 5.7, yang dukungan JSON tidak tersedia. Itu hanya bergantung pada fungsi manipulasi string. Harap dicatat bahwa ini hanya akan berfungsi selama bidang varchar tidak mengandung karakter " :

SELECT
    CONCAT(
        '[', 
        GROUP_CONCAT( CONCAT( '{ "id":', u.id, ', "name":"', u.name, '", "sales":', s.sales, ' }' )  SEPARATOR ', ' ),
        ']'
    )
FROM 
    user u
    LEFT JOIN (
        SELECT 
            user, 
            CONCAT( 
               '[', 
                GROUP_CONCAT( CONCAT( '{ "id":', id, ', "item":"', item, '" }' ) SEPARATOR ', '),
                ']'
            ) sales 
    FROM sale
    GROUP BY user ) s ON s.user = u.id

Demo di DB Fiddle

person GMB    schedule 26.01.2019
comment
Ini fantastis. Apakah ada cara agar secara otomatis mengisi bidang JSON_OBJECT dengan bidang tabel, jadi saya tidak perlu memperbarui kueri jika saya menambahkan bidang ke tabel Pengguna, misalnya? - person Jordan; 27.01.2019
comment
ah gila, saya baru menyadari bahwa JSON_ARRAYAGG tidak ada sampai MySQL 5.7. Saya terjebak di versi 5.6 karena saya menggunakan AWS Aurora Tanpa Server. Ada saran lain? - person Jordan; 27.01.2019
comment
@Jordan: sepertinya Aurora sekarang mendukung MySQL 5.7... - person GMB; 27.01.2019
comment
Mencapai apa yang Anda inginkan di MySQL 5.6 akan lebih rumit karena tidak ada dukungan JSON di versi ini... Kami harus melakukannya menggunakan penggabungan dan agregasi string murni... - person GMB; 27.01.2019
comment
@Jordan: memperbarui jawaban saya dengan solusi dengan fungsi penggabungan string/agregasi saja. - person GMB; 27.01.2019
comment
Aurora (instance) mendukungnya, namun Aurora (tanpa server) tidak mendukung 5.7 - person Jordan; 28.01.2019
comment
@Jordan: silakan periksa permintaan kedua saya, apakah itu sesuai untuk kasus penggunaan Anda? Saya mengujinya di MySQL 5.6 - person GMB; 28.01.2019
comment
Terima kasih atas kueri yang diperbarui, sayangnya saya tidak dapat menjamin bahwa tidak ada karakter kutipan di kolom, jadi saya rasa saya harus menggunakan pendekatan yang kurang efisien (kecuali Anda punya ide lain). - person Jordan; 28.01.2019