Группировка после JOIN
У меня вопрос несколько общего характера, но я дам лабораторный пример для наглядности. Вопрос относится к ситуации, когда мы хотим поджойнить результат группировки, но при этом использовать индекс в группируемой таблице.
Пример:
Есть таблицы table1 и table2
table1:
| col_1a_pk | col_1b |
|---|---|
| a | 1 |
| b | 2 |
| c | 5 |
| d | 3 |
table2:
| col_2a_pk | col_2b_pk | col_2c |
|---|---|---|
| a | 1 | 3 |
| a | 2 | 4 |
| c | 1 | 3 |
| c | 2 | 2 |
| b | 1 | 1 |
Я хочу поджойнить table2 к table1, но я хочу получить сумму col_2c из таблицы table2, группируя по col_2a_pk и уже полученный результат джойнить к table1, используя для джойна первые столбцы. Что я хочу иметь в результате:
table_result:
| col_1a_pk | col_1b | sum_col_2c |
|---|---|---|
| a | 2 | 7 |
| c | 5 | 5 |
| d | 3 | NULL |
| b | 1 | 1 |
Для удобства, вот CTE:
WITH
table1 AS (
SELECT 'b' col_1a_pk, 1 col_1b FROM DUAL UNION ALL
SELECT 'a' col_1a_pk, 2 col_1b FROM DUAL UNION ALL
SELECT 'c' col_1a_pk, 5 col_1b FROM DUAL UNION ALL
SELECT 'd' col_1a_pk, 3 col_1b FROM DUAL
),
table2 AS (
SELECT 'a' col_2a_pk, 1 col_2b_pk, 3 col_2c FROM DUAL UNION ALL
SELECT 'a' col_2a_pk, 2 col_2b_pk, 4 col_2c FROM DUAL UNION ALL
SELECT 'c' col_2a_pk, 1 col_2b_pk, 3 col_2c FROM DUAL UNION ALL
SELECT 'c' col_2a_pk, 2 col_2b_pk, 2 col_2c FROM DUAL UNION ALL
SELECT 'b' col_2a_pk, 1 col_2b_pk, 1 col_2c FROM DUAL
)
Как я могу это сделать?
Вариант 1:
Просто джойним таблицу и группируем результат джойна
SELECT t1.col_1a_pk
, t1.col_1b
, SUM(t2.col_2c) AS sum_col_2c
FROM table1 t1
LEFT JOIN table2 t2 ON t1.col_1a_pk = t2.col_2a_pk
GROUP BY t1.col_1a_pk
, t1.col_1b;
Преимущества:
- Мы используем индекс для PK в присоединямой таблице table2 и не шарим вложенным циклом по всей таблице.
Недостатки:
- Мы должны выполнить группировку по всем остальным столбцам. А их может быть, скажем, под сотню. А некоторые иметь под собой выражения, а не просто имя столбца. А если даже делать внешний запрос чтобы выписать только алиасы, то это все-равно поиск и выковыривание по тексту сотни имен столбцов, а потом группировка по ним. Сначала размножили, а потом схлопнули - лишняя работа серверу.
Вариант 2
Делаем подзапрос перед джойном, в котором и группируем значения:
SELECT t1.col_1a_pk
, t1.col_1b
, t2.sum_col_2c
FROM table1 t1
LEFT JOIN (SELECT col_2a_pk
, SUM(col_2c) sum_col_2c
FROM table2
GROUP BY col_2a_pk) t2 ON t1.col_1a_pk = t2.col_2a_pk;
Преимущества:
- Не нужно выковыривать имена всех столбцов из внешнего запрос и потом делать по ним группировку
Недостатки:
- Сперва происходит полная обработка table2, то есть сперва он находит сумму по col_2c для КАЖДОГО значения col_2a_pk, даже для тех, которые нам не нужны, т.к. они отсутствуют в table1. В ситуации, когда в table2 огромное количество строк, 99% из которых нам не потребуется, это значительная лишняя нагрузка на сервер.
- Невозможно использовать индекс для col_2a_pk при джойне к table1
Вариант 3
Делаем подзапросы прямо в селект листе
SELECT t1.col_1a_pk
, t1.col_1b
, (SELECT col_2a_pk
, SUM(col_2c) sum_col_2c
FROM table2
WHERE col_2a_pk = t1.col_1a_pk) AS sum_col_2c
FROM table1 t1;
Ну или другая форма записи (хотя механически, как я понимаю, проихсодит тот же процесс)
SELECT t1.col_1a_pk
, t1.col_1b
, t2.sum_col_2c
FROM table1 t1
CROSS APPLY (SELECT SUM(col_2c) sum_col_2c
FROM table2
WHERE col_2a_pk = t1.col_1a_pk) t2
Преимущества:
- Мы используем индекс при обращении к table2
- Мы ищем результат группировки только для нужных строк
Недостатки:
- Происходит куча вызовов в table2 для каждой строки в table1
Итог и вопрос
Как ни странно, замена архитектуры запроса со второго на третий способ (с подзапросом в select-листе) в реальной задаче ускорил запрос в 5 раз, хотя казалось бы, подзапросы - практика довольно порочная. Тем не менее, каждый раз дергать таблицу table2 с единственным значением выглядит как нечто улучшаемое в плане производительности. Можно ли более элегантно решить эту задачу? Чтобы table2 знала все значения col_2a_pk, которые понадобятся (не обрабатывая все таблицу и пользуясь индексом), но при этом и не размножала строки в запросе перед джойном?