Как оптимизировать сложный запрос? - PullRequest
1 голос
/ 18 ноября 2010

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

т.

Birthdays     | 10
Anniversaries | 15
Introductions | 450
Recurring     | 249

Проблема в том, что я UNION использую все это, и в некоторых случаях запрос занимает более 10 секунд. (У нас есть кэширование, так что это проблема только при первом входе пользователя в систему в течение дня).

Существует множество других критериев:

  • , включенный в счетчик, должен быть только последним на каждого клиента для каждого типа (т. Е. Если у клиента есть два введения, он должен учитываться только один раз - я использую наибольшее число для каждой группы метод достижения этого)
  • для Дней Рождения и Юбилеев, дата должна быть +/- 7 дней с сегодняшнего дня
  • для всех них должны учитываться только записи за последние 60 дней
  • эти записи необходимо объединить с таблицей клиентов, чтобы убедиться, что продавец, отвечающий за данную возможность, соответствует текущему продавцу клиента

Вот сгенерированный запрос (длинный):

SELECT 'Birthdays' AS `type`, COUNT(*) AS `num` 
FROM `opportunities` 
INNER JOIN `customers` 
    ON `opportunities`.`customer_id` = `customers`.`customer_id` 
    AND `opportunities`.`sales_person_id` = `customers`.`sales_person_id` 
LEFT JOIN `opportunities` AS `o2` 
    ON `opportunities`.`customer_id` = `o2`.`customer_id` 
    AND `opportunities`.`marketing_message` = `o2`.`marketing_message` 
    AND opportunities.communication_alert_date < o2.communication_alert_date 
WHERE ((`opportunities`.`org_code` = ?)) 
AND (opportunities.marketing_message = 'Birthday Alert') 
AND ((opportunities.communication_alert_date BETWEEN 
    DATE_SUB(NOW(), INTERVAL 7 DAY) AND DATE_ADD(NOW(), INTERVAL 7 DAY))) 
AND (opportunities.communication_alert_date >= DATE_SUB(NOW(), INTERVAL 60 DAY)) 
AND (o2.customer_id IS NULL) 

UNION ALL 

SELECT 'Anniversaries' AS `type`, COUNT(*) AS `num` 
FROM `opportunities` 
INNER JOIN `customers` 
    ON `opportunities`.`customer_id` = `customers`.`customer_id` 
    AND `opportunities`.`sales_person_id` = `customers`.`sales_person_id` 
LEFT JOIN `opportunities` AS `o2` 
    ON `opportunities`.`customer_id` = `o2`.`customer_id` 
    AND `opportunities`.`marketing_message` = `o2`.`marketing_message` 
    AND opportunities.communication_alert_date < o2.communication_alert_date 
WHERE ((`opportunities`.`org_code` = ?)) 
AND (opportunities.marketing_message = 'Anniversary Alert') 
AND ((opportunities.communication_alert_date BETWEEN 
    DATE_SUB(NOW(), INTERVAL 7 DAY) AND DATE_ADD(NOW(), INTERVAL 7 DAY))) 
AND (opportunities.communication_alert_date >= DATE_SUB(NOW(), INTERVAL 60 DAY)) 
AND (o2.customer_id IS NULL) 

UNION ALL 

SELECT 'Introductions' AS `type`, COUNT(*) AS `num` 
FROM `opportunities` 
INNER JOIN `customers` 
    ON `opportunities`.`customer_id` = `customers`.`customer_id` 
    AND `opportunities`.`sales_person_id` = `customers`.`sales_person_id` 
LEFT JOIN `opportunities` AS `o2` 
    ON `opportunities`.`customer_id` = `o2`.`customer_id` 
    AND `opportunities`.`marketing_message` = `o2`.`marketing_message` 
    AND opportunities.communication_alert_date < o2.communication_alert_date 
WHERE ((`opportunities`.`org_code` = ?)) 
AND ((opportunities.Intro_Letter = 'Yes')) 
AND (opportunities.communication_alert_date >= DATE_SUB(NOW(), INTERVAL 60 DAY)) 
AND (o2.customer_id IS NULL) 

UNION ALL 

SELECT 'Recurring' AS `type`, COUNT(*) AS `num` 
FROM `opportunities` 
INNER JOIN `customers` 
    ON `opportunities`.`customer_id` = `customers`.`customer_id` 
    AND `opportunities`.`sales_person_id` = `customers`.`sales_person_id` 
LEFT JOIN `opportunities` AS `o2` 
    ON `opportunities`.`customer_id` = `o2`.`customer_id` 
    AND `opportunities`.`marketing_message` = `o2`.`marketing_message` 
    AND opportunities.communication_alert_date < o2.communication_alert_date 
WHERE ((`opportunities`.`org_code` = ?)) 
AND ((opportunities.marketing_message != 'Anniversary Alert' 
AND opportunities.marketing_message != 'Birthday Alert' 
AND opportunities.Intro_Letter != 'Yes')) 
AND (opportunities.communication_alert_date >= DATE_SUB(NOW(), INTERVAL 60 DAY)) 
AND (o2.customer_id IS NULL)

У меня есть индексы для следующих полей в таблице opportunities:

  • org_code
  • customer_id
  • Intro_Letter
  • marketing_message
  • sales_person_id
  • org_code, marketing_message
  • org_code, Intro_Letter
  • org_code, marketing_message, Intro_Letter

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

Ответы [ 6 ]

2 голосов
/ 19 ноября 2010

Я согласен с существующими комментариями, что текст предупреждения должен быть в таблице типов, с отношением внешнего ключа к таблице OPPORTUNITIES.

