2010-01-06 19 views
12

Utilizando SQLAlchemy, tengo una relación uno a muchos con dos tablas: usuarios y puntajes. Intento consultar a los 10 mejores usuarios ordenados por su puntaje agregado durante los últimos X días.Consulta de filtro SQLAlchemy por objeto relacionado

users: 
    id 
    user_name 
    score 

scores: 
    user 
    score_amount 
    created 

Mi consulta actual es:

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

Sé que esto claramente no es correcta, es sólo mi mejor conjetura. Sin embargo, después de mirar la documentación y buscar en Google, no puedo encontrar una respuesta.

EDIT: Tal vez que sería útil que he esbozado lo que la consulta MySQL se vería así:

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

el error en el uso de eagerload() en conjunción con criterio en contra de su unirse se explica en este Entrada de preguntas frecuentes: http: //www.sqlalchemy.org/trac/wiki/FAQ # ImusinglazyFalsetocreateaJOINOUTERJOINandSQLAlchemyis noconstruyelaquedacuandoIntentaaadherirseDONDE ESTÁDELDERECLIMITETc.queseguierelaOUTERJOIN – zzzeek

Respuesta

14

La forma fila unidos a una sola, con un group_by añadido en para todas las columnas de usuario aunque MySQL le permiten agrupar en un solo la columna "ID" si lo desea:

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 si lo que desea los usuarios en el resultado:

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

los dos anteriores tienen una ineficiencia en el que está agrupando en todas las columnas de "usuario" (o se están usando "grupo de sólo unas pocas columnas" de MySQL cosa, que es solo MySQL). Para minimizar esto, el enfoque subconsulta:

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 ejemplo del escenario idéntico está en el tutorial ORM en: http://docs.sqlalchemy.org/en/latest/orm/tutorial.html#selecting-entities-from-subqueries

+0

Hola, gracias por la respuesta. Esto funciona bien y la documentación ayuda mucho. ¿Cómo obtendría acceso al puntaje_increase para un usuario? Tomemos como ejemplo que la consulta se asigna a la variable top_users y yo recorro cada usuario. user.score_increase no funciona, ni user.UserScore.score_increase. – Marc

+0

usando la tercera consulta, si itera a través de sess.query (Usuario, subq.c.score_increase), obtendrá tuplas de (Usuario, score_increase) – zzzeek

+0

hmm Me falta algo aquí. En caso de que importe, estoy usando Turbogears 2 y estoy asignando el resultado de la tercera consulta a una variable top_users que está disponible en mis plantillas. Luego hago un bucle para el usuario en top_users: print user.user_name + '' + user.score_increase - básicamente quiero mostrar el monto que la puntuación de los usuarios ha aumentado en los últimos x días. No entiendo cómo acceder a los datos unidos dentro de la tupla top_users. – Marc

0

Supongo que la columna (no la relación) que está utilizando para la unión se llama Score.user_id, así que cámbiela si no es el caso.

Usted tendrá que hacer algo como esto:

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] 

Sin embargo, esto se traducirá en tuplas de (user_id, total_score). No estoy seguro de si la puntuación computarizada es realmente importante para ti, pero si lo es, es probable que desee hacer algo como esto:

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

Esto dará lugar a 11 consultas en ejecución, sin embargo. Es posible hacerlo todo en una sola consulta, pero debido a varias limitaciones en SQLAlchemy, es probable que cree una consulta o subconsulta de combinación múltiple muy fea (en función del motor) y no será muy eficaz.

Si planea hacer algo como esto a menudo y tiene una gran cantidad de puntajes, considere desnormalizar el puntaje actual en la tabla de usuarios. Es más trabajo para el mantenimiento, pero resultará en una sola consulta no unirse como:

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

Espero que ayude.

+1

yikes. no conozco esas limitaciones. – zzzeek

Cuestiones relacionadas