关系和连接

在本文档中,我们将介绍Peewee如何处理模型之间的关系。

模型定义

我们将使用以下模型定义作为示例:

import datetime
from peewee import *


db = SqliteDatabase(':memory:')

class BaseModel(Model):
    class Meta:
        database = db

class User(BaseModel):
    username = TextField()

class Tweet(BaseModel):
    content = TextField()
    timestamp = DateTimeField(default=datetime.datetime.now)
    user = ForeignKeyField(User, backref='tweets')

class Favorite(BaseModel):
    user = ForeignKeyField(User, backref='favorites')
    tweet = ForeignKeyField(Tweet, backref='favorites')

Peewee使用 ForeignKeyField 定义模型之间的外键关系。每个外键字段都有一个隐含的反向引用,该引用作为预筛选公开 Select 使用提供的 backref 属性。

创建测试数据

为了遵循这些示例,让我们用一些测试数据填充这个数据库:

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(username=username)
        for tweet in tweets:
            Tweet.create(user=user, content=tweet)

    # 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(user=user, tweet=tweet)

这给了我们以下信息:

用户

鸣叫

喜爱的

休伊

扎伊齐

休伊

嘘声

休伊

咕噜咕噜的响声

米奇,扎伊齐

米老鼠

沃夫

米老鼠

发牢骚

休伊

注意

在下面的示例中,我们将执行许多查询。如果不确定要执行多少查询,可以添加以下代码,这些代码将所有查询记录到控制台:

import logging
logger = logging.getLogger('peewee')
logger.addHandler(logging.StreamHandler())
logger.setLevel(logging.DEBUG)

注解

在sqlite中,默认情况下不启用外键。大多数东西,包括peewee foreign key api,都会很好地工作,但是在删除行为上会被忽略,即使您明确地指定 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})

执行简单联接

作为学习如何使用peewee执行joins的练习,让我们编写一个查询,用“huey”打印出所有tweet。为此,我们将从 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 = 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_id"
  FROM "tweet" AS "t1" WHERE ("t1"."user_id" = ?)', [1])

联接多个表

让我们再看看Join,查询用户列表,并计算出他们所写的、最受欢迎的tweet数量。这需要我们加入两次:从用户到推特,从推特到最爱。我们将增加额外的要求,包括没有创建任何tweet的用户,以及那些tweet不受欢迎的用户。用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

注解

在上面的查询中,两个连接都是保留在外部的,因为用户可能没有任何tweet,或者,如果他们有tweet,他们可能都没有受到青睐。

Peewee的概念是 连接上下文, 也就是说,每当我们调用 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的所有tweet以及它们被喜欢的次数。要做到这一点,我们需要执行两个联接,我们还将使用聚合函数来计算最喜爱的计数。

以下是我们将如何在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;

注解

我们使用从tweet到favorite的左外部联接,因为tweet可能没有任何收藏夹,但我们仍然希望在结果集中显示它的内容(以及计数为零)。

使用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设置 join context 回到 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))

我们可以迭代上述查询的结果,以打印tweet的内容和最喜爱的计数:

>>> for tweet in query:
...     print('%s favorited %d times' % (tweet.content, tweet.count))
...
meow favorited 1 times
hiss favorited 0 times
purr favorited 2 times

从多个来源中选择

如果我们希望列出数据库中的所有tweet以及作者的用户名,您可以尝试编写以下内容:

>>> for tweet in Tweet.select():
...     print(tweet.user.username, '->', tweet.content)
...
huey -> meow
huey -> hiss
huey -> purr
mickey -> woof
mickey -> whine

上面的循环存在一个大问题:它对每个tweet执行一个额外的查询来查找 tweet.user 外键。对于我们的小表,性能损失并不明显,但是我们会发现延迟随着行数的增加而增加。

如果您熟悉SQL,您可能会记住,可以从多个表中进行选择,从而允许我们获取tweet内容。 and 单个查询中的用户名:

SELECT tweet.content, user.username
FROM tweet
INNER JOIN user ON tweet.user_id = user.id;

Peewee 很容易。实际上,我们只需要稍微修改一下查询。我们告诉Peewee我们想选择 Tweet.content 以及 User.username 字段,然后我们包含一个从tweet到user的连接。为了更明显地表明它在做正确的事情,我们可以要求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 ——不是 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

如果我们愿意,我们可以控制Peewee把 User 通过指定 attrjoin() 方法:

>>> query = Tweet.select(Tweet.content, User.username).join(User, attr='author')
>>> for tweet in query:
...     print(tweet.author.username, '->', tweet.content)
...
huey -> meow
huey -> hiss
huey -> purr
mickey -> woof
mickey -> whine

相反,如果我们只是希望 all 我们选择的属性是 Tweet 例如,我们可以向 objects() 在查询结束时(类似于我们调用 dicts() ):

