【问题标题】:Flask-SQLAlchemy How to filter two different models based on one to many relationship?Flask-SQLAlchemy 如何根据一对多关系过滤两个不同的模型?
【发布时间】:2021-04-03 04:37:01
【问题描述】:

很抱歉,关于这个问题有很多问题,但我就是做不到。我已经尝试了两天。

我有三个模型:

class Post(db.Model):
    ### other columns
    oylar = db.relationship('Vote',backref='post_oylar',lazy='dynamic', cascade="all, delete")

class Plan(db.Model):
    ### other columns
    oylar = db.relationship('Vote',backref='oylar',lazy='dynamic', cascade="all, delete")

class Vote(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
    post_id = db.Column(db.Integer, db.ForeignKey("post.id"))
    plani_id = db.Column(db.Integer, db.ForeignKey("plan.id"))

我需要根据Vote user_id查询Plan和Post。

假设我有一个 ID 为 1 的 A 用户。 基本上,我想看看这个用户投票给了哪个计划和帖子。

我已经尝试了很多查询,以至于我什至想不出我应该在这里写哪一个。但为了更清楚,让我向您展示我在这个例子中的意思:

post = db.session.query(Post).join(Vote).filter(Vote.user_id == 1)
plan = db.session.query(Plan).join(Vote).filter(Vote.user_id == 1)
final_query = #there should be something to merge these two queries.

这对我来说是一场噩梦。不懂SQL语法,刚开始学习SQL-Alchemy,请不要拍。

提前感谢您的宝贵时间。

更新:最适合我的解决方案是使用单表继承。它让我的生活更轻松。

【问题讨论】:

    标签: python sql database flask sqlalchemy


    【解决方案1】:

    您使用association object 的多对多关系。
    我认为模型还可以,但我对它们进行了一些重组。

    class Vote(db.Model):
        __tablename__ = 'votes'
    
        id = db.Column(db.Integer, primary_key=True)
    
        # Foreign Keys
        plan_id = db.Column(db.Integer, db.ForeignKey('plans.id'))
        post_id = db.Column(db.Integer, db.ForeignKey('posts.id'))
        user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
    
        # Relationships
        # If a user, post or plan is deleted, referencing votes are also removed.
        # The associated plans and posts are loaded with the vote using a JOIN statement.
        plan = db.relationship('Plan',
            backref=db.backref('votes', cascade='all, delete-orphan'),
            lazy='joined')
        post = db.relationship('Post',
            backref=db.backref('votes', cascade='all, delete-orphan'),
            lazy='joined')
        user = db.relationship('User',
            backref=db.backref('votes', cascade='all, delete-orphan'))
    
        def __repr__(self):
            return f'Vote(plan_id={self.plan_id}, post_id={self.post_id}, user_id={self.user_id})'
    
    class Plan(db.Model):
        __tablename__ = 'plans'
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(255), index=True, nullable=False)
    
        def __repr__(self):
            return f'Plan(name={self.name})'
    
    class Post(db.Model):
        __tablename__ = 'posts'
        id = db.Column(db.Integer, primary_key=True)
        title = db.Column(db.String(255), index=True, nullable=False)
    
        def __repr__(self):
            return f'Post(title={self.title})'
    
    class User(db.Model):
        __tablename__ = 'users'
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(64), index=True, nullable=False)
    
        # All plans and posts that have been voted for can be reached via jointable.
        # CAUTION, objects can be added to the lists, but because of the viewonly flag
        # they are not transferred to the database during a commit.
        # An inconsistent state is therefore possible.
        #
        # _voted_plans = db.relationship(
        #     'Plan',
        #     secondary='votes',
        #     backref=db.backref('users_voted', viewonly=True),
        #     viewonly=True
        # )
        #
        # _voted_posts = db.relationship(
        #     'Post',
        #     secondary='votes',
        #     backref=db.backref('users_voted', viewonly=True),
        #     viewonly=True
        # )
    
        def __repr__(self):
            return f'User(name={self.name})'
    

    一方面,您可以使用 ORM 方法使用虚拟关系来 列出所有相关的模型。在这种情况下,您的表投票充当可连接的 并且可以直接查询 Post 和 Plan 类的关联对象 通过关系。

    plan_post_pairs = [(vote.plan, vote.post) for vote in Vote.query.filter_by(user_id=user_id).all()]
    

    作为替代方案,您也可以编写自己的请求。 作为一个例子,我会给你一个 SQL SELECT 语句和一个 JOIN 语句。 我还要求提供投票标识符以列出用户对相同计划-帖子组合的重复投票。

    # SELECT stmt
    items = db.session.query(       # SELECT ... FROM ...
        Vote.id, Plan, Post
    ).filter(                       # WHERE ...
        Vote.plan_id == Plan.id,    # ... AND
        Vote.post_id == Post.id,    # ... AND
        Vote.user_id == user_id     # ...
    ).all()
    
    # JOIN stmt
    items = db.session.query(Vote.id, Plan, Post)\      # SELECT ...
        .select_from(Vote)\                             # FROM ...
        .outerjoin(Plan, Post)\                         # LEFT OUTER JOIN ... ON ...
        .filter(Vote.user_id == user_id)\               # WHERE ...
        .all()
    

    以下示例更高级一些,将来可能会对您有所帮助。请求用户的所有计划-发布组合,包括该用户对相应组合的投票数。

    subquery = db.session\
        .query(Vote.plan_id, Vote.post_id, db.func.count('*').label('count'))\
        .group_by(Vote.plan_id, Vote.post_id)\
        .filter(Vote.user_id == user_id)\
        .subquery()
    
    items = db.session\
        .query(Plan, Post, subquery.c.count)\
        .select_from(subquery)\
        .outerjoin(Plan, subquery.c.plan_id == Plan.id)\
        .outerjoin(Post, subquery.c.post_id == Post.id)\
        .all()
    

    【讨论】:

    • 综合解决方案!
    • @Kerem Nayman 我认为您应该更改您的模型投票以使您的工作更轻松。删除列 id 并使外键也成为主键。现在,您可以添加一个额外的整数列作为用户对相同计划-帖子组合的多次投票的计数器。
    • 你的意思是这样?那么我将如何查询这个模型呢? i.ibb.co/s967th6/Ekran-Al-nt-s.jpg
    • 我认为当您将模型拆分为多个子类型时,继承会有所帮助。在 Post and Plan 的情况下,这对我来说似乎不是一个好的选择。您也许可以通过 mixins 创建更清晰的结构。对我来说,这是目前不容忽视的。就分页而言,这也可以使用查询的偏移量和限制功能来实现。不过,我可以理解对 flask-sqlalchemy 实现的偏好。
    • @KeremNayman 抱歉,我已经解决了分页问题。你还有兴趣吗? post_plan_amount = Vote.query.filter_by(user_id = user_id).join(Post, Plan).with_entities(Post, Plan, Vote.amount).paginate(page, per_page)
    【解决方案2】:

    因为您正在创建与您的 Vote 模型的关系,我建议您也建立 VoteUser 之间的关系:

    # Modify your class to include the following line
    class Vote(db.Model):
        ...
        user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
        user = db.Relationship("User", backref="vote") #  <--- This assumes you have a `User` model
        ...
    

    现在这种关系已经建立,我将从user-vote 关系中访问这些子对象:

    # 1. Get your user
    my_user_id = 1
    my_user = db.session.query(Post).get(my_user_id)
    
    # Get the vote for this user --> this will return a list
    # Note: there may be many votes, I'll use the first index as a demonstrative example.
    my_votes = my_user.vote
    
    # If there aren't any votes
    if not my_votes:
        raise Exception(f'User id={my_user.id} has no votes')
    
    # 2. Get the first vote 
    my_vote = my_votes[0]
    
    # 3. Get the children from that vote
    # These are the keywords specified in the 'backref' parameter
    my_post = my_vote.post_oylar
    my_plan = my_vote.oylar
    
    # Finally, print them
    print('post:', my_post.__dict__)
    print('plan:', my_plan.__dict__)
    

    作为旁注,我建议您对如何在 ORM 模型中使用 best establish relationships 进行一些额外的研究——看起来这可能是造成混乱的根源。

    【讨论】:

    • 非常感谢您的时间和回复。然而,不幸的是,这不是我想要的。现在我可以访问帖子和计划的投票。我想要的是;我想查看用户投票的帖子和计划,而不是 Vote 对象本身。从理论上讲,应该是这样的:查询 = 查询用户投票的帖子 + 查询用户投票的计划。现在我可以通过两个不同的查询来实现这一点,我在原始问题中展示了它们。但我需要以某种方式合并它们,这就是问题所在。也许我的模型做错了,我愿意接受建议。
    猜你喜欢
    • 2016-08-23
    • 2021-10-22
    • 2020-11-13
    • 2020-01-02
    • 1970-01-01
    • 2021-08-14
    • 2016-06-15
    • 2021-08-31
    • 2013-05-29
    相关资源
    最近更新 更多