2012-09-20 17 views
30

Questa potrebbe essere una domanda semplice, ma mi sembra di tirare fuori i miei capelli per trovare una soluzione elegante qui. Ho due classi del modello ActiveRecord, con un'associazione has_one e belongs_to tra di loro:Trovare nil has_one associazioni in cui query

class Item < ActiveRecord::Base 
    has_one :purchase 
end 

class Purchase < ActiveRecord::Base 
    belongs_to :item 
end 

Sto cercando un modo elegante per trovare tutti gli oggetti Item, che non hanno oggetto acquisto associato con loro, idealmente senza ricorrere a avere un valore booleano is_purchased o un attributo simile sull'articolo.

In questo momento ho:

purchases = Purchase.all 
Item.where('id not in (?)', purchases.map(&:item_id)) 

Che funziona, ma sembra inefficiente per me, come è eseguendo due query (e gli acquisti potrebbe essere un set di record di massa).

corso Rails 3.1.0

risposta

33

E 'abbastanza comune compito, SQL outer join di solito funziona bene per essa. Dai uno sguardo allo here, per esempio.

In te caso cercare di usare qualcosa come

not_purchased_items = Item.joins("LEFT OUTER JOIN purchases ON purchases.item_id = items.id").where("purchases.id IS null") 
+1

Questo collegamento è perfetto! Il join è praticamente esattamente ciò di cui avevo bisogno, dovevo semplicemente sostituire il punto precedente con 'where (" purchasing.item_id IS null ")' ed è bello andare. Grazie! –

+0

Se si utilizza 'has_many' o' has_one' è possibile ottenere il join senza dover scrivere la query effettiva usando 'Item.join (: acquisti)' –

21

trovato due altri modi railsey di fare questo:

Item.includes(:purchase).references(:purchase).where("purchases.id IS NULL") 

Item.includes(:purchase).where(purchases: { id: nil }) 

Tecnicamente il primo esempio funziona senza la clausola 'Riferimenti', ma Rotaie 4 spiedi avvisi di deprecazione senza di essa.

+0

Mentre questo è più succinto della risposta accettata, caricherà tutti i dati dalla tabella associata Se vuoi mantenere la tua query SQL bella e ordinata, usa il join personalizzato suggerito da @dimuch. – deadwards

+0

@deadwards Non penso sia vero. 'Asset.includes (: attachments) .where (attachments: {id: nil})' produce: "SELECT" assets "." Property_id "AS t0_r0, ..." attachments "." Updated_at "AS t1_r7 FROM" assets " LEFT OUTER JOIN "attachments" ON "attachments". "Property_id" = "assets". "Property_id" WHERE "attachments". "Id" IS NULL', come nell'esempio di @ dimuch. – bronson

+0

Rails 4 way: 'Item.includes (: purchase) .references (: purchase) .where (acquisti: {id: nil})'. Se chiami to_sql su di esso, lo vedrai sicuramente non carica tutti i dati. Fa esattamente la stessa cosa della risposta accettata. – bronson

3

Una versione più concisa della soluzione di @dimuch è quello di utilizzare il metodo left_outer_joins introdotta in Rails 5:

Item.left_outer_joins(:purchase).where(purchases: {id: nil}) 

Si noti che nella chiamata left_outer_joins:purchase è singolare (è il nome del metodo creato dal has_one) e nella clausola where:purchases è plurale (qui è il nome della tabella a cui appartiene il campo id)

+1

Puoi specificare la versione di Rails? Credo che questo sia> 5. – niborg

+0

Grazie @niborg. Questa è una buona idea. – ReggieB

Problemi correlati