2010-11-18 3 views
1

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

ie.

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

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

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

  • включены в счет должен быть только последним один на одного клиента для каждого типа (то есть, если у клиента есть два введений, она должна учитываться только один раз -. Я использую greatest-n-per-group способ достижения этого)
  • для рождения и юбилеев, дата должна быть +/- 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

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

+0

, чтобы процитировать 'или не указывать - вот вопрос –

ответ

2

Хорошее место для начала было бы удаление сравнения строк и положить их в таблицу с заданными идентификаторами и добавляя числовые столбцы в месте

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

+1 для нормализации' 'Birthday Alert'' в правильный тип. – FrustratedWithFormsDesigner

0

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

+0

Я использую Zend Framework, и запрос строится автоматически. Я опубликовал вывод как есть. –

0

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

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

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

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)) 
2

Я согласен с существующими комментариями о том, что уведомление текст должен находиться в таблице типа, с внешним ключом отношения к таблице ВОЗМОЖНОСТЕЙ.

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

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 
0

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

т.е. (всего за день рождения и подсчета Anniversary):

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

Нет необходимости в ORDER BY без LIMIT в подзапросе - он должен быть быстрее, если вы его удалите. Подзапрос также не нужен. –

+0

Мне нужны только самые последние записи для каждого пользователя за каждый проданный_месяц. Я не вижу, как я могу удалить ORDER BY и подзапрос, не изменяя результаты. Не могли бы вы еще больше объяснить, что вы имеете в виду? –

0

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