Оптимизировать запрос на заполнение пустой даты (используя таблицу календаря) - PullRequest
0 голосов
/ 04 февраля 2019

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

Я использую MySQL 5.7

У меня есть таблица calendar, содержащая все часы года (8760 строк)

CREATE TABLE calendar (
  date datetime PRIMARY KEY
);

У меня есть spentsтаблица, содержащая дату, пользователя, категорию и потраченное

CREATE TABLE spents (
  date datetime NOT NULL,
  user varchar(24) NOT NULL,
  category enum('food', 'hobbies', 'clothing', 'taxes') NOT NULL,
  spent int(5) unsigned NOT NULL DEFAULT '0',
  UNIQUE KEY hourly_composite (date, user, category)
);

Допустим, таблица spents содержит следующие строки:

+---------------------+------+----------+-------+
| date                | user | category | spent |
+---------------------+------+----------+-------+
| 2018-10-01 10:00:00 | bob  | food     |    10 |
| 2018-10-01 11:00:00 | bob  | hobbies  |    50 |
| 2018-10-01 11:00:00 | bob  | clothing |    30 |
| 2018-10-01 11:00:00 | bob  | taxes    |     3 |
| 2018-10-01 12:00:00 | bob  | food     |    30 |
| 2018-10-01 15:00:00 | bob  | clothing |    25 |
| 2018-10-01 16:00:00 | bob  | hobbies  |     5 |
+---------------------+------+----------+-------+  

Я хочу, например, чтобыполучить сумму, потраченную между 10 и 18 часов 2018-10-01 для пользователя bob .
Окончательный результат должен выглядеть следующим образом:

+---------------------+------+------------------------+-------------+
| hour                | user | categories             | total_spent |
+---------------------+------+------------------------+-------------+
| 2018-10-01 10:00:00 | bob  | food                   |          10 |
| 2018-10-01 11:00:00 | bob  | clothing,hobbies,taxes |          83 |
| 2018-10-01 12:00:00 | bob  | food                   |          30 |
| 2018-10-01 13:00:00 | bob  |                        |           0 |
| 2018-10-01 14:00:00 | bob  |                        |           0 |
| 2018-10-01 15:00:00 | bob  | clothing               |          25 |
| 2018-10-01 16:00:00 | bob  | hobbies                |           5 |
| 2018-10-01 17:00:00 | bob  |                        |           0 |
| 2018-10-01 18:00:00 | bob  |                        |           0 |
+---------------------+------+------------------------+-------------+

Таким образом, запрос выглядит следующим образом:

-- get the scalar product of unique group and hour
    SELECT hour, user,
      IFNULL(GROUP_CONCAT(DISTINCT IF(hour = DATE_FORMAT(spents.date, "%Y-%m-%d %T") AND spent > 0, category, NULL)), "") AS categories,  
      SUM(IF(hour = DATE_FORMAT(spents.date, "%Y-%m-%d %T"), IFNULL(spent, 0), 0)) AS total_spent
    FROM spents
    CROSS JOIN 
      (
-- get all hours in the time interval
        SELECT DATE_FORMAT(date, "%Y-%m-%d %T") AS hour
        FROM calendar
        WHERE date BETWEEN "2018-10-01 10:00:00" AND "2018-10-01 18:59:59"
        GROUP BY hour
      ) AS interval_units
    WHERE date BETWEEN "2018-10-01 10:00:00" AND "2018-10-01 18:59:59"
    GROUP BY user, hour
    ORDER BY user, hour;

Этот запрос работает отлично, но я не уверен, что это более оптимизированный способсделайте это.
Конечно, это очень упрощенная версия таблицы spents, представьте себе уникальный ключ с 8+ столбцами для каждого часа дней и таким количеством строк в таблице (несколько миллионов).
Причина, по которой я использую таблицу calendar, заключается в возможности получить исчерпывающий список всех часов между двумя датами.
Я также могу группировать по годам, месяцам, дням и днямнедели и т.д ...

РЕДАКТИРОВАТЬ:
вот оператор EXPLAIN:

+----+-------------+------------+------------+-------+------------------+------------------+---------+------+------+----------+-----------------------------------------------------------+
| id | select_type | table      | partitions | type  | possible_keys    | key              | key_len | ref  | rows | filtered | Extra                                                     |
+----+-------------+------------+------------+-------+------------------+------------------+---------+------+------+----------+-----------------------------------------------------------+
|  1 | PRIMARY     | spents     | NULL       | range | hourly_composite | hourly_composite | 5       | NULL |    7 |   100.00 | Using where; Using temporary; Using filesort              |
|  1 | PRIMARY     | <derived2> | NULL       | ALL   | NULL             | NULL             | NULL    | NULL |    9 |   100.00 | Using join buffer (Block Nested Loop)                     |
|  2 | DERIVED     | calendar   | NULL       | range | PRIMARY          | PRIMARY          | 5       | NULL |    9 |   100.00 | Using where; Using index; Using temporary; Using filesort |
+----+-------------+------------+------------+-------+------------------+------------------+---------+------+------+----------+-----------------------------------------------------------+
...