网站建设门户书店网站建设策划书
news/
2025/9/26 6:44:33/
文章来源:
网站建设门户,书店网站建设策划书,网站模版 源码之家,如何给一个企业的网站做推广文章目录 十、 关系和连接10.1 模型定义10.1.1 创建测试数据 10.2 执行简单连接10.3 连接多个表10.4 从多个来源中选择10.4.1 更复杂的例子 10.5 子查询10.5.1 公用表表达式 10.6 同一模型的多个外键10.7 加入任意字段 十、 关系和连接
在本文档中#xff0c;我们将介绍 Peew… 文章目录 十、 关系和连接10.1 模型定义10.1.1 创建测试数据 10.2 执行简单连接10.3 连接多个表10.4 从多个来源中选择10.4.1 更复杂的例子 10.5 子查询10.5.1 公用表表达式 10.6 同一模型的多个外键10.7 加入任意字段 十、 关系和连接
在本文档中我们将介绍 Peewee 如何处理模型之间的关系。
10.1 模型定义
我们将在示例中使用以下模型定义
import datetime
from peewee import *db SqliteDatabase(:memory:)class BaseModel(Model):class Meta:database dbclass User(BaseModel):username TextField()class Tweet(BaseModel):content TextField()timestamp DateTimeField(defaultdatetime.datetime.now)user ForeignKeyField(User, backreftweets)class Favorite(BaseModel):user ForeignKeyField(User, backreffavorites)tweet ForeignKeyField(Tweet, backreffavorites)Peewee 用于ForeignKeyField定义模型之间的外键关系。每个外键字段都有一个隐含的反向引用它使用提供的属性作为预过滤Select查询 公开。backref
10.1.1 创建测试数据
为了跟随示例让我们用一些测试数据填充这个数据库
def populate_test_data():db.create_tables([User, Tweet, Favorite])data ((huey, (meow, hiss, purr)),(mickey, (woof, whine)),(zaizee, ()))for username, tweets in data:user User.create(usernameusername)for tweet in tweets:Tweet.create(useruser, contenttweet)# Populate a few favorites for our users, such that:favorite_data ((huey, [whine]),(mickey, [purr]),(zaizee, [meow, purr]))for username, favorites in favorite_data:user User.get(User.username username)for content in favorites:tweet Tweet.get(Tweet.content content)Favorite.create(useruser, tweettweet)这给了我们以下信息
UserTweetFavorited byhueymeowzaizeehueyhisshueypurrmickey, zaizeemickeywoofmickeywhinehuey
在以下示例中我们将执行一些查询。如果您不确定正在执行多少查询您可以添加以下代码它将所有查询记录到控制台
import logging
logger logging.getLogger(peewee)
logger.addHandler(logging.StreamHandler())
logger.setLevel(logging.DEBUG)笔记 在 SQLite 中默认情况下不启用外键。大多数事情包括 Peewee 外键 API都可以正常工作但 ON DELETE 行为将被忽略即使您on_delete在 ForeignKeyField. 结合默认 AutoField行为可以重用已删除的记录 ID这可能会导致细微的错误。为避免出现问题我建议您在使用 SQLite 时启用外键约束方法是 在实例化时设置.pragmas{‘foreign_keys’: 1}SqliteDatabase # Ensure foreign-key constraints are enforced.
db SqliteDatabase(my_app.db, pragmas{foreign_keys: 1})10.2 执行简单连接
作为学习如何使用 Peewee 执行连接的练习让我们编写一个查询以打印出“huey”的所有推文。为此我们将从 Tweet模型中选择并加入User模型然后我们可以在字段上进行过滤 User.username query Tweet.select().join(User).where(User.username huey)for tweet in query:
... print(tweet.content)
...
meow
hiss
purr笔记 我们不必明确指定连接谓词“ON”子句因为 Peewee 从模型中推断当我们从 Tweet 连接到用户时我们是Tweet.user在外键上连接的。 以下代码是等效的但更明确 query (Tweet.select().join(User, on(Tweet.user User.id)).where(User.username huey))如果我们已经有了User对“huey”对象的引用我们可以使用User.tweets反向引用来列出所有huey的推文 huey User.get(User.username huey)for tweet in huey.tweets:
... print(tweet.content)
...
meow
hiss
purr仔细看huey.tweets我们可以看到它只是一个简单的预过滤SELECT查询 huey.tweets
peewee.ModelSelect at 0x7f0483931fd0 huey.tweets.sql()
(SELECT t1.id, t1.content, t1.timestamp, t1.user_idFROM tweet AS t1 WHERE (t1.user_id ?), [1])10.3 连接多个表
让我们通过查询用户列表并获取他们创作的推文的数量被收藏来再次查看连接。这将需要我们加入两次从用户到推文以及从推文到收藏。我们将添加额外的要求即应包括尚未创建任何推文的用户以及推文未被收藏的用户。以 SQL 表示的查询将是
SELECT user.username, COUNT(favorite.id)
FROM user
LEFT OUTER JOIN tweet ON tweet.user_id user.id
LEFT OUTER JOIN favorite ON favorite.tweet_id tweet.id
GROUP BY user.username笔记 在上面的查询中两个连接都是 LEFT OUTER因为用户可能没有任何推文或者如果他们有推文它们可能都没有被收藏。 Peewee 有一个join context的概念这意味着每当我们调用该 join()方法时我们都会隐式地加入先前加入的模型或者如果这是第一次调用则我们正在从中选择模型。由于我们是直接加入的从用户到推文然后从推文到收藏我们可以简单地写
query (User.select(User.username, fn.COUNT(Favorite.id).alias(count)).join(Tweet, JOIN.LEFT_OUTER) # Joins user - tweet..join(Favorite, JOIN.LEFT_OUTER) # Joins tweet - favorite..group_by(User.username))迭代结果 for user in query:
... print(user.username, user.count)
...
huey 3
mickey 1
zaizee 0对于涉及多个连接和切换连接上下文的更复杂的示例让我们查找 Huey 的所有推文以及它们被收藏的次数。为此我们需要执行两次连接并且我们还将使用聚合函数来计算收藏次数。
下面是我们如何在 SQL 中编写此查询
SELECT tweet.content, COUNT(favorite.id)
FROM tweet
INNER JOIN user ON tweet.user_id user.id
LEFT OUTER JOIN favorite ON favorite.tweet_id tweet.id
WHERE user.username huey
GROUP BY tweet.content;笔记 我们使用从推文到收藏夹的 LEFT OUTER 连接因为推文可能没有任何收藏夹但我们仍然希望在结果集中显示它的内容以及零计数。 使用 Peewee生成的 Python 代码看起来非常类似于我们用 SQL 编写的代码
query (Tweet.select(Tweet.content, fn.COUNT(Favorite.id).alias(count)).join(User) # Join from tweet - user..switch(Tweet) # Move join context back to tweet..join(Favorite, JOIN.LEFT_OUTER) # Join from tweet - favorite..where(User.username huey).group_by(Tweet.content))请注意对switch() 的调用它指示 Peewee 将连接上下文设置回Tweet. 如果我们省略了对 switch 的显式调用Peewee 将使用User我们加入的最后一个模型作为连接上下文并使用 Favorite.user外键构造从用户到收藏夹的连接这会给我们带来不正确的结果。
如果我们想省略连接上下文切换我们可以改用该 join_from()方法。以下查询等效于前一个查询
query (Tweet.select(Tweet.content, fn.COUNT(Favorite.id).alias(count)).join_from(Tweet, User) # Join tweet - user..join_from(Tweet, Favorite, JOIN.LEFT_OUTER) # Join tweet - favorite..where(User.username huey).group_by(Tweet.content))我们可以遍历上述查询的结果以打印推文的内容和收藏次数 for tweet in query:
... print(%s favorited %d times % (tweet.content, tweet.count))
...
meow favorited 1 times
hiss favorited 0 times
purr favorited 2 times10.4 从多个来源中选择
如果我们希望列出数据库中的所有推文及其作者的用户名您可以尝试这样写 for tweet in Tweet.select():
... print(tweet.user.username, -, tweet.content)
...
huey - meow
huey - hiss
huey - purr
mickey - woof
mickey - whine上面的循环有一个大问题它为每条推文执行一个额外的查询来查找tweet.user外键。对于我们的小表性能损失并不明显但我们会发现延迟随着行数的增加而增加。
如果您熟悉 SQL您可能还记得可以从多个表中进行 SELECT从而允许我们在单个查询中获取推文内容和用户名
SELECT tweet.content, user.username
FROM tweet
INNER JOIN user ON tweet.user_id user.id;Peewee 使这很容易。事实上我们只需要稍微修改一下我们的查询。我们告诉 Peewee 我们希望选择Tweet.content该User.username字段然后我们包含从推文到用户的连接。为了更清楚地表明它正在做正确的事情我们可以要求 Peewee 将行作为字典返回。 for row in Tweet.select(Tweet.content, User.username).join(User).dicts():
... print(row)
...
{content: meow, username: huey}
{content: hiss, username: huey}
{content: purr, username: huey}
{content: woof, username: mickey}
{content: whine, username: mickey}现在我们将停止对“.dicts()”的调用并将行作为Tweet 对象返回。请注意Peewee 将username值分配给 tweet.user.username- NOT tweet.username因为从 tweet 到 user 有一个外键并且我们从两个模型中选择了字段所以 Peewee 将为我们重建模型图 for tweet in Tweet.select(Tweet.content, User.username).join(User):
... print(tweet.user.username, -, tweet.content)
...
huey - meow
huey - hiss
huey - purr
mickey - woof
mickey - whine如果我们愿意我们可以通过在方法中User指定 an 来控制 Peewee 在上述查询中放置连接实例的位置attrjoin() query Tweet.select(Tweet.content, User.username).join(User, attrauthor)for tweet in query:
... print(tweet.author.username, -, tweet.content)
...
huey - meow
huey - hiss
huey - purr
mickey - woof
mickey - whine相反如果我们只是希望我们选择的所有属性都是Tweet实例的属性我们可以objects()在查询末尾添加一个调用类似于我们调用的方式dicts() for tweet in query.objects():
... print(tweet.username, -, tweet.content)
...
huey - meow
(etc)10.4.1 更复杂的例子
作为一个更复杂的示例在此查询中我们将编写一个查询该查询选择所有收藏夹以及创建收藏夹的用户、收藏的推文以及该推文的作者。
在 SQL 中我们会写
SELECT owner.username, tweet.content, author.username AS author
FROM favorite
INNER JOIN user AS owner ON (favorite.user_id owner.id)
INNER JOIN tweet ON (favorite.tweet_id tweet.id)
INNER JOIN user AS author ON (tweet.user_id author.id);请注意我们从用户表中选择了两次——一次是在创建收藏夹的用户的上下文中另一次是作为推文的作者。
使用 Peewee我们使用Model.alias()别名模型类以便可以在单个查询中引用它两次
Owner User.alias()
query (Favorite.select(Favorite, Tweet.content, User.username, Owner.username).join(Owner) # Join favorite - user (owner of favorite)..switch(Favorite).join(Tweet) # Join favorite - tweet.join(User)) # Join tweet - user我们可以通过以下方式遍历结果并访问连接的值。请注意 Peewee 如何从我们选择的各种模型中解析字段并重建模型图 for fav in query:
... print(fav.user.username, liked, fav.tweet.content, by, fav.tweet.user.username)
...
huey liked whine by mickey
mickey liked purr by huey
zaizee liked meow by huey
zaizee liked purr by huey10.5 子查询
Peewee 允许您加入任何类似表的对象包括子查询或公用表表达式 (CTE)。为了演示加入子查询让我们查询所有用户及其最新推文。
这是SQL
SELECT tweet.*, user.*
FROM tweet
INNER JOIN (SELECT latest.user_id, MAX(latest.timestamp) AS max_tsFROM tweet AS latestGROUP BY latest.user_id) AS latest_query
ON ((tweet.user_id latest_query.user_id) AND (tweet.timestamp latest_query.max_ts))
INNER JOIN user ON (tweet.user_id user.id)我们将通过创建一个选择每个用户及其最新推文时间戳的子查询来做到这一点。然后我们可以在外部查询中查询推文表并从子查询中加入用户和时间戳组合。
# Define our subquery first. Well use an alias of the Tweet model, since
# we will be querying from the Tweet model directly in the outer query.
Latest Tweet.alias()
latest_query (Latest.select(Latest.user, fn.MAX(Latest.timestamp).alias(max_ts)).group_by(Latest.user).alias(latest_query))# Our join predicate will ensure that we match tweets based on their
# timestamp *and* user_id.
predicate ((Tweet.user latest_query.c.user_id) (Tweet.timestamp latest_query.c.max_ts))# We put it all together, querying from tweet and joining on the subquery
# using the above predicate.
query (Tweet.select(Tweet, User) # Select all columns from tweet and user..join(latest_query, onpredicate) # Join tweet - subquery..join_from(Tweet, User)) # Join from tweet - user.遍历查询我们可以看到每个用户及其最新的推文。 for tweet in query:
... print(tweet.user.username, -, tweet.content)
...
huey - purr
mickey - whine在我们用于在本节中创建查询的代码中有几件事您可能以前没有见过
我们曾经join_from()明确指定连接上下文。我们写了相当于 …join_from(Tweet, User).switch(Tweet).join(User)我们使用魔法属性引用了子查询中的列.c例如latest_query.c.max_ts. 该.c属性用于动态创建列引用。我们没有将单个字段传递给Tweet.select()而是传递了 TweetandUser模型。这是选择给定模型上所有字段的简写。
10.5.1 公用表表达式
在上一节中我们加入了子查询但我们也可以轻松地使用公共表表达式 (CTE)。我们将重复与之前相同的查询列出用户及其最新的推文但这次我们将使用 CTE 来完成。
这是SQL
WITH latest AS (SELECT user_id, MAX(timestamp) AS max_tsFROM tweetGROUP BY user_id)
SELECT tweet.*, user.*
FROM tweet
INNER JOIN latestON ((latest.user_id tweet.user_id) AND (latest.max_ts tweet.timestamp))
INNER JOIN userON (tweet.user_id user.id)这个例子看起来与前面带有子查询的例子非常相似
# Define our CTE first. Well use an alias of the Tweet model, since
# we will be querying from the Tweet model directly in the main query.
Latest Tweet.alias()
cte (Latest.select(Latest.user, fn.MAX(Latest.timestamp).alias(max_ts)).group_by(Latest.user).cte(latest))# Our join predicate will ensure that we match tweets based on their
# timestamp *and* user_id.
predicate ((Tweet.user cte.c.user_id) (Tweet.timestamp cte.c.max_ts))# We put it all together, querying from tweet and joining on the CTE
# using the above predicate.
query (Tweet.select(Tweet, User) # Select all columns from tweet and user..join(cte, onpredicate) # Join tweet - CTE..join_from(Tweet, User) # Join from tweet - user..with_cte(cte))我们可以遍历结果集其中包含每个用户的最新推文 for tweet in query:
... print(tweet.user.username, -, tweet.content)
...
huey - purr
mickey - whine笔记 有关使用 CTE 的更多信息包括有关编写递归 CTE 的信息请参阅“查询”文档的公用表表达式部分。 10.6 同一模型的多个外键
当同一模型有多个外键时最好明确指定要加入的字段。
回顾示例应用程序的模型考虑关系模型它用于表示一个用户何时关注另一个用户。这是模型定义
class Relationship(BaseModel):from_user ForeignKeyField(User, backrefrelationships)to_user ForeignKeyField(User, backrefrelated_to)class Meta:indexes (# Specify a unique multi-column index on from/to-user.((from_user, to_user), True),)由于User有两个外键我们应该始终指定我们在连接中使用哪个字段。
例如要确定我关注哪些用户我会写
(User.select().join(Relationship, onRelationship.to_user).where(Relationship.from_user charlie))另一方面如果我想确定哪些用户在关注我我会加入from_user列并过滤关系的 to_user
(User.select().join(Relationship, onRelationship.from_user).where(Relationship.to_user charlie))10.7 加入任意字段
如果两个表之间不存在外键您仍然可以执行连接但您必须手动指定连接谓词。
在以下示例中 User和 ActivityLog之间没有显式外键但ActivityLog.object_id字段和User.id之间存在隐含关系 。Field我们将使用加入而不是加入特定 的Expression.
user_log (User.select(User, ActivityLog).join(ActivityLog, on(User.id ActivityLog.object_id), attrlog).where((ActivityLog.activity_type user_activity) (User.username charlie)))for user in user_log:print(user.username, user.log.description)#### Print something like ####
charlie logged in
charlie posted a tweet
charlie retweeted
charlie posted a tweet
charlie logged out笔记 attr回想一下我们可以通过在方法中指定参数来控制 Peewee 将分配连接实例的属性join()。在前面的示例中我们使用了以下连接 join(ActivityLog, on(User.id ActivityLog.object_id), attrlog)然后在遍历查询时我们能够直接访问加入的ActivityLog而不会产生额外的查询 for user in user_log:print(user.username, user.log.description)
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/917919.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!