查看已关注用户的动态
在数据库中支持粉丝机制的工作几近尾声,但是我却遗漏了一项重要的功能。应用主页中需要展示已登录用户关注的其他所有用户的动态,我需要用数据库查询来返回这些用户动态。
最显而易见的方案是先执行一个查询以返回已关注用户的列表,如你所知,可以使用 user.followed.all()
语句。然后对每个已关注的用户执行一个查询来返回他们的用户动态。最后将所有用户的动态按照日期时间倒序合并到一个列表中。听起来不错?其实不然。
这种方法有几个问题。 如果一个用户关注了一千人,会发生什么? 我需要执行一千个数据库查询来收集所有的用户动态。 然后我需要合并和排序内存中的一千个列表。 作为第二个问题,考虑到应用主页最终将实现 分页 ,所以它不会显示所有可用的用户动态,只能是前几个,并显示一个链接来提供感兴趣的用户查看更多动态。 如果我要按它们的日期排序来显示动态,我怎么能知道哪些用户动态才是所有用户中最新的呢?除非我首先得到了所有的用户动态并对其进行排序。 这实际上是一个糟糕的解决方案,不能很好地应对规模化。
用户动态的合并和排序操作是无法避免的,但是在应用中执行会导致效率十分低下, 而这种工作是关系数据库擅长的。 我可以使用数据库的索引,命令它以更有效的方式执行查询和排序。 所以我真正想要提供的方案是,定义我想要得到的信息来执行一个数据库查询,然后让数据库找出如何以最有效的方式来提取这些信息。
看看下面的这个查询:
class User(db.Model):
#...
def followed_posts(self):
return Post.query.join(
followers, (followers.c.followed_id == Post.user_id)).filter(
followers.c.follower_id == self.id).order_by(
Post.timestamp.desc())
这是迄今为止我在这个应用中使用的最复杂的查询。 我将尝试一步一步地解读这个查询。 如果你看一下这个查询的结构,你会注意到有三个主要部分,分别是 join()
、 filter()
和 order_by()
,他们都是 SQLAlchemy 查询对象的方法:
Post.query.join(...).filter(...).order_by(...)
联合查询
要理解 join 操作的功能,我们来看一个例子。 假设我有一个包含以下内容的 User
表:
id | username |
---|---|
1 | john |
2 | susan |
3 | mary |
4 | david |
为了简单起见,我只会保留用户模型的 id
和 username
字段以便进行查询,其他的都略去。
假设 followers
关系表中数据表达的是用户 john
关注用户 susan
和 david
,用户 susan
关注用户 mary
,用户 mary
关注用户 david
。这些的数据如下表所示:
follower_id | followed_id |
---|---|
1 | 2 |
1 | 4 |
2 | 3 |
3 | 4 |
最后,用户动态表中包含了每个用户的一条动态:
id | text | user_id |
---|---|---|
1 | post from susan | 2 |
2 | post from mary | 3 |
3 | post from david | 4 |
4 | post from john | 1 |
这张表也省略了一些不属于这个讨论范围的字段。
这是我为该查询再次设计的 join()
调用:
Post.query.join(followers, (followers.c.followed_id == Post.user_id))
我在用户动态表上调用 join 操作。 第一个参数是 followers 关联表,第二个参数是 join 条件 。 我的这个调用表达的含义是我希望数据库创建一个临时表,它将用户动态表和关注者表中的数据结合在一起。 数据将根据参数传递的条件进行合并。
我使用的条件表示了 followers 关系表的 followed_id
字段必须等于用户动态表的 user_id
字段。 要执行此合并,数据库将从用户动态表(join 的左侧)获取每条记录,并追加 followers
关系表(join 的右侧)中的匹配条件的所有记录。 如果 followers
关系表中有多个记录符合条件,那么用户动态数据行将重复出现。 如果对于一个给定的用户动态,followers 关系表中却没有匹配,那么该用户动态的记录不会出现在 join 操作的结果中。
利用我上面定义的示例数据,执行 join 操作的结果如下:
id | text | user_id | follower_id | followed_id |
---|---|---|---|---|
1 | post from susan | 2 | 1 | 2 |
2 | post from mary | 3 | 2 | 3 |
3 | post from david | 4 | 1 | 4 |
3 | post from david | 4 | 3 | 4 |
注意 user_id
和 followed_id
列在所有数据行中都是相等的,因为这是 join 条件。 来自用户 john
的用户动态不会出现在临时表中,因为被关注列表中没有包含 john
用户,换句话说,没有任何人关注 john。 而来自 david
的用户动态出现了两次,因为该用户有两个粉丝。
虽然创建了这个 join 操作,但却没有得到想要的结果。请继续看下去,因为这只是更大的查询的一部分。
过滤
Join 操作给了我一个所有被关注用户的用户动态的列表,远超出我想要的那部分数据。 我只对这个列表的一个子集感兴趣——某个用户关注的用户们的动态,所以我需要用 filter()
来剔除所有我不需要的数据。
这是过滤部分的查询语句:
filter(followers.c.follower_id == self.id)
该查询是 User
类的一个方法, self.id
表达式是指我感兴趣的用户的 ID。 filter()
挑选临时表中 follower_id
列等于这个 ID 的行,换句话说,我只保留 follower(粉丝) 是该用户的数据。
假如我现在对 id
为1的用户 john
能看到的用户动态感兴趣,这是从临时表过滤后的结果:
id | text | user_id | follower_id | followed_id |
---|---|---|---|---|
1 | post from susan | 2 | 1 | 2 |
3 | post from david | 4 | 1 | 4 |
这正是我想要的结果!
请记住,查询是从 Post
类中发出的,所以尽管我曾经得到了由数据库创建的一个临时表来作为查询的一部分,但结果将是包含在此临时表中的用户动态, 而不会存在由于执行 join 操作添加的其他列。
排序
查询流程的最后一步是对结果进行排序。这部分的查询语句如下:
order_by(Post.timestamp.desc())
在这里,我要说的是,我希望使用用户动态产生的时间戳按降序排列结果列表。排序之后,第一个结果将是最新的用户动态。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论