2010-11-24 4 views
1

Essayer d'obtenir des données de la manière la plus efficace possible pour certains rapports, en utilisant Rails 2.3 et MySQL.Rails - Tirer et calculer efficacement des données sur plusieurs relations

Notre application a des utilisateurs, des offres et des achats. Les relations ressemblent à ceci:

class User 
    has_many :purchased_deals 
    has_many :deals, :through => :purchased_deals 
end 

class Deal 
    has_many :purchased_deals 
    has_many :users, :through => :purchased_deals 
end 

class PurchasedDeal 
    belongs_to :deal 
    belongs_to :user 
end 

Pour le rapport, je suis en cours d'exécution, je dois obtenir tous les utilisateurs qui ont effectué un achat (c.-à au moins un PurchasedDeal), puis la somme totale de toutes les offres qu'ils avoir acheté (le prix est attaché à la transaction, pas le PurchasedDeal).

Certainement, je pourrais commencer avec une liste de tous les utilisateurs, y compris les offres et les offres achetées. J'ai essayé cela, et la requête est massive (30 000 utilisateurs, donner ou prendre, 3 000 affaires, plus de 100 000 offres achetées).

Je pourrais commencer avec les utilisateurs, puis faire un .each et de trouver ceux qui ont un deal acheté, les diviser en leur propre groupe, puis itérer sur chacun des ceux pour obtenir le montant total acheté, mais c'est une bonne quantité de requêtes.

Actuellement, ces deux méthodes prennent tellement de temps que les demandes arrivent à expiration. Quel serait le moyen le plus efficace d'obtenir les données dont j'ai besoin? Ajouter des colonnes aux tables est une solution totalement acceptable, btw. J'ai un accès complet à la base de données pour faire ce dont j'ai besoin.

Merci!

Répondre

0

Pour obtenir une liste d'ID utilisateur avec plus d'un achat, vous pouvez faire suivant, qui accédera à une seule table:

user_ids = PurchasedDeal.count(:group => :user_id, :having => 'count_all > 0').keys 

, vous pouvez chercher ensuite tous ces utilisateurs:

users = User.find user_ids 

Les choses peuvent être accélérées avec un cache de compteur. Dans votre modèle d'utilisateur, ajoutez l'option :counter_cache => true à l'association has_many pour les offres achetées. Vous aurez besoin d'une colonne entière supplémentaire sur votre table d'utilisateurs et initialisez, ce qui pourrait se présenter comme suit dans une migration:

add_column :users, :purchased_deals_count, :integer, :null => false, :default => 0 
User.each { |u| User.reset_counters u, :purchased_deals } 

Une fois cette étape de la route, il devient beaucoup plus simple:

users = User.all :conditions => 'purchased_deals_count > 0' 

Rails gardera la colonne à jour pour vous, avec la plupart des opérations standard.


Obtenir le prix total impliquera toujours une jointure. Ou vous pouvez construire un hachage des prix de vente et faire le traitement fastidieux dans Ruby. Je ne suis pas un expert SQL, mais vous pouvez potentiellement vous débarrasser de la jointure en stockant le prix avec PurchasedDeal. Sinon, voici comment le faire avec une jointure:

user_id_to_price = PurchasedDeal.sum 'deal.price', :include => :deal, :group => :user_id 

Vous pouvez filtrer que sur seulement les utilisateurs que vous souhaitez en ajoutant quelque chose comme :conditions => ['user_id IN (?)', users].(Où users peut être une liste d'ID, mais aussi d'objets Utilisateur.)

+0

Lorsque j'essaie d'exécuter votre première instruction, cela renvoie une erreur mysql à propos de "count_all" étant une colonne inconnue. –

+0

Mon mauvais, cela aurait dû être ': avoir', plutôt que': conditions'. Fixé. –

+0

Comment trier/commander à nouveau user_id_to_price? le genre! La méthode me donne une erreur sans méthode (c'est un OrderedHash). –

0

En supposant que vous ajoutez la colonne de prix purchased_deals table, vous pouvez obtenir de l'information des utilisateurs et total des transactions prix comme celui-ci:

select users.id, sum(purchased_deals.price) from users, purchased_deals where users.id = purchased_deals.user_id group by users.id having sum(purchased_deals.price) > 0