2010-01-06 6 views
12

En utilisant SQLAlchemy, j'ai une relation de un à plusieurs avec deux tables - utilisateurs et scores. J'essaie d'interroger les 10 meilleurs utilisateurs triés par leur score global au cours des X derniers jours.Requête de filtre SQLAlchemy par objet connexe

users: 
    id 
    user_name 
    score 

scores: 
    user 
    score_amount 
    created 

Ma requête en cours est:

top_users = DBSession.query(User).options(eagerload('scores')).filter_by(User.scores.created > somedate).order_by(func.sum(User.scores).desc()).all() 

Je sais que cela est clairement pas correct, il est juste ma meilleure estimation. Cependant, après avoir regardé la documentation et googling, je ne trouve pas de réponse.

EDIT: Peut-être que cela aiderait si je dessinai ce que la requête MySQL ressemblerait à ceci:

SELECT user.*, SUM(scores.amount) as score_increase 
FROM user LEFT JOIN scores ON scores.user_id = user.user_id 
WITH scores.created_at > someday 
ORDER BY score_increase DESC 
+0

l'erreur dans l'utilisation eagerload() conjointement avec le critère par rapport à son jointure est expliqué dans ce Entrée FAQ: http: //www.sqlalchemy.org/trac/wiki/FAQ # ImusinglazyFalsetocreateaJOINOUTERJOINandSQLAlchemyisnotconstructingthequerywhenItrytoaddaWHEREORDERBYLIMITetc.whichreliesupontheOUTERJOIN – zzzeek

Répondre

14

La voie rangée unique rejoint, avec une group_by ajoutée pour toutes les colonnes de l'utilisateur, bien que MySQL vous permettra de groupe uniquement sur la colonne « id » si vous choisissez:

sess.query(User, func.sum(Score.amount).label('score_increase')).\ 
       join(User.scores).\ 
       filter(Score.created_at > someday).\ 
       group_by(User).\ 
       order_by("score increase desc") 

Ou si vous voulez juste les utilisateurs du résultat:

sess.query(User).\ 
      join(User.scores).\ 
      filter(Score.created_at > someday).\ 
      group_by(User).\ 
      order_by(func.sum(Score.amount)) 

les deux ci-dessus ont une inefficacité que vous regroupement sur toutes les colonnes de chose « utilisateur » (ou que vous utilisez « groupe sur quelques colonnes » de MySQL, qui est MySQL seulement). Pour minimiser cela, l'approche sous-requête:

subq = sess.query(Score.user_id, func.sum(Score.amount).label('score_increase')).\ 
        filter(Score.created_at > someday).\ 
        group_by(Score.user_id).subquery() 
sess.query(User).join((subq, subq.c.user_id==User.user_id)).order_by(subq.c.score_increase) 

Un exemple du scénario identique est dans le tutoriel ORM à: http://docs.sqlalchemy.org/en/latest/orm/tutorial.html#selecting-entities-from-subqueries

+0

Salut merci pour la réponse. Cela fonctionne bien et la documentation aide beaucoup. Comment aurais-je accès au score_increase pour un utilisateur? Prenons par exemple la requête est assignée à la variable top_users et je boucle à travers chaque utilisateur. user.score_increase ne fonctionne pas, pas plus que user.UserScore.score_increase. – Marc

+0

en utilisant la troisième requête, si vous parcourez sess.query (User, subq.c.score_increase), vous obtiendrez des tuples de (User, score_increase) – zzzeek

+0

hmm Je dois manquer quelque chose ici. Dans le cas où cela est important, j'utilise Turbogears 2 et j'affecte le résultat de la troisième requête à une variable top_users qui est disponible dans mes templates. Je fais alors une boucle - pour l'utilisateur dans top_users: print user.user_name + '' + user.score_increase - fondamentalement je veux montrer le montant que les utilisateurs marquent a augmenté le nombre x de jours passés. Je ne comprends pas comment accéder aux données jointes dans le tuple top_users. – Marc

1

Vous aurez besoin d'utiliser une sous-requête pour calculer le score global pour chaque utilisateur. Les sous-requêtes sont décrites ici:

0

Je suppose que la colonne (pas la relation) que vous utilisez pour la jointure s'appelle Score.user_id, donc changez-le si ce n'est pas le cas.

Vous aurez besoin de faire quelque chose comme ceci:

DBSession.query(Score.user_id, func.sum(Score.score_amount).label('total_score')).group_by(Score.user_id).filter(Score.created > somedate).order_by('total_score DESC')[:10] 

Cependant, cela se traduira par tuples de (user_id, total_score). Je ne sais pas si le score calculé est en fait important pour vous, mais si elle est, vous voudrez probablement faire quelque chose comme ceci:

users_scores = [] 
q = DBSession.query(Score.user_id, func.sum(Score.score_amount).label('total_score')).group_by(Score.user_id).filter(Score.created > somedate).order_by('total_score DESC')[:10] 
for user_id, total_score in q: 
    user = DBSession.query(User) 
    users_scores.append((user, total_score)) 

Cela se traduira par 11 requêtes en cours d'exécution, cependant. Il est possible de tout faire en une seule requête, mais en raison de diverses limitations dans SQLAlchemy, il va probablement créer une requête ou sous-requête à plusieurs jointures très moche (en fonction du moteur) et elle ne sera pas très performante.

Si vous prévoyez de faire quelque chose comme ça souvent et que vous avez une grande quantité de scores, pensez à dénormaliser le score actuel sur la table des utilisateurs. Il est plus de travail à l 'entretien, mais se traduira par une seule requête non comme se joindre à:

DBSession.query(User).order_by(User.computed_score.desc()) 

espoir qui aide.

+1

yikes. Je ne connais pas de telles limitations. – zzzeek