Запрос фильтра SQLAlchemy по связанному объекту - PullRequest
16 голосов
/ 06 января 2010

Используя SQLAlchemy , я имею отношение один ко многим с двумя таблицами - пользователями и оценками.Я пытаюсь запросить 10 лучших пользователей, отсортированных по их совокупному баллу за последние X дней.

users:  
  id  
  user_name  
  score  

scores:  
  user   
  score_amount  
  created  

Мой текущий запрос:

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

Я знаю, что это явноне правильно, это просто моя лучшая догадка.Однако, после просмотра документации и поиска в Google, я не могу найти ответ.

РЕДАКТИРОВАТЬ: Возможно, было бы полезно, если бы я набросал, как будет выглядеть запрос MySQL :

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

Ответы [ 3 ]

19 голосов
/ 06 января 2010

Способ с одиночной строкой, с добавлением group_by для всех пользовательских столбцов, хотя MySQL позволит вам группировать только по столбцу "id", если вы выберете:

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

Или, если вы просто хотите, чтобы пользователи в результате:

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

Вышеупомянутые два имеют неэффективность в том смысле, что вы группируете по всем столбцам «пользователя» (или вы используете MySQL «группа по нескольким столбцам», то есть только в MySQL). Чтобы минимизировать это, подход подзапроса:

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)

Пример идентичного сценария приведен в руководстве ORM по адресу: http://docs.sqlalchemy.org/en/latest/orm/tutorial.html#selecting-entities-from-subqueries

1 голос
/ 06 января 2010

Вам нужно будет использовать подзапрос, чтобы вычислить совокупную оценку для каждого пользователя. Подзапросы описаны здесь: http://www.sqlalchemy.org/docs/05/ormtutorial.html?highlight=subquery#using-subqueries

0 голосов
/ 06 января 2010

Я предполагаю, что столбец (не отношение), который вы используете для объединения, называется Score.user_id, поэтому измените его, если это не так.

Вам нужно будет сделать что-то вроде этого:

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]

Однако это приведет к кортежу (user_id, total_score). Я не уверен, что вычисленная оценка действительно важна для вас, но если это так, вы, вероятно, захотите сделать что-то вроде этого:

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

Однако это приведет к выполнению 11 запросов. Все это можно сделать одним запросом, но из-за различных ограничений в SQLAlchemy он, вероятно, создаст очень уродливый запрос или подзапрос с несколькими объединениями (зависит от механизма) и не будет очень производительным.

Если вы планируете делать что-то подобное часто и у вас большое количество баллов, подумайте о денормализации текущего балла в таблицу пользователей. Это больше работы для поддержки, но приведет к одному не присоединяющемуся запросу, как:

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

Надеюсь, это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...