2010-11-18 21 views
1

Je développe un système de type marketing. En première page, l'une des exigences est que le personnel des ventes puisse voir le nombre d'opportunités de ventes qu'ils ont actuellement.Comment optimiser une requête complexe?

ie.

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

Le problème est que je suis UNION ing tous ces éléments et la requête reprend 10s dans certains cas. (La mise en cache est en place, ce qui pose problème uniquement la première fois qu'un utilisateur se connecte pour la journée).

Il y a beaucoup d'autres critères impliqués:

  • inclus dans le décompte ne devrait être le dernier par client par type (par exemple, si un client a deux introductions, il ne doit être compté qu'une seule fois -. J'utilise la méthode greatest-n-per-group d'y parvenir)
  • pour les anniversaires, la date devrait être +/- 7 jours à compter d'aujourd'hui
  • pour tous, seuls les enregistrements dans les 60 derniers jours devraient être comptés
  • ces enregistrements doivent être rejoindre ed avec les clients table pour faire en sorte que la personne de vente opportunité correspond à la personne de vente actuel du client

Voici la requête générée (elle est longue):

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) 

Je index sur les champs suivants dans le tableau 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

Toute aide l'optimisation de ce serait grandement apprécié. Je suis ouvert à la création d'autres tables ou vues si nécessaire.

+0

pour citer 'ou ne pas citer - c'est la question –

Répondre

2

Un bon endroit pour commencer serait enlever les comparaisons de chaînes et de les mettre dans une table avec ID attribués et l'ajout de colonnes numériques dans le lieu de

opportunities.marketing_message != 'Birthday Alert' 

Vous auriez ...

[id] [name] 
1  Birthday Alert 
2  Anniversary 

Les comparaisons numériques sont toujours beaucoup plus rapides, même avec l'indexation. Cela vous permettrait également d'ajouter facilement de nouveaux types dans le futur.

Cette partie est redondante, vous n'avez pas besoin de AND (opportunities.communication_alert_date >= DATE_SUB(NOW(), INTERVAL 60 DAY)) parce que la clause juste avant elle fera le travail.

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 pour normaliser' 'Birthday Alert'' dans un type approprié. – FrustratedWithFormsDesigner

0

Vous pouvez faciliter la lecture en supprimant toutes les parenthèses de regroupement de la clause where. Ce serait au moins plus facile de voir ce qui se passe et d'optimiser

+0

J'utilise Zend Framework et la requête est construite automatiquement. J'ai posté la sortie telle quelle. –

0

Dans chaque sous-requête vous avez:

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

Cela signifie que vous ne souhaitez que opportunities o2 qui ont NULL pour customer_id. A cause de cela, ces requêtes peuvent être écrites avec 2 INNER jointures au lieu de 1 OUTER et 1 INNER rejoindre ce qui est probablement plus rapide. Quelque chose comme ceci:

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

Je suis d'accord avec les commentaires existants que le texte d'alerte doit être dans un tableau de type, avec une relation de clé étrangère à la table des chances.

Laisser à Zend deux requêtes lorsque vous avez seulement besoin d'un:

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

En plus des réponses fournies, j'ai remplacé l'LEFT JOIN avec une sous-requête pour renvoyer uniquement les cas les plus récents par type. Cela a semblé aider énormément.

-à-dire (pour seulement le nombre anniversaire et anniversaire):

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

Il n'y a pas besoin d'ORDER BY sans LIMIT dans la sous-requête - il devrait être plus rapide si vous le supprimez. La sous-requête ne semble pas non plus nécessaire. –

+0

Je souhaite uniquement les entrées les plus récentes par utilisateur par message marketing retourné. Je ne vois pas comment je pourrais supprimer l'ORDER BY et la sous-requête sans changer les résultats. Pourriez-vous expliquer plus ce que vous voulez dire? –