MySQL One-to-Many в формате JSON

У меня есть две таблицы MySQL:

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

Где Sale(user) — внешний ключ для User(id), так что это отношение «один ко многим» (один пользователь может совершать много продаж).

Я пытаюсь получить это из базы данных и вернуть в формате JSON для нескольких пользователей, поэтому это будет выглядеть так:

[
  {
    "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"
      }
    ]
  }
]

Где объекты «продажи» вложены в объект соответствующего им «пользователя».

Итак, вопрос в том, как лучше всего запросить это из БД и превратить в JSON? Я мог бы выполнить запрос для всех пользователей, затем выполнить итерацию по каждому из них и выполнить запрос, чтобы получить их продажи, но это довольно медленно. Или я мог бы выполнить внешнее соединение между пользователями и продажами, а затем проанализировать его в коде в формате JSON, но это отправляет лишнюю информацию из БД (включая весь набор данных пользователя для каждой продажи) и требует циклического просмотра всего этого в код. Есть ли удобный способ сделать это? Кстати, я использую Python 3.7.


person Jordan    schedule 26.01.2019    source источник


Ответы (1)


Вот запрос SQL, который может удовлетворить ваши требования. Он использует агрегатная функция MySQL JSON_ARRAYAGG() для создания массива объектов JSON (которые создаются с помощью JSON_OBJECT()).

Промежуточный уровень группировки выполняется внутри соединения для создания массива sales JSON для каждого пользователя. Затем результаты объединяются в одну строку с одним столбцом, содержащим результирующий массив объектов JSON.

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

Демонстрация скрипта БД

Если вы оберните возвращаемое значение JSON_PRETTY, вывод будет следующим:

[
  {
    "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"
      }
    ]
  }
]

Изменить: вот (уродливое) решение для MySQL ‹ 5.7, где поддержка JSON недоступна. Он полагается только на функции манипулирования строками. Обратите внимание, что это будет работать только до тех пор, пока поля varchar не содержат символ ":

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

Демонстрация DB Fiddle

person GMB    schedule 26.01.2019
comment
Это фантастика. Есть ли способ, чтобы он автоматически заполнил поля JSON_OBJECT полями таблицы, поэтому мне не нужно обновлять запрос, например, если я добавляю поле в таблицу User? - person Jordan; 27.01.2019
comment
ох, я только что понял, что JSON_ARRAYAGG не существует до MySQL 5.7. Я застрял на 5.6, так как использую AWS Aurora Serverless. Любые другие предложения? - person Jordan; 27.01.2019
comment
@Jordan: это выглядит как Aurora теперь поддерживает MySQL 5.7... - person GMB; 27.01.2019
comment
Достичь того, что вы хотите, в MySQL 5.6 будет сложнее, поскольку в этой версии нет поддержки JSON... Нам пришлось бы делать это, используя чистую конкатенацию и агрегацию строк... - person GMB; 27.01.2019
comment
@Jordan: обновил мой ответ решением только с функциями конкатенации/агрегации строк. - person GMB; 27.01.2019
comment
Aurora (экземпляр) поддерживает, но Aurora (без сервера) не поддерживает 5.7 - person Jordan; 28.01.2019
comment
@Jordan: пожалуйста, проверьте мой второй запрос, который подходит для вашего варианта использования? Я тестировал его на MySQL 5.6. - person GMB; 28.01.2019
comment
Спасибо за обновленный запрос, к сожалению, я не могу гарантировать отсутствие каких-либо кавычек в полях, поэтому я думаю, что мне придется использовать гораздо менее эффективный подход (если у вас нет других идей). - person Jordan; 28.01.2019