2009-06-11 10 views
10

Come posso scrivere una query di ricerca AR per ottenere i risultati ordinati per il numero di record in un'associazione has_many?Come ordinare Rails AR.find per numero di oggetti in una relazione has_many

class User < ActiveRecord::Base 
    has_many :photos 
end 

voglio fare una cosa del genere ...

User.find(:all, :order => photos.count) 

mi rendo conto il ritrovamento non è un codice valido. Dì che ho i seguenti dati.

User 1, which has 3 photos 
User 2, which has 5 photos 
User 3, which has 2 photos 

Voglio che la mia scoperta di riportarmi gli utenti in ordine di ...

User 2, 
User 1, 
User 3 

in base al conteggio delle foto degli utenti

risposta

13

Se non si desidera una colonna in più, si può sempre chiedere una colonna aggiuntiva nel set di risultati restituito:

User.all(:select => "#{User.table_name}.*, COUNT(#{Photo.table_name}.id) number_of_photos", 
     :joins => :photos, 
     :order => "number_of_photos") 

Questo genera il seguente SQL:

SELECT users.*, COUNT(photos.id) number_of_photos 
FROM `users` INNER JOIN `photos` ON photos.user_id = users.id 
ORDER BY number_of_photos 
+0

Questo davvero quello che stavo cercando non l'ho spiegato bene. –

+0

+1 Per utilizzare Model.table_name, -1 per non utilizzare: joins => "# {Photo.table_name.pluralize}"! –

+0

-1 per caricare tutto con entusiasmo, -1 per scrivere il proprio conteggio SQL che tutti usano MySQL. – ChuckE

1

Counter cache aiuterà, ma si Avrai bisogno di una colonna in più nel db.

-4

La tua domanda non ha senso. Il parametro :order specifica un nome di colonna e una direzione di ordinamento opzionale cioè asc (finale) o desc (finale).

Qual è il risultato che stai cercando di raggiungere?

+0

Giusto, mi rendo conto che la mia ricerca non è un codice valido. Dire che ho i seguenti dati. Utente 1, che ha 3 foto utente 2, che ha 5 foto utente 3, che ha 2 foto Voglio che la mia scoperta di riportarmi gli utenti in ordine di ... utente 2, Utente 1, Utente 3 basato sul conteggio delle foto degli utenti. –

+0

Sta cercando di ordinare gli utenti in base alla somma delle loro foto –

19

Il modo più semplice per ottenere ciò è probabilmente aggiungere un contatore di cache a quel modello e quindi ordinare per quella colonna.

class Photo < ActiveRecord::Base 
    belongs_to :user, :counter_cache => true 
end 

E non dimenticare di aggiungere una colonna al tuo users tabella chiamata photos_count.

Allora si sarà in grado di ...

User.find(:all, :order => 'photos_count') 
7

Se non si desidera aggiungere una colonna di cache di contatore, l'unica opzione è quella di ordinare, dopo il ritrovamento. Se si associa l'associazione :include alla ricerca, non si verificherà alcun lavoro di database aggiuntivo.

users = User.find(:all, :include => :photos).sort_by { |u| -u.photos.size } 

Nota il segno negativo nel blocco sort_by per ordinare da alta a bassa.

+1

Credo che il recupero e solo dopo l'ordinamento degli oggetti ruby ​​sia molto più lento di quello preformato a livello di database. – gmile

+0

Quale è più veloce dipenderà dalla situazione specifica. Su un server di database molto occupato, scaricare l'ordinamento sul server delle applicazioni (presupponendo hardware diverso) potrebbe essere più veloce. In questo caso, hai ragione perché la mia soluzione inserisce i record di foto. Evitare il comando ': include' e fare ancora il genere in Ruby comporterebbe ulteriori domande, il che è anche peggio. La risposta accettata di François mostra come ordinare all'interno della query, senza inserire le Foto, che è quasi certamente la soluzione migliore per questo caso. –

0

I' d aggiungere questo come commento sulla risposta in alto, ma non posso per qualche motivo. Secondo questo post:

http://m.onkey.org/active-record-query-interface

Il metodo User.all(options) sarà deprecato dopo Rails 3.0.3, e sostituito con un gruppo di altri (chainable a portata di mano,) roba tipo di record attivo, ma rende molto difficile capire come mettere insieme lo stesso tipo di query.

Come risultato, sono andato avanti e implementato il metodo del contatore di cache.Questo è stato abbastanza facile e indolore con l'eccezione che è necessario ricordare di aggiornare le informazioni della colonna nella migrazione, altrimenti tutti i record esistenti avranno "0".

Ecco quello che ho usato nel mio migrazione:

class AddUserCountToCollections < ActiveRecord::Migration 
    def self.up 
    add_column :collections, :collectionusers_count, :integer, :default => 0 
    Collection.reset_column_information 
    Collection.all.each do |c| 
     Collection.update_counters c.id, :collectionusers_count => c.collectionusers.count 
    end 
    end 

    def self.down 
    remove_column :collections, :collectionusers_count 
    end 
end 

In teoria questo dovrebbe essere più veloce, troppo. Spero che sia utile andando avanti.

+0

può anche utilizzare reset_counters al posto di update_counters – MatthewFord

5

Vorrei consigliarvi di non scrivere direttamente SQL, poiché le sue implementazioni possono variare da negozio a negozio. Per fortuna, hai arel:

User.joins(:photos).group(Photo.arel_table[:user_id]). 
    order(Photo.arel_table[:user_id].count) 
+0

Perché non stiamo finanziando questo? –

+0

Sei a conoscenza di un modo per aggiungere anche la direzione di ordinamento in modo che sia crescente o decrescente? – manveru

Problemi correlati