Как оптимизировать мой запрос о объединении 3 таблиц в одну таблицу? - PullRequest
0 голосов
/ 16 июня 2020

Я хочу получить идентификатор клиента, который покупает мой продукт каждый месяц с трех месяцев до go. Сегодня 15 февраля 2020. Итак, я хочу получить клиента, который совершит покупку в ноябре 2019 года, декабре 2019 года, январе 2020 года.

У меня есть только 1 заказ стола (MySQL), например:

Таблица заказов (первичный ключ = ID (Автоинкремент)):

-----------------------------------------------
|      ID      |    id_cust  |    buy_date    |
-----------------------------------------------
|       1      |       10    |   2019-11-01   | 
|       2      |       11    |   2019-11-10   |
|       3      |       10    |   2019-12-11   |
|       4      |       12    |   2019-12-12   |
|       5      |       10    |   2020-01-13   |
|       6      |       11    |   2020-01-14   |
|       7      |       12    |   2020-01-15   |
-----------------------------------------------

В зависимости от того, что я хочу, ответ будет id_cust 10

Я пробовал и получил такие результаты:

SELECT g1.`id_cust`
FROM `orders` g1 
    JOIN `orders` g2
    ON g2.`id_cust`   = g1.`id_cust`
      AND g2.`buy_date` >= STR_TO_DATE(CONCAT('01-', LPAD(MONTH(DATE_SUB(NOW(), INTERVAL 2 MONTH)), 2, '0'), '-', YEAR(DATE_SUB(NOW(), INTERVAL 2 MONTH))), '%d-%m-%Y')
      AND g2.`buy_date` < STR_TO_DATE(CONCAT('01-', LPAD(MONTH(DATE_SUB(NOW(), INTERVAL 1 MONTH)), 2, '0'), '-', YEAR(DATE_SUB(NOW(), INTERVAL 1 MONTH))), '%d-%m-%Y')
    JOIN `orders` g3
    ON g3.`id_cust`   = g1.`id_cust`
      AND g3.`id_cust`   = g2.`id_cust`
      AND g3.`buy_date` >= STR_TO_DATE(CONCAT('01-', LPAD(MONTH(DATE_SUB(NOW(), INTERVAL 1 MONTH)), 2, '0'), '-', YEAR(DATE_SUB(NOW(), INTERVAL 1 MONTH))), '%d-%m-%Y')
      AND g3.`buy_date` < STR_TO_DATE(CONCAT('01-', LPAD(MONTH(NOW()), 2, '0'), '-', YEAR(NOW())), '%d-%m-%Y')
WHERE g1.`buy_date` >= STR_TO_DATE(CONCAT('01-', LPAD(MONTH(DATE_SUB(NOW(), INTERVAL 3 MONTH)), 2, '0'), '-', YEAR(DATE_SUB(NOW(), INTERVAL 3 MONTH))), '%d-%m-%Y')
AND g1.`buy_date` < STR_TO_DATE(CONCAT('01-', LPAD(MONTH(DATE_SUB(NOW(), INTERVAL 2 MONTH)), 2, '0'), '-', YEAR(DATE_SUB(NOW(), INTERVAL 2 MONTH))), '%d-%m-%Y')
GROUP BY g1.`id_cust`

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

Ответы [ 2 ]

0 голосов
/ 16 июня 2020

Я бы использовал второй запрос Гордона. Но если ваш код работает (в качестве упражнения), вы можете оптимизировать время выполнения, создав индексы для (buy_date, id_cust) и (id_cust, buy_date). Первый для предложения WHERE, а второй для предложений ON.

С этой схемой

CREATE TABLE orders (
  `ID` INTEGER primary key,
  `id_cust` INTEGER,
  `buy_date` VARCHAR(10)
);

Результат EXPLAIN вашего запроса:

| id  | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra                                              |
| --- | ----------- | ----- | ---------- | ---- | ------------- | --- | ------- | --- | ---- | -------- | -------------------------------------------------- |
| 1   | SIMPLE      | g1    |            | ALL  |               |     |         |     | 7    | 14.29    | Using where; Using temporary; Using filesort       |
| 1   | SIMPLE      | g2    |            | ALL  |               |     |         |     | 7    | 14.29    | Using where; Using join buffer (Block Nested Loop) |
| 1   | SIMPLE      | g3    |            | ALL  |               |     |         |     | 7    | 14.29    | Using where; Using join buffer (Block Nested Loop) |

Нет используется ключ, и "Block Nested L oop" звучит очень плохо.

После добавления индексов

ALTER TABLE orders ADD INDEX (buy_date, id_cust);
ALTER TABLE orders ADD INDEX (id_cust, buy_date);
| id  | select_type | table | partitions | type  | possible_keys    | key     | key_len | ref             | rows | filtered | Extra                    |
| --- | ----------- | ----- | ---------- | ----- | ---------------- | ------- | ------- | --------------- | ---- | -------- | ------------------------ |
| 1   | SIMPLE      | g1    |            | index | buy_date,id_cust | id_cust | 48      |                 | 7    | 14.29    | Using where; Using index |
| 1   | SIMPLE      | g2    |            | ref   | buy_date,id_cust | id_cust | 5       | test.g1.id_cust | 2    | 14.29    | Using where; Using index |
| 1   | SIMPLE      | g3    |            | ref   | buy_date,id_cust | id_cust | 5       | test.g1.id_cust | 2    | 14.29    | Using where; Using index |

db-fiddle

Теперь он выглядит намного лучше, хотя он не хочет использовать мой первый индекс (вероятно, из-за GROUP BY).

Тогда я бы упростил запрос до:

SELECT DISTINCT g1.id_cust
FROM orders g1 
    JOIN orders g2 ON g2.id_cust = g1.id_cust
    JOIN orders g3 ON g3.id_cust = g1.id_cust
    -- AND g3.id_cust  = g2.id_cust -- redundant condition
WHERE g1.buy_date >= DATE_FORMAT(NOW() - INTERVAL 3 MONTH, '%Y-%m-01')
  AND g1.buy_date <  DATE_FORMAT(NOW() - INTERVAL 2 MONTH, '%Y-%m-01')
  AND g2.buy_date >= DATE_FORMAT(NOW() - INTERVAL 2 MONTH, '%Y-%m-01')
  AND g2.buy_date <  DATE_FORMAT(NOW() - INTERVAL 1 MONTH, '%Y-%m-01')
  AND g3.buy_date >= DATE_FORMAT(NOW() - INTERVAL 1 MONTH, '%Y-%m-01')
  AND g3.buy_date <  DATE_FORMAT(NOW() - INTERVAL 0 MONTH, '%Y-%m-01')
-- GROUP BY g1.id_cust -- You can use DISTINCT instead

db-скрипка

0 голосов
/ 16 июня 2020

Как насчет этого?

select c.id_cust
from (select o.id_cust, year(buy_date) as yyyy, month(buy_date) as mm,
             row_number() over (partition by o.id_cust) as month_counter
      from orders o
      where buy_date >= date_format(current_date - interval 3 month, '%Y-%m-%d') and
            buy_date < date_format(current_date, '%Y-%m-%d')
      group by id_cust, yyyy, mm
     ) c
where month_counter = 3;

Это фильтрует только три месяца, о которых вы заботитесь. Затем он агрегирует по году и месяцу и возвращает только третью строку.

На самом деле, это проще выразить как:

select o.id_cust
from orders o
where buy_date >= date_format(current_date - interval 3 month, '%Y-%m-%d') and
      buy_date < date_format(current_date, '%Y-%m-%d')
group by o.id_cust
having count(distinct year(buy_date), month(buy_date)) = 3;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...