Оставьте его на Zend для двух запросов, когда вам нужен только один:

   SELECT CASE
            WHEN marketing_message = 'Birthday Alert' THEN 'Birthdays'
            WHEN marketing_message = 'Anniversary Alert' THEN 'Anniversaries'
          END AS msg,
          COUNT(*)
     FROM OPPORTUNITIES o
     JOIN CUSTOMERS c ON c.customer_id = o.customer_id
                 AND c.sales_person_id = o.sales_person_id
LEFT JOIN OPPORTUNITIES o2 ON o2.customer_id = o.customer_id
                      AND o2.marketing_message = o.marketing_message
                      AND o2.communication_alert_date < o.communication_alert_date
    WHERE o.org_code ?
      AND o.marketing_message IN ('Birthday Alert', 'Anniversary Alert') 
      AND o.communication_alert_date BETWEEN DATE_SUB(NOW(), INTERVAL 7 DAY) 
                                         AND DATE_ADD(NOW(), INTERVAL 7 DAY)
      AND o.communication_alert_date >= DATE_SUB(NOW(), INTERVAL 60 DAY)
      AND o2.customer_id IS NULL
 GROUP BY msg
2 голосов
/ 18 ноября 2010

Хорошим началом будет удаление сравнений строк и помещение их в таблицу с присвоенными идентификаторами и добавление числовых столбцов вместо

opportunities.marketing_message != 'Birthday Alert'

Таким образом, вы получите ...

[id]    [name]
1       Birthday Alert
2       Anniversary

Числовые сравнения всегда намного быстрее, даже с индексированием.Это также позволит вам легко добавлять новые типы в будущем.

Эта часть является избыточной, вам не нужно AND (opportunities.communication_alert_date >= DATE_SUB(NOW(), INTERVAL 60 DAY)), потому что предложение прямо перед ней выполнит свою работу.

AND ((opportunities.communication_alert_date BETWEEN 
    DATE_SUB(NOW(), INTERVAL 7 DAY) AND DATE_ADD(NOW(), INTERVAL 7 DAY))) 
AND (opportunities.communication_alert_date >= DATE_SUB(NOW(), INTERVAL 60 DAY))
0 голосов
/ 08 июля 2011

Если вы посмотрите на это http://dev.mysql.com/doc/refman/5.0/en/using-explain.html Вы увидите, что проверка вашего запроса с ключевым словом EXPLAIN дает вам информацию о том, как выполняется запрос. Тогда вы точно увидите, где производительность плохая.

0 голосов
/ 20 ноября 2010

В дополнение к предоставленным ответам я заменил LEFT JOIN подзапросом, чтобы возвращать только самые последние экземпляры по типу. Это, казалось, очень помогло.

т. Е. (Только для количества дней рождения и годовщины):

SELECT 
    CASE
        WHEN marketing_message = 'Birthday Alert' THEN 'Birthdays'
        WHEN marketing_message = 'Anniversary Alert' THEN 'Anniversaries'
    END AS `type`, 
    COUNT(*) AS `num` 
FROM (
    SELECT `opp_sub`.* 
    FROM (
        SELECT `opportunities`.`marketing_message`, `opportunities`.`customer_id`
        FROM `opportunities`
        INNER JOIN `customers` 
            ON `opportunities`.`customer_id` = `customers`.`customer_id` 
            AND `opportunities`.`sales_person_id` = `customers`.`sales_person_id` 
        WHERE (opportunities.communication_alert_date >= DATE_SUB(NOW(), INTERVAL 60 DAY)) 
        AND (`opportunities`.`dealer_code` = ?)
        AND (opportunities.marketing_message IN ('Anniversary Alert', 'Birthday Alert')) 
        AND (opportunities.communication_alert_date 
            BETWEEN DATE_SUB(NOW(), INTERVAL 7 DAY) 
                AND DATE_ADD(NOW(), INTERVAL 7 DAY))
        ORDER BY `opportunities`.`communication_alert_date` DESC
    ) AS `wool_sub` 
    GROUP BY `customer_id`, `marketing_message`
) AS `c_table` 
0 голосов
/ 19 ноября 2010

В каждом подзапросе у вас есть:

LEFT JOIN `opportunities` AS `o2` 
    ON `opportunities`.`customer_id` = `o2`.`customer_id` 
...
AND (o2.customer_id IS NULL)

Это означает, что вы хотите только opportunities o2, которые имеют NULL для customer_id.Из-за этого эти запросы могут быть записаны с 2 соединениями INNER вместо 1 OUTER и 1 INNER join, что, вероятно, быстрее.Как то так:

SELECT `o1`.`Birthdays` AS `type`, COUNT(*) AS `num` 
FROM `opportunities` as `o2`
INNER JOIN `opportunities` AS `o1` 
    ON `o1`.`marketing_message` = `o2`.`marketing_message` 
    AND o1.communication_alert_date < o2.communication_alert_date 
INNER JOIN `customers` 
    ON `o1`.`customer_id` = `customers`.`customer_id` 
    AND `o1`.`sales_person_id` = `customers`.`sales_person_id` 
WHERE (o2.customer_id IS NULL)
AND (o2.marketing_message = 'Birthday Alert') 
AND ((`o1`.`org_code` = ?)) 
AND ((o1.communication_alert_date BETWEEN 
    DATE_SUB(NOW(), INTERVAL 7 DAY) AND DATE_ADD(NOW(), INTERVAL 7 DAY))) 
AND (o1.communication_alert_date >= DATE_SUB(NOW(), INTERVAL 60 DAY)) 
0 голосов
/ 19 ноября 2010

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

...