Можно ли индексировать промежуточный итог для ускорения оконных функций? - PullRequest
0 голосов
/ 17 октября 2019

Я хочу выполнить:

    SELECT cust_id, SUM(cost)
    FROM purchases
    WHERE purchase_time BETWEEN('2018-01-01', '2019-01-01')
    GROUP BY cust_id

Но с большим количеством строк, я ожидаю, что каждая запись должна быть загружена для агрегирования соответствующей суммы. То, что я хочу сделать, это иметь индекс как:

    CREATE INDEX purchase_sum 
    ON purchases(cust_id, purchase_time, 
    SUM(cost) OVER (PARTITION BY cust_id 
    ORDER BY purchase_time) AS rolling_cost)

Из которого я хотел бы индекс, который выглядит следующим образом:

 cust_id    time    rolling_cost
--------   ------  --------------
   1        Jan 1       5
   1        Jan 2       12
   1        Jan 3       14
   1        Jan 4       20
   1        Jan 5       24
   2        Jan 1       1
   2        Jan 2       7
   2        Jan 3       11
   2        Jan 4       14
   2        Jan 5       19
   3        Jan 1       4
   3        Jan 2       5
   3        Jan 3       10
   3        Jan 4       21
   3        Jan 5       45

Исходя из этого, мой исходный запрос может быть вычисленпросто вычитая 2 известных значения для cust_id, например, cost_in_window = rolling_cost('2019-01-01') - rolling_cost('2018-01-01'), что не потребует загрузки чего-либо из исходной таблицы.

Это выполнимо как индекс? Или есть другой способ достижения той же цели?

Ответы [ 2 ]

2 голосов
/ 17 октября 2019

Вы можете обнаружить, что это быстрее:

select c.cust_id,
       (select sum(p.cost)
        from purchases p
        where p.cust_id = c.cust_id and
              p.purchase_time >= '2018-01-01' and
              p.purchase_time < '2019-01-01' and
       ) as total_cost
from customers c
having total_cost is not null;

Тогда это может использовать индекс для purchases(cust_id, purchase_time, cost). Только индекс необходим для расчета суммы. Это экономия. Что еще более важно, нет общих агрегаций - и это может быть большая экономия, которая компенсирует выполнение расчета для всех клиентов.

Однако, с тем же индексом, это может быть немного лучше:

select c.cust_id,
       (select sum(p.cost)
        from purchases p
        where p.cust_id = c.cust_id and
              p.purchase_time >= '2018-01-01' and
              p.purchase_time < '2019-01-01'
       ) as total_cost
from customers c
where exists (select 1
              from purchases p
              where p.cust_id = c.cust_id and
                    p.purchase_time >= '2018-01-01' and
                    p.purchase_time < '2019-01-01' 
             );

РЕДАКТИРОВАТЬ:

Единственный способ реализовать то, что вы хотите, это явно включить столбец накопленной суммы в данные. Это потребовало бы перефразирования запроса (для выполнения требуемого вычитания) и наличия триггеров для сохранения значения.

Если исторические данные никогда не изменяются, это может быть разумным подходом. Однако обновления или вставки более ранних строк могут стать довольно дорогими.

1 голос
/ 17 октября 2019

Вот индекс, который может помочь вашему запросу:

CREATE INDEX idx ON purchases (purchase_time, cust_id, cost);

Это должно, по крайней мере, позволить MySQL выбросить все записи, которые не соответствуют диапазону времени покупки. Затем этот индекс также охватывает столбцы cust_id и cost, что означает, что MySQL потребуется только сканирование индекса для вычисления сумм затрат для каждой группы записей о клиентах.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...