2010-11-18 71 views
1

我正在开发一个营销型系统。在首页上,销售人员需要查看其当前销售机会的数量。如何优化复杂查询?

即。

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

的问题是我UNION荷兰国际集团所有这些和查询接管在某些情况下10秒。 (我们有缓存,所以这只是用户第一次登录的那一天的问题)。

这牵涉到很多其他标准:

计算在内
  • 应该只是每类客户(即,最后一个,如果客户有两个介绍,它应该只计算一次 - 。我使用的解决这个问题的方法greatest-n-per-group
  • 生日和纪念日的日期应该是从今天
  • +/- 7天,所有的人,在过去60天内只记录应该算
  • 这些记录需要加入ED与客户表,以确保机会的销售人员对客户当前的销售人员

匹配下面是生成的查询(长):

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

要引用'或不引用 - 这是 – 2010-11-18 21:01:07

回答

2

一个良好的开端将被移除字符串比较并且把它们放在一个表分配的ID,并在

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

这个问题,用于将生日警报正常化为正确的类型。 – FrustratedWithFormsDesigner 2010-11-18 21:01:14

0

您可以通过删除where子句中的所有分组括号来更容易阅读。这将至少使其更容易看到怎么回事,优化

+0

我正在使用Zend Framework并且查询是自动构建的。我已经发布了输出。 – 2010-11-18 21:14:25

0

在你的每个子查询:

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

这意味着你只希望有NULL为CUSTOMER_ID opportunities o2。因为这些查询可以用2个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

我与现有的意见同意该警示文字需要在一个类型的表,用带来的机遇表的外键关系。

交给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 
0

除了提供的答案,我更换了留下了一个子查询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

在子查询中没有必要使用没有限制的ORDER BY - 如果删除它,速度应该会更快。子查询似乎也不需要。 – 2010-11-20 16:52:56

+0

我只希望每个用户每个marketing_message返回最近的条目。我看不到如何在不改变结果的情况下删除ORDER BY和子查询。你能解释一下你的意思吗? – 2010-11-22 23:31:40