>>> for tweet in query.objects():
...     print(tweet.username, '->', tweet.content)
...
huey -> meow
(etc)

更复杂的例子

作为一个更复杂的例子,在这个查询中,我们将编写一个选择所有收藏的查询,以及创建收藏的用户、收藏的tweet和tweet的作者。

在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);

请注意,我们从用户表中选择了两次——一次是在创建最喜爱的用户的上下文中,另一次是作为tweet的作者。

我们用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 huey

子查询

Peewee允许您连接任何类似于表的对象,包括子查询或公共表表达式(CTE)。为了演示在子查询上的连接,让我们查询所有用户及其最新tweet。

以下是SQL:

SELECT tweet.*, user.*
FROM tweet
INNER JOIN (
    SELECT latest.user_id, MAX(latest.timestamp) AS max_ts
    FROM tweet AS latest
    GROUP 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)

我们将通过创建一个子查询来实现这一点,该子查询选择每个用户及其最新tweet的时间戳。然后我们可以在外部查询中查询tweets表,并从子查询中联接用户和时间戳组合。

# Define our subquery first. We'll 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, on=predicate)  # Join tweet -> subquery.
         .join_from(Tweet, User))  # Join from tweet -> user.

通过迭代查询,我们可以看到每个用户及其最新的tweet。

>>> for tweet in query:
...     print(tweet.user.username, '->', tweet.content)
...
huey -> purr
mickey -> whine

在我们在本节中创建查询时使用的代码中,可能有一些您以前没有见过的内容:

  • 我们用过 join_from() 显式指定联接上下文。我们写道 .join_from(Tweet, User) ,相当于 .switch(Tweet).join(User) .

  • 我们使用magic引用了子查询中的列 .c 例如,属性 latest_query.c.max_ts . 这个 .c 属性用于动态创建列引用。

  • 而不是将单个字段传递给 Tweet.select() 我们通过了 TweetUser 模型。这是选择给定模型上所有字段的简写。

公用表表达式

在上一节中,我们加入了一个子查询,但是我们可以很容易地使用 common-table expression (CTE) . 我们将重复同样的查询,列出用户和他们最新的tweet,但这次我们将使用CTE进行查询。

以下是SQL:

WITH latest AS (
    SELECT user_id, MAX(timestamp) AS max_ts
    FROM tweet
    GROUP BY user_id)
SELECT tweet.*, user.*
FROM tweet
INNER JOIN latest
    ON ((latest.user_id = tweet.user_id) AND (latest.max_ts = tweet.timestamp))
INNER JOIN user
    ON (tweet.user_id = user.id)

此示例与前面的子查询示例非常相似:

# Define our CTE first. We'll 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, on=predicate)  # Join tweet -> CTE.
         .join_from(Tweet, User)  # Join from tweet -> user.
         .with_cte(cte))

我们可以迭代结果集,其中包括每个用户的最新tweet:

>>> for tweet in query:
...     print(tweet.user.username, '->', tweet.content)
...
huey -> purr
mickey -> whine

注解

有关使用CTE的详细信息,包括有关编写递归CTE的信息,请参阅 公用表表达式 “查询”文档的部分。

同一模型的多个外键

当同一模型有多个外键时,最好显式指定要加入的字段。

指回 example app's models 考虑一下 Relationship 模型,用于表示一个用户跟踪另一个用户的时间。模型定义如下:

class Relationship(BaseModel):
    from_user = ForeignKeyField(User, backref='relationships')
    to_user = ForeignKeyField(User, backref='related_to')

    class Meta:
        indexes = (
            # Specify a unique multi-column index on from/to-user.
            (('from_user', 'to_user'), True),
        )

因为有两个外键 User, 我们应该始终指定在联接中使用的字段。

例如,要确定我要跟踪哪些用户,我将编写:

(User
 .select()
 .join(Relationship, on=Relationship.to_user)
 .where(Relationship.from_user == charlie))

另一方面,如果我想确定哪些用户在跟踪我,我将加入 from_user 列和筛选关系的 to_user:

(User
 .select()
 .join(Relationship, on=Relationship.from_user)
 .where(Relationship.to_user == charlie))

在任意字段上联接

如果两个表之间不存在外键,则仍可以执行联接,但必须手动指定联接谓词。

在下面的示例中,在 User 和 活动日志, 但是在 ActivityLog.object_id 字段和 用户ID. 而不是加入特定的 Field ,我们将使用 Expression .

