【问题标题】:SQLAlchemy select with all many to many relationship objects as listSQLAlchemy 选择所有多对多关系对象作为列表
【发布时间】:2023-03-23 23:45:01
【问题描述】:

鉴于这种多对多关系:

tagmap = db.Table('tagmap', db.Model.metadata,
    db.Column('post_id', db.Integer, db.ForeignKey('posts.id'),
    db.Column('tag_id', db.Integer, db.ForeignKey('tags.id'),)

class Post(db.Model):
    __tablename__ = 'posts'
    id    = db.Column(db.Integer, primary_key=True)
    text  = db.Column(db.Text)
    tags  = db.relationship('Tag', secondary=tagmap, backref='posts')

class Tag(db.Model):
    __tablename__ = 'tags'
    id    = db.Column(db.Integer, primary_key=True)
    name  = db.Column(db.String)

您将如何构造一个查询来选择所有帖子,包括关联标签对象的可迭代列表?

想要的查询结果结构:

[ <Post 1, "Blah",      [<Tag "Goober">, <Tag "Mexico">]>,
  <Post 2, "Daft Punk", [<Tag "French">]>,
  <Post 3, "Slurpee",   [<Tag "Diabetes">, <Tag "Tasty">, <Tag "Sugary">]>,
  <Post 4, "Lasers",    []> ]

Jinja 模板中的期望结果用法示例:

{% for post in posts %}
    {{ post.text }}
    <hr>
    {% for tag in post.tags %}
        {{ tag.name }}, 
    {% endfor %}
{% endfor %}

编辑

当尝试扩展查询时,标签停止被包含。请参阅下面的失败查询:

Post.query.join(User)\
    .options(db.joinedload(Post.tags))\
    .order_by(Post.create_date.desc()).limit(5)\
    .values(User.name.label('author'),
            Post.create_date,
            Post.text,)

编辑 2

所以这个查询很好用:

posts = db.session.query(Post, User.name.label('author')).join(User)\
            .options(db.joinedload(Post.tags))\
            .order_by(Post.created.desc()).limit(5)

但如果我想对帖子中的列有选择性,我会选择:

# I only need the post text, nothing else
posts = db.session.query(Post.text, User.name.label('author')).join(User)\
            .options(db.joinedload(Post.tags))\
            .order_by(Post.created.desc()).limit(5)

它开始迅速分崩离析:

ArgumentError: Query has only expression-based entities - can't find property named 'tags'.

所以我尝试将标签添加回来:

# Yes, I know this is wrong
posts = db.session.query(Post.text, Post.tags, User.name.label('author'))\
            .join(User)\               
            .options(db.joinedload(Post.tags))\
            .order_by(Post.created.desc()).limit(5)

仍然失败。

因此,如果您只想要父对象(Post)中的特定列,那么您在表达式中的哪个位置说“我仍然想要标签”?

现在它并不那么重要,但我认为知道它会很有用。甚至知道这是不可能的。

【问题讨论】:

    标签: python sqlalchemy flask flask-sqlalchemy


    【解决方案1】:

    我假设您想使用急切加载来建立关系。您可以通过在与'joined''subquery' 的关系中设置lazy 关键字参数来实现这一点:

    class Post(db.Model):
        # ...
        tags = db.relationship('Tag', secondary=tagmap, backref='posts',
                               lazy='joined')    # Or lazy='subquery'
    

    或者您可以根据查询设置它:

    q = Post.query.options(db.joinedload(Post.tags)).all()
    # Or db.subqueryload(Post.tags)
    

    请参阅 ORM 教程的Eager Loading 部分和Relationship Loading Techniques 了解更多详细信息。

    【讨论】:

    • 如何将joinedload 语句包含在更复杂的查询中,例如使用.values() 的查询? (我用示例查询更新了我的问题。)
    • 好吧,首先你没有在你的values()参数中使用Post.tags,虽然我不认为它适用于关系,至少它不会很快用我的其他代码测试。如果要从多个表中查询值,在这种情况下使用db.session.query(...) 会更好吗?
    • 我的意思是,Post.querydb.session.query(Post) 的缩写形式。由于您要查询的对象不止Post,因此您应该使用db.session.query 表单。
    • 我设法让我的查询正常工作。但是,我想知道的是,当您想选择父对象的各个列时,joinedload 对象将不再被包含在内。请参阅上面的更新问题。
    • 嗯,我试图想出一些可行的方法,但失败了。我不认为关系可以在这样的查询中使用,尽管我可能错了。如我所见,您只能查询映射对象(并使用预先加载来填充其关系)或单个列和表达式。也许在 SA 中更有知识的人会站出来证明我错了(或证实我的观察)。
    【解决方案2】:

    SQLAlchemy 会在您访问 post.tags 属性时自动加载它,因此您只需要加载您想要的所有帖子。

    db.Column('tag_id', db.Integer, db.ForeignKey('quotes.id'),)

    quotes.id 是不是打错字了?

    【讨论】:

    • 是的,这是一个错字,我已经更正了外键,以便它们现在有意义。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-12-22
    • 2015-03-16
    • 1970-01-01
    • 2019-10-16
    • 1970-01-01
    • 2012-06-21
    相关资源
    最近更新 更多