美文网首页
Flask - SQLAlchemy - 自关联联合查询

Flask - SQLAlchemy - 自关联联合查询

作者: SingleDiego | 来源:发表于2019-01-09 16:41 被阅读59次

    我们继续上一篇的用户模型,在 “关注者/粉丝” 模型的基础上加上用户动态(Post),更接近于微博的结构。

    完整代码如下:

    from flask import Flask
    from flask_sqlalchemy import SQLAlchemy
    from flask_migrate import Migrate
    
    
    app = Flask(__name__)
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///demo.db'
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
    
    db = SQLAlchemy(app)
    migrate = Migrate(app, db)
    
    
    followers = db.Table('followers',
        db.Column('follower_id', db.Integer, db.ForeignKey('user.id')),
        db.Column('followed_id', db.Integer, db.ForeignKey('user.id'))
    )
    
    
    class User(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(50), nullable=False)
        followed = db.relationship(
            # User 表示关系当中的右侧实体(将左侧实体看成是上级类)
            # 由于这是自引用关系,所以两侧都是同一个实体。
            'User', 
    
            # secondary 指定了用于该关系的关联表
            # 就是使用我在上面定义的 followers
            secondary=followers,
    
            # primaryjoin 指明了右侧对象关联到左侧实体(关注者)的条件 
            # 也就是根据左侧实体查找出对应的右侧对象
            # 执行 user.followed 时候就是这样的查找
            primaryjoin=(followers.c.follower_id == id),
    
            # secondaryjoin 指明了左侧对象关联到右侧实体(被关注者)的条件 
            # 也就是根据右侧实体找出左侧对象
            # 执行 user.followers 时候就是这样的查找
            secondaryjoin=(followers.c.followed_id == id),
    
            # backref 定义了右侧实体如何访问该关系
            # 也就是根据右侧实体查找对应的左侧对象
            # 在左侧,关系被命名为 followed
            # 在右侧使用 followers 来表示所有左侧用户的列表,即粉丝列表
            backref=db.backref('followers', lazy='dynamic'), 
            lazy='dynamic'
            )
    
        posts = db.relationship('Post', backref='author', lazy='dynamic')
    
        def is_following(self, user):
            """
            检查是否已关注 user
            """
            return self.followed.filter(
                followers.c.followed_id == user.id
                ).count() > 0
    
        def follow(self, user):
            if not self.is_following(user):
                self.followed.append(user)
    
        def unfollow(self, user):
            if self.is_following(user):
                self.followed.remove(user)
    
        def followed_posts(self):
            followed = Post.query.join(
                followers, (followers.c.followed_id == Post.user_id)).filter(
                    followers.c.follower_id == self.id)
            own = Post.query.filter_by(user_id=self.id)
            return followed.union(own)
    
    
    class Post(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        body = db.Column(db.String(140))
        user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    
        def __repr__(self):
            return '<Post {}>'.format(self.body)
    
    
    
    @app.shell_context_processor
    def make_shell_context():
        return {
        'db': db, 
        'User': User, 
        'Post': Post,
        'followers': followers,
        }
    

    数据库示意图如下:

    数据库示意

    开始我们实验前现为数据表添加一点数据。

    followers 表有如下数据:

    follower_id followed_id
    1 2
    1 3
    2 3
    2 4

    Post 表有如下数据:

    id body user_id
    1 post1 from user2 2
    2 post2 from user2 2
    3 post1 from user3 3
    4 post1 from user4 4
    5 post1 from user1 1

    现在我想查询 user1 关注的用户的动态,我们的思路先是从 followers 表查询到 user1 关注用户的 id,即 2 和 3;然后在 Post 表中找出 user_id 为 2 和 3 的记录。为了实现这一目的,我们需要把 Post 表和 followers 表联合起来。

    我们使用 join() 关键字建立联合查询:

    Post.query.join(followers, (followers.c.followed_id == Post.user_id))
    

    第一个参数是 followers 关联表,第二个参数是 join 的条件。

    使用 join 后数据库创建一个临时表,它将用 Post 表和 followers 表中的数据结合在一起,数据将根据参数传递的条件进行合并。

    联合后的临时表类似这样:

    id body user_id follower_id followed_id
    1 post1 from user2 2 1 2
    2 post2 from user2 2 1 2
    3 post1 from user3 3 1 3
    3 post1 from user3 3 2 3
    4 post1 from user4 4 2 4

    注意 user_idfollowed_id 列在所有数据行中都是相等的,因为这是
    join 的条件。

    user3 的 Post 出现了两次,因为他被 2 个人关注了。

    有了联合表,现在用 filter 关键字过滤搜索条件:

    filter(followers.c.follower_id == self.id)
    

    现在要查询 user1 关注者的 post,使用 followers.c.follower_id == 1 查询条件就能容易得出。

    相关文章

      网友评论

          本文标题:Flask - SQLAlchemy - 自关联联合查询

          本文链接:https://www.haomeiwen.com/subject/zdpwrqtx.html