2009-02-12 22 views
18

Tengo dos tablas: 'películas' y 'usuarios'. Hay una relación n: m entre esos, que describe qué películas ha visto un usuario. Esto se describe con una tabla 'vista' Ahora quiero averiguar para un usuario dado, todas las películas que no ha visto. Mi solución actual es la siguiente:MySQL: Encontrar filas que no forman parte de una relación

SELECT * 
FROM movies 
WHERE movies.id NOT IN (
    SELECT seen.movie_id 
    FROM seen 
    WHERE seen.user_id=123 
) 

Esto funciona bien, pero no parece que escalar muy bien. ¿Hay un mejor enfoque para esto?

+0

Si no está escalando bien, su indexación no será efectiva. ¿Cuáles son tus índices? – dkretz

+0

> Esto funciona bien, pero no parece escalar muy bien. ¿Hay un mejor enfoque para esto? ¿Has probado EXPLAIN en esta consulta? – VolkerK

Respuesta

27

Esta es una forma típica de hacer esta consulta sin usar el método de subconsulta que mostró. Esto puede satisfacer la solicitud de @Godeke para ver una solución basada en unirse.

SELECT * 
FROM movies m 
LEFT OUTER JOIN seen s 
ON (m.id = s.movie_id AND s.user_id = 123) 
WHERE s.movie_id IS NULL; 

Sin embargo, en la mayoría de las marcas de base de datos, esta solución puede funcionar peor que la solución de subconsulta. Lo mejor es usar EXPLAIN para analizar ambas consultas, para ver cuál le irá mejor dado su esquema y datos.

Aquí es otra variante de la solución de subconsulta:

SELECT * 
FROM movies m 
WHERE NOT EXISTS (SELECT * FROM seen s 
        WHERE s.movie_id = m.id 
        AND s.user_id=123); 

Ésta es una consulta correlacionada, que debe ser evaluado para cada fila de la consulta externa. Por lo general, esto es costoso y su consulta de ejemplo original es mejor. Por otro lado, en MySQL "NOT EXISTS" a menudo es mejor que "column NOT IN (...)"

De nuevo, debe probar cada solución y comparar los resultados para estar seguro. Es una pérdida de tiempo elegir cualquier solución sin medir el rendimiento.

+0

Me sigo olvidando de este truco 'OUTER JOIN'. ¡Gracias! –

4

No solo su consulta funciona, sino que es el enfoque correcto para el problema tal como se establece. ¿Quizás pueda encontrar una forma diferente de abordar el problema? Un LÍMITE simple en su selección externa debería ser muy rápido incluso para tablas grandes, por ejemplo.

4

Visto es su tabla de unión, así que sí, esta parece ser la solución correcta. Está efectivamente "restando" el conjunto de ID de película en SEEN (para un usuario) de la totalidad en PELÍCULAS, lo que da como resultado las películas invisibles para ese usuario.

Esto se conoce como "unión negativa" y, lamentablemente, NO ENTRE O NO EXISTE son las mejores opciones. (Me encantaría ver una sintaxis de unión negativa que fuera similar a las uniones INTERIOR/EXTERIOR/IZQUIERDA/DERECHA, pero donde la cláusula ON podría ser una declaración de resta).

@ La solución de Bill sin una subconsulta debería funcionar, aunque como señaló es una buena idea probar su solución para el rendimiento en ambos sentidos. Sospecho que subconsultar o no, todo el índice SEEN.ID (y, por supuesto, todo el índice MOVIE.ID) se evaluará en ambos sentidos: dependerá de cómo lo maneje el optimizador desde allí.

0

Si su DBMS admite índices de mapa de bits, puede probarlos.

+0

Marcó la pregunta 'mysql'. MySQL no es compatible con los índices de mapa de bits. –

+0

Vaya, no miré la etiqueta. :( –

Cuestiones relacionadas