web-dev-qa-db-fra.com

SQLAlchemy - sous-requête dans une clause WHERE

J'ai récemment commencé à utiliser SQLAlchemy et j'ai toujours du mal à comprendre certains des concepts.

Résumée aux éléments essentiels, j'ai deux tableaux comme celui-ci (c'est via Flask-SQLAlchemy):

class User(db.Model):
    __table= 'users'
    user_id = db.Column(db.Integer, primary_key=True)

class Posts(db.Model):
    __table= 'posts'
    post_id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('users.user_id'))
    post_time = db.Column(db.DateTime)

    user = db.relationship('User', backref='posts')

Comment pourrais-je procéder pour interroger une liste d'utilisateurs et leur plus récent message (à l'exclusion des utilisateurs sans message). Si j'utilisais SQL, je ferais:

SELECT [whatever]
FROM posts AS p
    LEFT JOIN users AS u ON u.user_id = p.user_id
WHERE p.post_time = (SELECT MAX(post_time) FROM posts WHERE user_id = u.user_id)

Je connais donc exactement le SQL "souhaité" pour obtenir l'effet que je veux, mais je ne sais pas comment l'exprimer "correctement" dans SQLAlchemy.

Edit: au cas où c'est important, je suis sur SQLAlchemy 0.6.6.

47
Chad Birch

Cela devrait fonctionner (SQL différent, même résultat):

t = Session.query(
    Posts.user_id,
    func.max(Posts.post_time).label('max_post_time'),
).group_by(Posts.user_id).subquery('t')

query = Session.query(User, Posts).filter(and_(
    User.user_id == Posts.user_id,
    User.user_id == t.c.user_id,
    Posts.post_time == t.c.max_post_time,
))

for user, post in query:
    print user.user_id, post.post_id

Où c signifie "colonnes"

52
sayap

la réponse précédente fonctionne, mais le sql exact que vous avez demandé est également écrit comme la déclaration réelle:

print s.query(User, Posts).\
    outerjoin(Posts.user).\
    filter(Posts.post_time==\
        s.query(
            func.max(Posts.post_time)
        ).
        filter(Posts.user_id==User.user_id).
        correlate(User).
        as_scalar()
    )

Je suppose que le "concept" qui n'est pas nécessairement apparent est que as_scalar () est actuellement nécessaire pour établir une sous-requête en tant que "scalaire" (il devrait probablement supposer cela à partir du contexte contre ==).

Edit: Confirmé, c'est un comportement de buggy, ticket # 2190 terminé. Dans l'astuce ou la version 0.7.2 actuelle, as_scalar () est appelée automatiquement et la requête ci-dessus peut être:

print s.query(User, Posts).\
    outerjoin(Posts.user).\
    filter(Posts.post_time==\
        s.query(
            func.max(Posts.post_time)
        ).
        filter(Posts.user_id==User.user_id).
        correlate(User)
    )
61
zzzeek