2009-02-12 13 views
18

Ho due tabelle: "film" e "utenti". C'è una relazione n: m tra quelli, che descrive quali filmati un utente ha visto. Questo è descritto con una tabella 'vista' Ora voglio scoprire per un dato utente, tutti i film che non ha visto. mia soluzione attuale è come questo:MySQL: ricerca di righe che non prendono parte a una relazione

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

Questo funziona bene, ma non sembra in scala molto bene. C'è un approccio migliore a questo?

+0

Se il ridimensionamento non è corretto, l'indicizzazione non è efficace. Quali sono i tuoi indici? – dkretz

+0

> Funziona bene ma non sembra scalare molto bene. C'è un approccio migliore a questo? Hai provato EXPLAIN su questa query? – VolkerK

risposta

27

Ecco un metodo tipico per eseguire questa query senza utilizzare il metodo subquery mostrato. Questo potrebbe soddisfare la richiesta di @ Godeke di vedere una soluzione basata su join.

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; 

Tuttavia, nella maggior parte delle marche di database questa soluzione può avere prestazioni peggiori rispetto alla soluzione di subquery. È preferibile utilizzare EXPLAIN per analizzare entrambe le query, per vedere quale funzionerà meglio in base allo schema e ai dati.

Ecco un'altra variazione sulla soluzione subquery:

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

Questo è un subquery correlato, che deve essere valutata per ogni riga della query esterna. Di solito questo è costoso e la tua query di esempio originale è migliore. D'altra parte, in MySQL "NOT EXISTS" è spesso meglio di "column NOT IN (...)"

Anche in questo caso, è necessario testare ciascuna soluzione e confrontare i risultati per essere sicuri. È una perdita di tempo scegliere una soluzione senza misurare le prestazioni.

+0

Continuo a dimenticare questo trucco "OUTER JOIN". Grazie! –

4

Non solo la query funziona, è l'approccio corretto al problema, come indicato. Forse puoi trovare un modo diverso per affrontare il problema? Un semplice LIMIT sulla selezione esterna dovrebbe essere molto veloce anche per tabelle grandi, ad esempio.

4

Visto è la tabella di join, quindi sì, questa sembra la soluzione corretta. Stai effettivamente "sottraendo" la serie di ID film in SEEN (per un utente) dalla totalità in MOVIES, risultando nei film non visti per quell'utente.

Questo è chiamato "join negativo" e purtroppo NOT IN o NOT EXISTS sono le opzioni migliori. (Mi piacerebbe vedere una sintassi di join negativa simile ai join INNER/OUTER/LEFT/RIGHT, ma dove la clausola ON potrebbe essere una dichiarazione di sottrazione).

@ La soluzione di Bill senza subquery dovrebbe funzionare, anche se, come ha notato, è una buona idea testare la soluzione per le prestazioni in entrambi i modi. Sospetto che la sottoquery o no, l'intero indice SEEN.ID (e ovviamente l'intero indice MOVIE.ID) verrà valutato in entrambi i modi: dipenderà da come l'ottimizzatore lo maneggia da lì.

0

Se il DBMS supporta indici bitmap, è possibile provarli.

+0

Ha taggato la domanda "mysql". MySQL non supporta gli indici bitmap. –

+0

Oops, non ho guardato il tag. :( –