2010-11-18 66 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