Группировка параметров товаров по группам в SQL
При работе с интернет-магазинами часто возникает задача вывести характеристики товара, сгруппированные по категориям (например, «Габариты», «Производитель»). В этой статье разберём, как написать правильный запрос на PostgreSQL для объединения параметров и их значений в JSON-массивы, сгруппированные по группам.
Исходная структура таблиц
У нас есть четыре связанные таблицы:
- amqp_param_groups - группы параметров (uuid, name);
- amqp_trade_parameters - параметры товаров (uuid, name, group_id);
- amqp_trade_parameters_values - значения параметров (uuid, value, param_id);
- amqp_trade_parameters_assignments - сводная таблица, связывающая товар с параметрами и их значениями.
Цель - для каждого товара получить массив групп, внутри которых перечислены параметры и их значения.
Проблема первоначального запроса
Исходный запрос использовал GROUP BY pa.param, что приводило к разбивке по отдельным параметрам, а не по группам. В результате каждая строка содержала только один параметр и одно значение. Чтобы объединить все параметры одной группы в один массив, нужно группировать по group_id и применять агрегацию JSON.
Правильный SQL-запрос для группировки
Вот корректный запрос, который выдаёт желаемый результат:
SELECT
g.uuid AS group_id,
jsonb_agg(DISTINCT jsonb_build_object('g_uuid', g.uuid, 'g_name', g.name)) AS g_name,
jsonb_agg(DISTINCT jsonb_build_object('p_uuid', p.uuid, 'p_name', p.name)) AS p_name,
jsonb_agg(DISTINCT jsonb_build_object('pv_uuid', pv.uuid, 'pv_value', pv.value)) AS pv_value
FROM amqp_trade_parameters_assignments AS pa
JOIN amqp_trade_parameters AS p ON p.uuid = pa.param
JOIN amqp_trade_parameters_values AS pv ON pv.uuid::text = pa.value::text
JOIN amqp_param_groups AS g ON g.uuid = p.group_id
WHERE pa.trade = :trade
GROUP BY g.uuid, g.name
ORDER BY g.name;
Этот запрос группирует данные по g.uuid (идентификатору группы), собирая все параметры и их значения в JSON-массивы с помощью jsonb_agg. DISTINCT исключает дубликаты, если один и тот же параметр привязан к нескольким значениям.
Альтернативный вариант с вложенными JSON
Если нужно получить более сложную структуру, например, массив объектов с параметрами и их значениями, можно использовать:
SELECT
g.uuid AS group_id,
jsonb_build_object('g_uuid', g.uuid, 'g_name', g.name) AS g_name,
jsonb_agg(
jsonb_build_object(
'p_uuid', p.uuid,
'p_name', p.name,
'values', jsonb_agg(jsonb_build_object('pv_uuid', pv.uuid, 'pv_value', pv.value))
)
) AS parameters
FROM amqp_trade_parameters_assignments AS pa
JOIN amqp_trade_parameters AS p ON p.uuid = pa.param
JOIN amqp_trade_parameters_values AS pv ON pv.uuid::text = pa.value::text
JOIN amqp_param_groups AS g ON g.uuid = p.group_id
WHERE pa.trade = :trade
GROUP BY g.uuid, g.name, p.uuid, p.name
ORDER BY g.name;
Почему это работает?
Ключевое отличие - группировка по g.uuid (или по g.uuid, g.name). Это объединяет все строки, относящиеся к одной группе. Функция jsonb_agg собирает значения из разных строк в один JSON-массив. Использование jsonb_build_object позволяет сформировать правильную структуру ключ-значение.
Советы по оптимизации
- Убедитесь, что на колонках
uuid,param_id,group_idесть индексы - это ускорит JOIN. - Если данных много, избегайте
DISTINCTвнутри агрегации, если в этом нет необходимости. - Для вывода в PHP можно использовать
json_decodeдля преобразования JSON-строк в массивы.