八、查询
http://docs.peewee-orm.com/en/latest/peewee/querying.html
本节将介绍通常在关系数据库上执行的基本 CRUD 操作:
- Model.create(), 用于执行INSERT查询。
- Model.save()和Model.update(), 用于执行UPDATE 查询。
- Model.delete_instance()和Model.delete(), 用于执行 DELETE查询。
- Model.select(),用于执行SELECT查询。
笔记
还有大量来自 Postgresql 练习网站的示例查询。查询示例文档中列出了示例。
8.1 创建新记录
您可以使用它Model.create()来创建一个新的模型实例。此方法接受关键字参数,其中键对应于模型字段的名称。返回一个新实例,并将一行添加到表中。
>>> User.create(username='Charlie')
<__main__.User object at 0x2529350>
这将在数据库中插入一个新行。主键将被自动检索并存储在模型实例中。
或者,您可以以编程方式构建模型实例,然后调用 save():
>>> user = User(username='Charlie')
>>> user.save() # save() returns the number of rows modified.
1
>>> user.id
1
>>> huey = User()
>>> huey.username = 'Huey'
>>> huey.save()
1
>>> huey.id
2
当模型有外键时,可以在新建记录时直接将模型实例赋值给外键字段。
>>> tweet = Tweet.create(user=huey, message='Hello!')
您还可以使用相关对象的主键的值:
>>> tweet = Tweet.create(user=2, message='Hello again!')
如果您只是想插入数据而不需要创建模型实例,您可以使用Model.insert():
>>> User.insert(username='Mickey').execute()
3
执行插入查询后,返回新行的主键。
笔记
有几种方法可以加快批量插入操作。查看批量插入配方部分了解更多信息。
8.2 批量插入
有几种方法可以快速加载大量数据。天真的方法是简单地调用Model.create()一个循环:
data_source = [
{'field1': 'val1-1', 'field2': 'val1-2'},
{'field1': 'val2-1', 'field2': 'val2-2'},
# ...
]
for data_dict in data_source:
MyModel.create(**data_dict)
由于以下几个原因,上述方法很慢:
- 如果您没有将循环包装在事务中,则每次调用都 create()发生在其自己的事务中。这将是非常缓慢的!
- 有相当数量的 Python 逻辑阻碍了你的工作,每个逻辑都 InsertQuery必须生成并解析为 SQL。
- 这是您发送到数据库进行解析的大量数据(就 SQL 的原始字节而言)。
- 我们正在检索最后一个插入 id,这在某些情况下会导致执行额外的查询。
你可以通过简单地将它包装在一个事务中来获得显着的加速 atomic()。
# This is much faster.
with db.atomic():
for data_dict in data_source:
MyModel.create(**data_dict)
上面的代码仍然存在第 2、3 和 4 点的问题。我们可以通过使用insert_many(). 此方法接受元组或字典列表,并在单个查询中插入多行:
data_source = [
{'field1': 'val1-1', 'field2': 'val1-2'},
{'field1': 'val2-1', 'field2': 'val2-2'},
# ...
]
# Fastest way to INSERT multiple rows.
MyModel.insert_many(data_source).execute()
该insert_many()方法还接受行元组列表,前提是您还指定了相应的字段:
# We can INSERT tuples as well...
data = [('val1-1', 'val1-2'),
('val2-1', 'val2-2'),
('val3-1', 'val3-2')]
# But we need to indicate which fields the values correspond to.
MyModel.insert_many(data, fields=[MyModel.field1, MyModel.field2]).execute()
将批量插入包装在事务中也是一个好习惯:
# You can, of course, wrap this in a transaction as well:
with db.atomic():
MyModel.insert_many(data, fields=fields).execute()
笔记
SQLite 用户在使用批量插入时应该注意一些警告。具体来说,您的 SQLite3 版本必须是 3.7.11.0 或更高版本才能利用批量插入
API。此外,默认情况下,SQLite 将 SQL 查询中绑定变量的数量限制999为 3.32.0 (2020-05-22) 之前的
SQLite 版本和 3.32.0 之后的 SQLite 版本的 32766。
8.2.1 批量插入行
根据数据源中的行数,您可能需要将其分成块。特别是 SQLite 通常限制每个查询 999 或 32766 个 变量(批量大小将是 999 // 行长度或 32766 // 行长度)。
您可以编写一个循环来将您的数据批处理成块(在这种情况下, 强烈建议您使用事务):
# Insert rows 100 at a time.
with db.atomic():
for idx in range(0, len(data_source), 100):
MyModel.insert_many(data_source[idx:idx+100]).execute()
Peewee 附带了一个chunked()辅助函数,您可以使用它来 有效地将通用迭代分块为一系列批量大小的迭代:
from peewee import chunked
# Insert rows 100 at a time.
with db.atomic():
for batch in chunked(data_source, 100):
MyModel.insert_many(batch).execute()
8.2.2 备择方案
该Model.bulk_create()方法的行为很像 Model.insert_many(),但它接受要插入的未保存模型实例的列表,并且它可以选择接受批量大小参数。要使用bulk_create()API:
# Read list of usernames from a file, for example.
with open('user_list.txt') as fh:
# Create a list of unsaved User instances.
users = [User(username=line.strip()) for line in fh.readlines()]
# Wrap the operation in a transaction and batch INSERT the users
# 100 at a time.
with db.atomic():
User.bulk_create(users, batch_size=100)
笔记
如果您使用的是 Postgresql(支持该RETURNING子句),那么以前未保存的模型实例将自动填充其新的主键值。
此外,Peewee 还提供Model.bulk_update(),可以有效地更新模型列表中的一个或多个列。例如:
# First, create 3 users with usernames u1, u2, u3.
u1, u2, u3 = [User.create(username='u%s' % i) for i in (1, 2, 3)]
# Now we'll modify the user instances.
u1.username = 'u1-x'
u2.username = 'u2-y'
u3.username = 'u3-z'
# Update all three users with a single UPDATE query.
User.bulk_update([u1, u2, u3], fields=[User.username])
笔记
对于大型对象列表,您应该指定一个合理的 batch_size 并将调用包装到bulk_update()with
Database.atomic():
with database.atomic():
User.bulk_update(list_of_users, fields=['username'], batch_size=50)
或者,您可以使用Database.batch_commit()帮助程序来处理批处理大小的事务中的行块。此方法还为除 Postgresql 之外的数据库提供了一种解决方法,当必须获取新创建的行的主键时。
# List of row data to insert.
row_data = [{'username': 'u1'}, {'username': 'u2'}, ...]
# Assume there are 789 items in row_data. The following code will result in
# 8 total transactions (7x100 rows + 1x89 rows).
for row in db.batch_commit(row_data, 100):
User.create(**row)
8.2.3 从另一个表批量加载
如果您要批量加载的数据存储在另一个表中,您还可以创建源为SELECT查询的INSERT查询。使用 方法:Model.insert_from()
res = (TweetArchive
.insert_from(
Tweet.select(Tweet.user, Tweet.message),
fields=[TweetArchive.user, TweetArchive.message])
.execute())
上面的查询等价于下面的 SQL:
INSERT INTO "tweet_archive" ("user_id", "message")
SELECT "user_id", "message" FROM "tweet";