8.3 更新现有记录
一旦模型实例具有主键,任何后续调用都 save()将导致UPDATE而不是另一个INSERT。模型的主键不会改变:
>>> user.save() # save() returns the number of rows modified.
1
>>> user.id
1
>>> user.save()
>>> user.id
1
>>> huey.save()
1
>>> huey.id
2
如果要更新多条记录,请发出UPDATE查询。以下示例将更新所有Tweet对象,将它们标记为已发布,如果它们是在今天之前创建的。Model.update()接受关键字参数,其中键对应于模型的字段名称:
>>> today = datetime.today()
>>> query = Tweet.update(is_published=True).where(Tweet.creation_date < today)
>>> query.execute() # Returns the number of rows that were updated.
4
有关详细信息,请参阅有关Model.update()和 Update的文档Model.bulk_update()。
笔记
如果您想了解有关执行原子更新的更多信息(例如增加列的值),请查看原子更新 配方。
8.4 Atomic updates原子更新
Peewee 允许您执行原子更新。假设我们需要更新一些计数器。天真的方法是这样写:
>>> for stat in Stat.select().where(Stat.url == request.url):
... stat.counter += 1
... stat.save()
不要这样做!这不仅速度慢,而且如果多个进程同时更新计数器,它也容易受到竞争条件的影响。
相反,您可以使用以下方法自动更新计数器update():
>>> query = Stat.update(counter=Stat.counter + 1).where(Stat.url == request.url)
>>> query.execute()
您可以根据需要使这些更新语句变得复杂。让我们给所有员工的奖金等于他们之前的奖金加上他们工资的 10%:
>>> query = Employee.update(bonus=(Employee.bonus + (Employee.salary * .1)))
>>> query.execute() # Give everyone a bonus!
我们甚至可以使用子查询来更新列的值。假设我们在模型上有一个非规范化列,User用于存储用户发布的推文数量,并且我们会定期更新此值。以下是编写此类查询的方法:
>>> subquery = Tweet.select(fn.COUNT(Tweet.id)).where(Tweet.user == User.id)
>>> update = User.update(num_tweets=subquery)
>>> update.execute()
8.4.1 Upsert
Peewee 提供对不同类型的 upsert 功能的支持。对于 3.24.0 之前的 SQLite 和 MySQL,Peewee 提供了replace(),它允许您插入记录,或者在违反约束的情况下替换现有记录。
使用replace()and的例子on_conflict_replace():
class User(Model):
username = TextField(unique=True)
last_login = DateTimeField(null=True)
# Insert or update the user. The "last_login" value will be updated
# regardless of whether the user existed previously.
user_id = (User
.replace(username='the-user', last_login=datetime.now())
.execute())
# This query is equivalent:
user_id = (User
.insert(username='the-user', last_login=datetime.now())
.on_conflict_replace()
.execute())
笔记
除了replace之外,如果您只想插入并忽略任何潜在的约束违规,SQLite、MySQL 和 Postgresql 还提供忽略
操作(请参阅:)。on_conflict_ignore()
MySQL通过ON DUPLICATE KEY UPDATE子句支持 upsert 。例如:
class User(Model):
username = TextField(unique=True)
last_login = DateTimeField(null=True)
login_count = IntegerField()
# Insert a new user.
User.create(username='huey', login_count=0)
# Simulate the user logging in. The login count and timestamp will be
# either created or updated correctly.
now = datetime.now()
rowid = (User
.insert(username='huey', last_login=now, login_count=1)
.on_conflict(
preserve=[User.last_login], # Use the value we would have inserted.
update={User.login_count: User.login_count + 1})
.execute())
在上面的示例中,我们可以安全地多次调用 upsert 查询。登录计数将自动增加,最后登录列将被更新,并且不会创建重复的行。
Postgresql 和 SQLite(3.24.0 和更新版本)提供了不同的语法,允许更精细地控制哪些约束违反应该触发冲突解决,以及应该更新或保留哪些值。
on_conflict()用于执行 Postgresql 样式的 upsert(或 SQLite 3.24+)的示例:
class User(Model):
username = TextField(unique=True)
last_login = DateTimeField(null=True)
login_count = IntegerField()
# Insert a new user.
User.create(username='huey', login_count=0)
# Simulate the user logging in. The login count and timestamp will be
# either created or updated correctly.
now = datetime.now()
rowid = (User
.insert(username='huey', last_login=now, login_count=1)
.on_conflict(
conflict_target=[User.username], # Which constraint?
preserve=[User.last_login], # Use the value we would have inserted.
update={User.login_count: User.login_count + 1})
.execute())
在上面的示例中,我们可以安全地多次调用 upsert 查询。登录计数将自动增加,最后登录列将被更新,并且不会创建重复的行。
笔记
MySQL 和 Postgresql/SQLite 之间的主要区别在于 Postgresql 和 SQLite
要求您指定一个conflict_target.
这是一个使用命名空间的更高级(如果是人为的)示例EXCLUDED 。EXCLUDED帮助器允许我们引用冲突数据中的值。对于我们的示例,我们将假设一个简单的表将唯一键(字符串)映射到值(整数):
class KV(Model):
key = CharField(unique=True)
value = IntegerField()
# Create one row.
KV.create(key='k1', value=1)
# Demonstrate usage of EXCLUDED.
# Here we will attempt to insert a new value for a given key. If that
# key already exists, then we will update its value with the *sum* of its
# original value and the value we attempted to insert -- provided that
# the new value is larger than the original value.
query = (KV.insert(key='k1', value=10)
.on_conflict(conflict_target=[KV.key],
update={KV.value: KV.value + EXCLUDED.value},
where=(EXCLUDED.value > KV.value)))
# Executing the above query will result in the following data being
# present in the "kv" table:
# (key='k1', value=11)
query.execute()
# If we attempted to execute the query *again*, then nothing would be
# updated, as the new value (10) is now less than the value in the
# original row (11).
有关详细信息,请参阅Insert.on_conflict()和 OnConflict。
8.5 删除记录
要删除单个模型实例,您可以使用 Model.delete_instance()快捷方式。delete_instance() 将删除给定的模型实例,并且可以选择递归地删除任何依赖对象(通过指定recursive=True)。
>>> user = User.get(User.id == 1)
>>> user.delete_instance() # Returns the number of rows deleted.
1
>>> User.get(User.id == 1)
UserDoesNotExist: instance matching query does not exist:
SQL: SELECT t1."id", t1."username" FROM "user" AS t1 WHERE t1."id" = ?
PARAMS: [1]
要删除任意一组行,您可以发出DELETE查询。以下将删除所有Tweet超过一年的对象:
>>> query = Tweet.delete().where(Tweet.creation_date < one_year_ago)
>>> query.execute() # Returns the number of rows deleted.
7
有关更多信息,请参阅以下文档:
- Model.delete_instance()
- Model.delete()
- DeleteQuery