У MySQL (як і в PostgreSQL) немає оператора PIVOT. Рядки перетворюють на колонки умовною агрегацією: агрегат по групі, де враховуються лише рядки, що відповідають умові колонки.
-- продажі: рядок на кожну пару (товар, місяць)
SELECT product_id,
SUM(CASE WHEN MONTH(created_at) = 1 THEN total ELSE 0 END) AS jan,
SUM(CASE WHEN MONTH(created_at) = 2 THEN total ELSE 0 END) AS feb,
SUM(CASE WHEN MONTH(created_at) = 3 THEN total ELSE 0 END) AS mar
FROM orders
WHERE created_at >= '2026-01-01' AND created_at < '2026-04-01'
GROUP BY product_id;
Короткий запис для підрахунків - у MySQL логічний вираз дорівнює 1 чи 0:
SELECT DATE(created_at) AS day,
SUM(status = 'paid') AS paid,
SUM(status = 'cancelled') AS cancelled,
SUM(status = 'refunded') AS refunded
FROM orders
GROUP BY day;
У PostgreSQL для цього є COUNT(*) FILTER (WHERE status = 'paid').
Головне обмеження - колонки мають бути відомі заздалегідь. SQL-запит повертає фіксований набір колонок, тож «стільки колонок, скільки є статусів» одним статичним запитом не зробити.
Варіанти для динамічних колонок:
- Побудувати запит у застосунку: отримати список значень, згенерувати вирази
SUM(CASE ...)і виконати. - Підготовлений запит у збереженій процедурі (
PREPARE/EXECUTEзі зібраного рядка) - працює, але важко підтримувати й небезпечно, якщо значення приходять від користувача. - Повернути «довгий» формат (рядок на кожну пару) і розвернути в таблицю на клієнті - часто найпростіше й найгнучкіше: бібліотеки графіків і таблиць саме цього й очікують.
Зворотна операція (unpivot) - колонки в рядки - робиться через UNION ALL по колонках або JSON_TABLE з масиву значень.
Обережно з NULL: ELSE 0 дає нуль там, де даних немає; без ELSE вийде NULL. Для звіту важливо вирішити, що з цього означає «нуль продажів», а що - «немає даних».