2009-02-12 14 views
18

J'ai deux tables: 'movies' et 'users'. Il existe une relation n: m entre ceux-ci, décrivant les films que l'utilisateur a vu. Ceci est décrit avec une table 'vu' Maintenant, je veux savoir pour un utilisateur donné, tous les films qu'il n'a pas vu. Ma solution actuelle est comme ceci:MySQL: Trouver des lignes qui ne participent pas à une relation

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

Cela fonctionne bien, mais ne semble pas à l'échelle très bien. Y a-t-il une meilleure approche à cela?

+0

Si la mise à l'échelle échoue, l'indexation n'est pas effective. Quels sont vos index? – dkretz

+0

> Cela fonctionne bien, mais ne semble pas très bien évoluer. Y a-t-il une meilleure approche à cela? Avez-vous essayé EXPLAIN sur cette requête? – VolkerK

Répondre

27

Voici une manière typique de faire cette requête sans utiliser la méthode de sous-requête que vous avez montrée. Cela peut satisfaire la demande de @ Godeke de voir une solution basée sur les jointures. Cependant, dans la plupart des marques de base de données, cette solution peut être moins performante que la solution de la sous-requête. Cependant, dans la plupart des marques de base de données, cette solution peut être moins performante que la solution de sous-requête. Il est préférable d'utiliser EXPLAIN pour analyser les deux requêtes, afin de déterminer laquelle sera la mieux adaptée à votre schéma et à vos données.

Voici une autre variante de la solution de sous-requête:

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

Ceci est une sous-requête corrélée, qui doit être évalué pour chaque ligne de la requête externe. Habituellement, cela coûte cher, et votre requête d'exemple originale est meilleure. D'autre part, dans MySQL "NOT EXISTS" est souvent mieux que "column NOT IN (...)"

Encore une fois, vous devez tester chaque solution et comparer les résultats pour être sûr. C'est une perte de temps de choisir une solution sans mesurer les performances.

+0

Je n'arrête pas d'oublier ce truc "OUTER JOIN" Merci! –

4

Non seulement votre requête fonctionne, mais c'est la bonne approche du problème. Peut-être que vous pouvez trouver une manière différente d'aborder le problème? Une simple LIMITE sur votre sélection externe devrait être très rapide, même pour les grandes tables, par exemple.

4

Vu est votre table de jointure, donc oui, cela ressemble à la bonne solution. Vous «soustrayez» effectivement l'ensemble des ID de film dans SEEN (pour un utilisateur) de la totalité dans FILMS, ce qui entraîne les films non-visionnés pour cet utilisateur. C'est ce qu'on appelle une "jointure négative", et malheureusement, PAS IN ou NOT EXISTS sont les meilleures options. (J'aimerais voir une syntaxe de jointure négative similaire aux jointures INNER/OUTER/LEFT/RIGHT, mais où la clause ON pourrait être une instruction de soustraction). La solution de Bill sans sous-requête devrait fonctionner, bien que, comme il l'a noté, c'est une bonne idée de tester votre solution pour les performances dans les deux sens. Je suspecte cette sous-requête ou pas, l'index SEEN.ID entier (et bien sûr l'index MOVIE.ID entier) va être évalué des deux manières: cela dépendra de la façon dont l'optimiseur le gère à partir de là.

0

Si votre SGBD prend en charge les index bitmap, vous pouvez les essayer.

+0

Il a tagué la question 'mysql'. MySQL ne supporte pas les index bitmap. –

+0

Oups, je n'ai pas regardé l'étiquette. :( –