使用数据库联结查询所关注用户的文章
程序首页目前按时间降序显示数据库中的所有文章,现在我们已经完成了关注功能,如果让用户选择只查看所关注用户发布的博客文章就更好了
若想显示所关注用户发布的所有文章,第一步显然先要获取这些用户,然后获取各用户的文章,再按一定顺序排列,写入单独列表,可是这种方式的伸缩性不好,随着数据库不断变大,生成这个列表的工作量也不断增长,而且分页等操作也无法高效率完成,获取博客文章的高效方式是只用一次查询
完成这个操作的数据库操作称为联结,联结操作用到两个或更多的数据表,在其中查找满足指定条件的记录组合,再把记录组合插入一个临时表中,这个临时表就是联结查询的结果,理解联结查询的最好方式是实例讲解
下表是一个users表实例,表中有3个用户
下表是对应的posts表,表中有几篇博客文章
最后,下面follows表显示了谁关注谁,从这个表中你可以看出,john关注了david,susan关注了john,但david谁也没关注
若想获得susan所关注用户发布的文章,就要合并posts
表和follows
表,首先过滤follows表,只留下关注者为susan的记录,即上表中的最后两行,然后过滤posts表,留下author_id
和过滤后的follows表中followed_id
相等的记录,把两次过滤结果合并,组成临时联结表,这就能高效查询susan所关注用户的文章列表,下表是联结操作的结果,表中用来执行链接操作的列用*
标记:
id | author_id* | body | followed_id | followed_id* |
---|---|---|---|---|
2 | 1 | john的博客文章 | 2 | 1 |
3 | 3 | david的博客文章 | 2 | 3 |
4 | 1 | john的第二篇博客文章 | 2 | 1 |
这个表中包含的博客文章都是用户susan所关注用户发布的,使用Flask-SQLAlchemy执行这个联结操作的查询相当复杂:
return db.session.query(Post).select_from(Follow).\
filter_by(follower_id=self.id).\
join(Post, Follow.followed_id == Post.author_id)
在此之前见到的查询都是从所查询模型的query属性开始的,这种查询不能在这里使用,因为查询要返回posts记录,所以首先要做的操作是在follow表上执行过滤器,因此,这里使用了一种更基础的查询方式,为了完全理解上述查询,下面分别说明各部分:
- db.session.query(Post):指明这个查询表要返回(Post)对象
- select_from(Follow)的意思是这个查询从Follow模型开始
- filter_by(follower_id=self.id)使用关注用户过滤follows表
- john(Post, Follow.followed_id == Post.author_id)联结filter_by()得到的结果和Post对象
调换过滤器和联结的顺序可以简化这个查询:
return Post.query.john(Follow, Follow.followed_id == Post.author_id)\
.filter(Follow.follower_id == self.id)
如果首先执行联结操作,那么这个查询就可以从Post.query
开始,此时唯一需要使用的两个过滤器是john()
和filter()
,但这两种查询是一样的吗?先执行联结操作再过滤看起来工作量会更大一些,但实际上这两种查询是等效的,SQLAlchemy首先收集所有的过滤器,然后再以最高效的方式生成查询,这两种查询生成的原生SQL指令是一样的,我们要把后一种查询写入Post模型,如下:
class User(db.Model):
#...
@property
def followed_posts(self):
return Post.query.join(Follow, Follow.followed_id == Post.author_id)\
.filter(Follow.follower_id == self.id)
followed_posts()
方法定义为属性,因此调用时无需加(),如此一来所有关系的句法都一样了