2009-04-10 15 views
5

He estado usando SQL durante años, pero rara vez nada más que simples inserciones y selecciones, etc. ... así que no soy experto en SQL. Me pregunto si podría obtener ayuda para optimizar una declaración SQL más compleja que estoy ejecutando en SQLite, desde PHP hasta PDO.SQLite optimización de inserción de selección múltiple

La declaración parece funcionar correctamente, parece que tarda más de lo que esperaba (o tal vez estoy esperando demasiado).

Este es el 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') 
     ); 

Lo que tengo es una lista de suscriptores, en uno o más grupos. Quiero agregar suscriptores a una cola de correo, seleccionando aquellos que pertenecen a uno o más grupos (1,2,3) pero excluyo aquellos que también están en otro grupo de grupos (4,5,6).

En primer lugar, ¿el SQL anterior es típico de cómo hacer esto?

En segundo lugar, ¿qué índices debo tener para que esto funcione de la manera más eficiente posible?

Actualmente, se tardan unos 30 segundos en atravesar unos 5000 registros de suscriptores (y un puñado de grupos) en una especificación AVAG LAMP.

Al final del día, el rendimiento no es tan crítico, pero me gustaría entender esto mejor para que cualquier idea sea muy apreciada.

Brad

Respuesta

6

Es probable que el extra se suma que están matando. ¿Y si lo hace:

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') 
    ); 

También querría asegurarse de que tiene un índice en SubscriberGroups (SubscriberId, GroupID)

Mi suposición es que los suscriptores ya tiene un índice en ID, ¿verdad?

EDITAR: Otra opción, que puede o no ser más rápida. Mira los planes de consulta de cada uno para ver ...

Ésta puede ser una sola exploración índice que podría ser más rápido que el índice busca dos, pero depende de optimizador de 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

Gracias Matt, eso es genial. Su primera solución la obtuvo de 30 segundos a 5 o 6, lo cual es suficiente. No probé la segunda opción porque realmente no la entiendo, pero la tendré en cuenta si se convierte en un problema. Gracias de nuevo –

3

Otra manera de escribir el código SQL que podría ser más rápido (no tengo SQLite en el que poner a prueba):

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étodo de Matt también debería funcionar bien. Todo depende de cómo SQLite decida crear los planes de consulta.

Además, tenga en cuenta mis comentarios. Si realmente se definen como tipos de datos INT en su base de datos, habrá algún procesamiento adicional para convertir entre los dos tipos de datos diferentes. Si son cadenas en la base de datos, ¿hay alguna razón para eso? ¿Tiene valores no numéricos en esas columnas?

+0

Gracias Tom, tienes razón en los IDs citados ... no estoy seguro de por qué los tenía allí.No intenté con tu sugerencia porque Matt parece funcionar bien y parece que te falte la parte de excluir grupos (4,5,6). ¡Gracias de cualquier manera! –

+0

En realidad, al observar esto más de cerca me di cuenta de que una de las columnas de ID no estaba declarada como INTEGER y por eso necesitaba las comillas. SQLite no es muy quisquilloso con los tipos, por eso lo extrañé. Cambiando a entero y eliminando comillas ahora se ejecuta en aproximadamente 1/2 segundo. ¡Gracias! –

+0

El mío debe ocuparse de las exclusiones. ¿Lo has probado? De eso se trata todo el LEFT JOIN. Compruebe una columna NOT NULL de la tabla unida en la cláusula WHERE y si es NULL, sabe que no existe coincidencia. –

Cuestiones relacionadas