Rails 3 query a condizione del conteggio di un’associazione

In Rails 3 con mysql, supponiamo di avere due modelli, clienti e acquisti, ovviamente l’acquisto appartiene al cliente. Voglio trovare tutti i clienti con 2 ordini o più. Posso semplicemente dire:

Customer.includes(:purchases).all.select{|c| c.purchases.count > 2} 

In ogni caso, la riga sopra fa interrogazioni sulla grandezza di Customer.all e Purchase.all, quindi il tipo “seleziona” viene elaborato in ruby. In un grande database, preferirei di gran lunga evitare di fare tutti questi calcoli “selezionati” in ruby, e avere mysql fare l’elaborazione e solo darmi l’elenco dei clienti qualificati. Questo è molto più veloce (dal momento che mysql è più sintonizzato per farlo) e riduce significativamente la larghezza di banda dal database.

Sfortunatamente non sono in grado di evocare il codice con i blocchi di costruzione in binari (dove, avendo, gruppo, ecc.) Per fare in modo che ciò accada, qualcosa sulla falsariga di (psudo-code):

 Customer.joins(:purchases).where("count(purchases) > 2").all 

Mi accontenterò di una soluzione MySql diritta, sebbene preferisco di gran lunga trarlo nell’elegante struttura dei binari.

La documentazione su questa roba è piuttosto scarsa a questo punto. Cercherò di utilizzare Metawhere se farai altre query simili a questa. Usando Metawhere, puoi farlo (o qualcosa di simile, non sono sicuro che la syntax sia corretta):

 Customer.includes(:purchases).where(:purchases => {:count.gte => 2}) 

La bellezza di questo è che MetaWhere usa ancora ActiveRecord e Arel per eseguire la query, quindi funziona con il “nuovo” rails 3 modo di fare query.

Inoltre, probabilmente non si desidera chiamare .all alla fine, poiché ciò comporterà il ping della query sul database. Invece, si desidera utilizzare il caricamento lazy e non premere il db fino a quando non si richiede effettivamente i dati (nella vista, o qualche altro metodo che elabora i dati effettivi).

Non c’è bisogno di installare una gem per farlo funzionare (anche se metawhere è bello)

 Customer.joins(:purchases).group("customers.id").having("count(purchases.id) > ?",0) 

Questo è un po ‘più prolisso, ma se vuoi Clienti con where count = 0 o un SQL più flessibile, dovresti fare un LEFT JOIN

 Customer.joins('LEFT JOIN purchases on purchases.customer_id = customers.id').group('customers.id').having('count(purchases.id) = 0').length