2009-02-12 3 views
18

Ich habe zwei Tabellen: "Filme" und "Benutzer". Zwischen diesen gibt es eine n: m-Beziehung, die beschreibt, welche Filme ein Benutzer gesehen hat. Dies ist mit einer Tabelle 'gesehen' beschrieben Nun möchte ich für einen bestimmten Benutzer alle Filme herausfinden, die er nicht gesehen hat. Meine aktuelle Lösung ist wie folgt:MySQL: Finden von Zeilen, die nicht in einer Beziehung teilnehmen

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

Dies funktioniert gut, aber scheint nicht sehr gut zu skalieren. Gibt es einen besseren Ansatz dafür?

+0

Wenn es nicht gut skaliert, ist Ihre Indizierung nicht effektiv. Was sind deine Indizes? – dkretz

+0

> Das funktioniert gut, scheint aber nicht sehr gut zu skalieren. Gibt es einen besseren Ansatz dafür? Haben Sie EXPLAIN für diese Abfrage versucht? – VolkerK

Antwort

27

Hier ist ein typischer Weg, um diese Abfrage ohne Verwendung der Unterabfrage-Methode, die Sie gezeigt haben, zu tun. Dies kann @ Godekes Anfrage nach einer Join-basierten Lösung erfüllen.

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; 

Allerdings kann diese Lösung in den meisten Marken der Datenbank schlechter als die Unterabfrage Lösung durchführen. Es ist am besten, EXPLAIN zu verwenden, um beide Abfragen zu analysieren, um zu sehen, welche Version mit Ihrem Schema und Ihren Daten besser funktioniert.

Hier ist eine andere Variante der Unterabfrage Lösung:

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

Dies ist eine korrelierte Unterabfrage, die für jede Zeile der äußeren Abfrage ausgewertet werden müssen. Normalerweise ist das teuer und Ihre ursprüngliche Beispielabfrage ist besser. Auf der anderen Seite ist in MySQL "" oft besser als "column NOT IN (...)"

Auch hier müssen Sie jede Lösung testen und vergleichen Sie die Ergebnisse, um sicher zu gehen. Es ist Zeitverschwendung, eine Lösung zu wählen, ohne die Leistung zu messen.

+0

Ich vergesse immer wieder über diesen 'OUTER JOIN' Trick. Danke! –

4

Nicht nur Ihre Abfrage funktioniert, es ist der richtige Ansatz für das Problem wie angegeben. Vielleicht können Sie einen anderen Weg finden, das Problem anzugehen? Ein einfaches LIMIT auf Ihrer äußeren Auswahl sollte beispielsweise für große Tabellen sehr schnell sein.

4

Gesehen ist Ihre Join Tabelle, also ja, das sieht nach der richtigen Lösung aus. Sie "subtrahieren" den Satz von Film-IDs in SEEN (für einen Benutzer) von der Gesamtheit in MOVIES, was zu den ungesehenen Filmen für diesen Benutzer führt.

Dies wird als "negativer Join" bezeichnet, und leider sind NICHT IN oder NOT EXISTS die besten Optionen. (Ich würde gerne eine negative Join-Syntax sehen, die INNER/OUTER/LINKS/RECHTS-Joins ähnlich ist, aber wo die ON-Klausel eine Subtraktions-Anweisung sein könnte).

@ Bills Lösung ohne eine Unterabfrage sollte funktionieren, obwohl, wie er bemerkte, es eine gute Idee ist, Ihre Lösung für Leistung in beide Richtungen zu testen. Ich vermute, dass die Unterabfrage oder nicht, der gesamte SEEN.ID-Index (und natürlich der gesamte MOVIE.ID-Index) wird in beide Richtungen ausgewertet werden: Es wird davon abhängen, wie der Optimizer es von dort behandelt.

0

Wenn Ihr DBMS Bitmap-Indizes unterstützt, können Sie sie versuchen.

+0

Er markierte die Frage 'mysql'. MySQL unterstützt keine Bitmap-Indizes. –

+0

Ups, ich habe das Tag nicht angesehen. :( –

Verwandte Themen