2010-01-06 27 views
12

Utilizzo di SQLAlchemy, ho una relazione uno a molti con due tabelle: utenti e punteggi. Sto cercando di interrogare i primi 10 utenti ordinati per il loro punteggio aggregato nell'ultimo periodo di X giorni.SQLAlchemy query di filtro per oggetto correlato

users: 
    id 
    user_name 
    score 

scores: 
    user 
    score_amount 
    created 

La mia domanda attuale è:

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

So che questo è chiaramente non è corretta, è solo la mia ipotesi migliore. Tuttavia, dopo aver consultato la documentazione e googling, non riesco a trovare una risposta.

EDIT: Forse sarebbe utile se ho abbozzato quello che la query MySQL sarà simile:

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'errore utilizzando eagerload() in combinazione con criterio contro il suo join viene spiegato in questo Voce delle domande frequenti: http: //www.sqlalchemy.org/trac/wiki/Domande frequenti # ImusinglazyFalsetocreateaJOINOUTERJOINeSQLAlchemy non è in grado di ricostruire l'interrogazione quando l'oggetto è in esecuzioneWHEREORDERBYLIMITetc.che si trova nell'interfacciaOUTERJOIN – zzzeek

risposta

14

Il modo fila singola incollati, con una group_by aggiunto per tutte le colonne di utenti anche se MySQL vi permetterà di gruppo sul solo la colonna "id" se si sceglie:

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") 

O se volete semplicemente gli utenti nel risultato:

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

È possibile che due hanno un'inefficienza che si sta raggruppamento su tutte le colonne di "utente" (o si sta utilizzando "gruppo solo su alcune colonne" di MySQL cosa, che è solo MySQL). Per ridurre al minimo questo, l'approccio subquery:

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 esempio dello scenario identico è nel tutorial ORM a: http://docs.sqlalchemy.org/en/latest/orm/tutorial.html#selecting-entities-from-subqueries

+0

Ciao grazie per la risposta. Funziona bene e la documentazione aiuta molto. Come potrei accedere a score_increase per un utente? Prendiamo ad esempio la query è assegnata alla variabile top_users e I loop attraverso ogni utente. user.score_increase non funziona, né user.UserScore.score_increase. – Marc

+0

utilizzando la terza query, se si scorre su sess.query (Utente, subq.c.score_increase), si otterranno tuple di (Utente, score_increase) – zzzeek

+0

hmm Mi manca qualcosa qui. Se è importante, sto utilizzando Turbogears 2 e sto assegnando il risultato della terza query a una variabile top_users che è disponibile nei miei modelli. Quindi faccio un loop - per utente in top_users: print user.user_name + '' + user.score_increase - in pratica voglio mostrare l'importo che il punteggio degli utenti ha aumentato il numero x di giorni passati. Non capisco come accedere ai dati uniti nella tupla top_users. – Marc

0

Sto assumendo che la colonna (non la relazione) che stai usando per il join sia chiamata Score.user_id, quindi cambialo se questo non è il caso.

Avrete bisogno di fare qualcosa di simile:

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] 

Tuttavia, questo si tradurrà in tuple di (user_id, total_score). Non sono sicuro se il punteggio calcolato è in realtà importante per voi, ma se lo è, probabilmente si vuole fare qualcosa di simile:

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)) 

Questo si tradurrà in 11 interrogazioni al database in corso di esecuzione, tuttavia. È possibile eseguire tutto in una singola query, ma a causa di varie limitazioni in SQLAlchemy, è probabile che crei una query o sottoquery multi-join molto brutta (a seconda del motore) e non sarà molto performante.

Se hai intenzione di fare qualcosa di simile spesso e hai una grande quantità di punteggi, considera denormalizzare il punteggio corrente sulla tabella utente. È più lavoro da mantenere, ma risulterà in una singola query non di join come:

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

Sperare che aiuti.

+1

yikes. non ho limitazioni di questo tipo. – zzzeek

Problemi correlati