Trova i record in cui il join non esiste

Ho la possibilità di limitare tutte le questions se un utente ha votato o meno su di esse. Nel modello:

 scope :answered_by, lambda {|u| joins(:votes).where("votes.user_id = ?", u.id) } scope :unanswered_by, lambda {|u| joins(:votes).where("votes.user_id != ?", u.id) } 

Nel controller, li chiamo così:

 @answered = Question.answered_by(current_user) @unanswered = Question.unanswered_by(current_user) 

L’ambito senza risposta non è corretto. In sostanza, voglio trovare dove non c’è il voto. Invece, sta cercando di cercare se c’è un voto che non è uguale all’utente corrente. Qualche idea su come restituire tutti i record in cui un join non esiste?

Usa un anti-semi-join di EXISTS :

 WHERE NOT EXISTS ( SELECT 1 FROM votes v WHERE v.some_id = base_table.some_id AND v.user_id = ? ) 

La differenza

… tra NOT EXISTS() (e) e NOT IN() (i) è duplice:

  • Prestazione
    (e) è generalmente più veloce. Può interrompere la scansione della tabella o dell’indice non appena viene trovata la prima riga corrispondente. (i) può anche essere ottimizzato dal pianificatore di query, ma in misura minore poiché la gestione NULL è più complessa.

  • Correttezza
    Se uno dei valori risultanti nell’espressione sottoquery può essere NULL , il risultato di (i) potrebbe essere NULL dove ci si aspetterebbe – e (e) restituire – TRUE . Il manuale:

Se tutti i risultati per riga sono o non uguali o nulli, con almeno un nullo, il risultato di NOT IN è nullo.

Essenzialmente, (NOT) EXISTS è la scelta migliore nella maggior parte dei casi.

Esempio

La tua query potrebbe assomigliare a questa:

 SELECT * FROM questions q WHERE NOT EXISTS ( SELECT 1 FROM votes v WHERE v.question_id = q.id AND v.user_id = ? ) 

Non partecipare ai votes nella query di base. Ciò annullerebbe lo sforzo.

Oltre a NOT EXISTS e NOT IN c’è anche LEFT JOIN / IS NULL . Altre varianti di syntax in questa risposta correlata:

  • Seleziona le righe che non sono presenti in un’altra tabella

prova questo e fammi sapere se funziona

EDIT-1

 scope :unanswered_questions, lambda { joins('LEFT OUTER JOIN votes ON questions.id = votes.question_id').where('votes.question_id IS NULL') } 

EDIT-2

 scope :unanswered_by, lambda {|u| where("questions.id NOT IN (SELECT votes.question_id from votes where votes.user_id = ?)",u.id) } 

E se vuoi fare una query EXISTS in modo elegante e Rails-ish, puoi usare Where Exists gem che ho scritto:

 Question.where_not_exists(:votes, user_id: current_user.id) 

Certo, puoi farne anche lo scopo:

 scope :unanswered_by, ->(user){ where_not_exists(:votes, user_id: user.id) }