Группировка после 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, которые понадобятся (не обрабатывая все таблицу и пользуясь индексом), но при этом и не размножала строки в запросе перед джойном?


Ответы (0 шт):