2010-11-18 12 views
1

Ich entwickle ein Marketing-System. Auf der ersten Seite müssen Vertriebsmitarbeiter die Anzahl der Verkaufschancen sehen, die sie derzeit haben.Wie komplexe Abfrage optimieren?

dh.

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

Das Problem ist, ich bin UNION all diese ing und die Abfrage über 10s in einigen Fällen nimmt. (Wir haben Caching an Ort und Stelle, so dass dies nur ein Problem ist, wenn sich ein Benutzer zum ersten Mal für den Tag anmeldet).

Es gibt eine Menge anderer Kriterien beteiligt: ​​

  • in der Zählung enthalten sollte nur die letzte einer pro Kunde Typ sein (dh, wenn ein Kunde zwei Einführungen hat, sollte es nur einmal gezählt werden -. ich bin mit der greatest-n-per-group Methode, dies zu erreichen)
  • für Geburtstage und Jubiläen, sollte das Datum +/- 7 Tage ab heute
  • für alle von ihnen, nur die Datensätze in den letzten 60 Tagen sind zu
  • gezählt
  • Diese Datensätze müssen beitreten ed mit dem Kunden-Tabelle, um sicherzustellen, dass der Verkäufer die Person ist

Hier aktuellen Verkaufs des Kunden passt die Möglichkeit die erzeugte Abfrage (Es ist lang):

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) 

Ich habe Indizes auf folgenden Feldern in der opportunities Tabelle:

  • 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

Jede Hilfe dieser Optimierung würde sehr geschätzt werden. Ich bin offen dafür, andere Tabellen oder Ansichten zu erstellen, wenn es sein muss.

+0

zu zitieren "oder nicht zu zitieren - das ist die Frage –

Antwort

2

Ein guter Platz zur würde beginnen, die String-Vergleiche zu entfernen und sie in einer Tabelle mit zugeordneten IDs setzen und dem Hinzufügen von numerischen Spalten an der Stelle der

opportunities.marketing_message != 'Birthday Alert' 

So müßten Sie ...

[id] [name] 
1  Birthday Alert 
2  Anniversary 

Numerische Vergleiche sind auch mit Indizierung immer viel schneller. Auf diese Weise können Sie auch in Zukunft problemlos neue Typen hinzufügen.

Dieser Teil ist redundant, Sie brauchen nicht AND (opportunities.communication_alert_date >= DATE_SUB(NOW(), INTERVAL 60 DAY)), weil die Klausel direkt vor ihm die Aufgabe erledigen wird.

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 für die Normalisierung" "Birthday Alert" in einen richtigen Typ. – FrustratedWithFormsDesigner

0

Sie könnten es einfacher zu lesen, indem Sie alle Gruppierung Klammern in der WHERE-Klausel loszuwerden. Das wäre zumindest machen es leichter zu sehen, was los ist und

+0

Ich benutze Zend Framework und die Abfrage wird automatisch erstellt. Ich habe die Ausgabe so geschrieben wie sie ist. –

0

In jeder Unterabfrage Sie haben zu optimieren:

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

Das bedeutet, dass Sie nur opportunities o2 möchten, die für customer_id NULL haben. Aus diesem Grund können diese Abfragen mit 2 INNER Joins geschrieben werden, anstatt 1 OUTER und 1 INNER Join, was wahrscheinlich schneller ist. Etwas wie folgt aus:

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

ich mit bestehenden Kommentare einig, dass der Alert-Text in einer Art Tabelle sein muss, mit einer Fremdschlüsselbeziehung zu dem Tisch CHANCEN.

Lassen Sie es zu zwei Abfragen zu Zend, wenn Sie nur eine benötigen:

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

Neben den Antworten zur Verfügung gestellt, ersetzen ich die linke Seite mit einer Unterabfrage JOIN nur die letzten Instanzen von Typ zurückzukehren. Dies schien immens zu helfen.

dh (nur für die Geburtstag und Jahrestag Zahl):

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

Es gibt keine Notwendigkeit für die ORDER BY ohne LIMIT in der Unterabfrage - es sollte schneller sein, wenn Sie es entfernen. Die Unterabfrage scheint auch nicht benötigt zu werden. –

+0

Ich möchte nur die neuesten Einträge pro Benutzer pro marketing_message zurückgegeben. Ich sehe nicht, wie ich ORDER BY und die Unterabfrage entfernen könnte, ohne die Ergebnisse zu ändern. Kannst du mehr erklären, was du meinst? –