user_log = (User
            .select(User, ActivityLog)
            .join(ActivityLog, on=(User.id == ActivityLog.object_id), attr='log')
            .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 中的参数 join() 方法。在前面的示例中,我们使用了以下内容 join:

join(ActivityLog, on=(User.id == ActivityLog.object_id), attr='log')

然后,当遍历查询时,我们能够直接访问 ActivityLog 在不引发额外查询的情况下:

for user in user_log:
    print(user.username, user.log.description)

自连接

Peewee支持构造包含自连接的查询。

使用模型别名

要在同一个模型(表)上联接两次,需要创建一个模型别名来表示查询中表的第二个实例。考虑以下模型:

class Category(Model):
    name = CharField()
    parent = ForeignKeyField('self', backref='children')

如果我们想查询父类别是 数码产品. 一种方法是执行自连接:

Parent = Category.alias()
query = (Category
         .select()
         .join(Parent, on=(Category.parent == Parent.id))
         .where(Parent.name == 'Electronics'))

当执行使用 ModelAlias ,有必要使用 on 关键字参数。在这种情况下,我们将把类别与其父类别相结合。

使用子查询

另一种不太常见的方法是使用子查询。下面是另一种方法,我们可以构造一个查询来获取父类别为的所有类别 Electronics 使用子查询:

Parent = Category.alias()
join_query = Parent.select().where(Parent.name == 'Electronics')

# Subqueries used as JOINs need to have an alias.
join_query = join_query.alias('jq')

query = (Category
         .select()
         .join(join_query, on=(Category.parent == join_query.c.id)))

这将生成以下SQL查询:

SELECT t1."id", t1."name", t1."parent_id"
FROM "category" AS t1
INNER JOIN (
  SELECT t2."id"
  FROM "category" AS t2
  WHERE (t2."name" = ?)) AS jq ON (t1."parent_id" = "jq"."id")

访问 id 值来自子查询,我们使用 .c magic lookup将生成适当的SQL表达式:

Category.parent == join_query.c.id
# Becomes: (t1."parent_id" = "jq"."id")

实现多对多

Peewee提供了一个表示多对多关系的字段,就像Django一样。这个特性是由于用户的许多请求而添加的,但我强烈建议不要使用它,因为它将字段的概念与连接表和隐藏连接混为一谈。提供方便的附件只是一个讨厌的黑客。

实现多对多 correctly 因此,使用peewee,您将自己创建中介表并通过它进行查询:

class Student(Model):
    name = CharField()

class Course(Model):
    name = CharField()

class StudentCourse(Model):
    student = ForeignKeyField(Student)
    course = ForeignKeyField(Course)

要查询,假设我们要查找进入数学班的学生:

query = (Student
         .select()
         .join(StudentCourse)
         .join(Course)
         .where(Course.name == 'math'))
for student in query:
    print(student.name)

要查询给定学生注册的课程:

courses = (Course
           .select()
           .join(StudentCourse)
           .join(Student)
           .where(Student.name == 'da vinci'))

for course in courses:
    print(course.name)

为了有效地迭代多对多关系,即列出所有学生及其各自的课程,我们将查询 through 模型 StudentCourseprecompute 学生和课程:

query = (StudentCourse
         .select(StudentCourse, Student, Course)
         .join(Course)
         .switch(StudentCourse)
         .join(Student)
         .order_by(Student.name))

要打印学生及其课程的列表,您可以执行以下操作:

for student_course in query:
    print(student_course.student.name, '->', student_course.course.name)

因为我们从 StudentCourseselect 查询的子句,这些外键遍历是“自由的”,我们只用1个查询就完成了整个迭代。

ManyToManyField

这个 ManyToManyField 提供了一个 field-like 多对多字段的API。除了最简单的多对多情况外,您最好使用标准的Peewee API。但是,如果您的模型非常简单,并且您的查询需求也不是很复杂, ManyToManyField 可以工作。

建模学生和课程使用 ManyToManyField

from peewee import *

db = SqliteDatabase('school.db')

class BaseModel(Model):
    class Meta:
        database = db

class Student(BaseModel):
    name = CharField()

class Course(BaseModel):
    name = CharField()
    students = ManyToManyField(Student, backref='courses')

StudentCourse = Course.students.get_through_model()

db.create_tables([
    Student,
    Course,
    StudentCourse])

# Get all classes that "huey" is enrolled in:
huey = Student.get(Student.name == 'Huey')
for course in huey.courses.order_by(Course.name):
    print(course.name)

# Get all students in "English 101":
engl_101 = Course.get(Course.name == 'English 101')
for student in engl_101.students:
    print(student.name)

# When adding objects to a many-to-many relationship, we can pass
# in either a single model instance, a list of models, or even a
# query of models:
huey.courses.add(Course.select().where(Course.name.contains('English')))

engl_101.students.add(Student.get(Student.name == 'Mickey'))
engl_101.students.add([
    Student.get(Student.name == 'Charlie'),
    Student.get(Student.name == 'Zaizee')])

# The same rules apply for removing items from a many-to-many:
huey.courses.remove(Course.select().where(Course.name.startswith('CS')))

engl_101.students.remove(huey)

# Calling .clear() will remove all associated objects:
cs_150.students.clear()

注意

在添加多对多关系之前,需要先保存被引用的对象。为了通过表在多对多中创建关系,Peewee需要知道被引用模型的主键。

警告

它是 strongly recommended 您不会尝试对包含 ManyToManyField 实例。

A ManyToManyField 尽管它的名字,但不是通常意义上的字段。“多对多”字段不是表中的列,而是包含这样一个事实:在后台实际上有一个带有两个外键指针的单独表(即 直达表) .

因此,当创建继承多对多字段的子类时,实际上需要继承的是 直达表. 由于可能存在细微的错误,Peewee不会尝试自动将through模型子类化并修改其外键指针。因此,多对多字段通常不适用于继承。

有关更多示例,请参见:

避免N+1问题

这个 N+1 problem 指应用程序执行查询的情况,然后对于结果集的每一行,应用程序至少执行一个其他查询(将其概念化的另一种方法是作为嵌套循环)。在许多情况下,这些 n 可以通过使用SQL联接或子查询来避免查询。数据库本身可以做一个嵌套的循环,但它通常比做更具性能。 n 应用程序代码中的查询,涉及与数据库通信的延迟,在加入或执行子查询时可能无法利用数据库使用的索引或其他优化。

Peewee提供了几种用于缓解 N+1 查询行为。回顾本文档中使用的模型, User 和 鸣叫, 本节将尝试概述一些常见的 N+1 场景,以及peewee如何帮助您避免它们。

注意

在某些情况下,N+1查询不会导致显著或可测量的性能损失。这一切都取决于您查询的数据、您使用的数据库以及执行查询和检索结果所涉及的延迟。像往常一样,在进行优化时,前后进行概要分析,以确保所做的更改符合您的期望。

列出最近的tweets

Twitter时间线显示来自多个用户的tweet列表。除了tweet的内容外,还显示tweet作者的用户名。这里的N+1方案是:

  1. 获取10条最近的tweets。

  2. 对于每个tweet,选择作者(10个查询)。

通过选择两个表并使用 join, Peewee可以在单个查询中完成此操作:

query = (Tweet
         .select(Tweet, User)  # Note that we are selecting both models.
         .join(User)  # Use an INNER join because every tweet has an author.
         .order_by(Tweet.id.desc())  # Get the most recent tweets.
         .limit(10))

for tweet in query:
    print(tweet.user.username, '-', tweet.message)

没有连接,访问 tweet.user.username 将触发查询以解析外键 tweet.user 并检索关联的用户。但自从我们选择并加入 User ,Peewee将自动为我们解析外键。

注解

这项技术在 从多个来源中选择 .

列出用户及其所有推文

假设您想要构建一个显示几个用户及其所有tweet的页面。N+1方案是:

  1. 获取一些用户。

  2. 对于每个用户,获取他们的tweets。

这种情况与前面的例子类似,但有一个重要的区别:当我们选择tweets时,它们只有一个关联的用户,因此我们可以直接分配外键。然而,情况并非如此,因为一个用户可能有任何数量的tweet(或者根本没有)。

Peewee提供了一种在这种情况下避免*o(n)*查询的方法。先获取用户,然后获取与这些用户相关联的所有tweet。一旦peewee拥有了大量的tweet列表,它就会将它们分配出去,并与相应的用户进行匹配。此方法通常更快,但将涉及对所选每个表的查询。

使用预取

Peewee支持使用子查询预取相关数据。此方法需要使用特殊的API, prefetch() .预取,顾名思义,将使用子查询为给定的用户热切地加载适当的tweet。这意味着不是 O(n) queries for n 排,我们会的 O(k) queries for k 桌子。

下面是一个例子,说明我们如何获取几个用户以及他们在过去一周内创建的任何tweet。

week_ago = datetime.date.today() - datetime.timedelta(days=7)
users = User.select()
tweets = (Tweet
          .select()
          .where(Tweet.timestamp >= week_ago))

# This will perform two queries.
users_with_tweets = prefetch(users, tweets)

for user in users_with_tweets:
    print(user.username)
    for tweet in user.tweets:
        print('  ', tweet.message)

注解

注意,两者都不是 User 查询,也不 Tweet 查询包含联接子句。使用时 prefetch() 您不需要指定联接。

prefetch() 可用于查询任意数量的表。有关更多示例,请查看API文档。

使用时需要考虑的一些事项 prefetch()

  • 要预取的模型之间必须存在外键。

  • LIMIT 在最外部的查询上可以像您预期的那样工作,但是如果试图限制子选择的大小,可能很难正确地实现。