2009-04-10 6 views
5

J'utilise SQL depuis des années mais rarement plus rien de plus simple insère et sélectionne etc ... donc je ne suis pas un expert SQL. Je me demande si je pourrais obtenir de l'aide pour optimiser une instruction SQL plus complexe que j'exécute sur SQLite, de PHP à PDO.SQLite optimisant l'insertion multi-sélection

La déclaration semble fonctionner correctement, semble juste prendre plus de temps que je me serais attendu (ou peut-être que je m'attends à trop).

C'est le SQL:

INSERT OR IGNORE INTO MailQueue(SubscriberID, TemplateID) 
    SELECT Subscribers.ID, '1' AS TemplateID 
    FROM Subscribers 
    INNER JOIN SubscriberGroups ON Subscribers.ID=SubscriberGroups.SubscriberID 
    WHERE SubscriberGroups.GroupID IN ('1', '2', '3') 
    AND Subscribers.ID NOT IN 
     ( 
     SELECT Subscribers.ID FROM Subscribers 
     INNER JOIN SubscriberGroups ON Subscribers.ID=SubscriberGroups.SubscriberID 
     WHERE SubscriberGroups.GroupID IN ('4', '5', '6') 
     ); 

Ce que j'ai une liste d'abonnés, dans un ou plusieurs groupes. Je veux ajouter des abonnés à une file d'attente de courrier, en sélectionnant ceux qui appartiennent à un ou plusieurs groupes (1,2,3) mais excluez ceux qui sont également dans un autre groupe de groupes (4,5,6).

Premièrement, le SQL ci-dessus est-il typique de la façon de faire cela? Deuxièmement, quels indices dois-je avoir pour que ce travail fonctionne le plus efficacement possible?

Actuellement, il faut environ 30 secondes pour parcourir environ 5000 enregistrements d'abonnés (et une poignée de groupes) sur une LAMP moyenne. À la fin de la journée, les performances ne sont pas si importantes, mais je voudrais mieux comprendre ce genre de choses afin que toute idée soit grandement appréciée.

Brad

Répondre

6

Les chances sont que les jointures supplémentaires vous tue. Que faire si vous faites:

SELECT Subscribers.ID, '1' AS TemplateID 
FROM Subscribers 
WHERE EXISTS(SELECT * 
       FROM SubscriberGroups 
       WHERE Subscribers.ID=SubscriberGroups.SubscriberID 
           AND SubscriberGroups.GroupID IN ('1', '2', '3')) 

    AND NOT EXISTS(SELECT * 
        FROM SubscriberGroups 
        WHERE Subscribers.ID=SubscriberGroups.SubscriberID 
        AND SubscriberGroups.GroupID IN ('4', '5', '6') 
    ); 

Vous aurait également besoin de vous assurer que vous avez un index sur SubscriberGroups (SubscriberId, GroupID)

Je pense que les abonnés a déjà un index sur ID, non?

EDIT: Autre option, qui peut être plus rapide ou moins rapide. Regardez les plans de requête de chacun pour voir ...

Celui-ci peut être un seul balayage d'index qui pourrait être plus rapide que deux index cherche, mais cela dépend de l'optimiseur SQLite ...

SELECT Subscribers.ID, '1' AS TemplateID 
FROM Subscribers 
INNER JOIN(SELECT SUM(CASE WHEN GroupID IN('1', '2', '3') THEN 1 ELSE 0 END) AS inGroup, 
        SUM(CASE WHEN GroupID IN('4', '5', '6') THEN 1 ELSE 0 END) AS outGroup, 
        SubscriberID 
          FROM SubscriberGroups 
         WHERE SubscriberGroups.GroupID IN ('1', '2', '3', '4', '5', '6') 
     ) SubscriberGroups 
     ON Subscribers.ID=SubscriberGroups.SubscriberID 
     AND inGroup > 0 
     AND outGroup = 0 
+0

Merci Matt, c'est génial. Votre première solution l'a fait passer de 30 secondes à environ 5 ou 6, ce qui est assez bon. Je n'ai pas essayé la deuxième option parce que je ne la comprends pas vraiment, mais je la garderai à l'esprit si cela devient un problème. Merci encore –

3

Une autre façon d'écrire le code SQL qui pourrait être plus rapide (je n'ai pas SQLite pour tester):

SELECT 
    S.ID, 
    '1' AS TemplateID  -- Is this really a string? Does it need to be? 
FROM 
    Subscribers S 
LEFT OUTER JOIN SubscriberGroups SG ON 
    SG.SubscriberID = S.ID 
WHERE 
    SG.SubscriberID IS NULL AND 
    EXISTS 
    (
      SELECT 
       * 
      FROM 
       SubscriberGroups SG2 
      WHERE 
       SG2.SubscriberID = S.ID AND 
       SG2.GroupID IN ('1', '2', '3') -- Again, really strings? 
    ) 

méthode de Matt devrait aussi bien fonctionner. Tout dépend de la façon dont SQLite décide de créer les plans de requête.

Aussi, s'il vous plaît noter mes commentaires. Si ceux-ci sont réellement définis comme des types de données INT dans votre base de données, il y aura un traitement supplémentaire pour convertir entre les deux types de données différents. Si ce sont des chaînes dans la base de données, y a-t-il une raison à cela? Avez-vous des valeurs non numériques dans ces colonnes?

+0

Merci Tom, vous avez raison sur les IDs cités ... je ne sais pas pourquoi je les avais là.Je n'ai pas essayé votre suggestion parce que le mat semble bien fonctionner et que vous avez manqué la partie des groupes d'exclusion (4,5,6). Merci quand même! –

+0

En regardant cela de plus près, j'ai réalisé que l'une des colonnes d'ID n'était pas déclarée comme INTEGER, c'est pourquoi j'avais besoin des guillemets. SQLite n'est pas vraiment difficile sur les types, c'est pourquoi je l'ai manqué. En changeant en entier et en supprimant les guillemets, il fonctionne maintenant en environ 1/2 seconde. Merci! –

+0

La mine devrait prendre soin des exclus. Avez-vous testé? Voilà à quoi sert l'ensemble de LEFT JOIN. Vérifiez une colonne NOT NULL de la table jointe dans la clause WHERE et si elle est NULL, vous savez qu'il n'existe aucune correspondance. –