说明:peewee 中有很多方法是延时执行的,需要调用
execute()方法使其执行。下文中不再特意说明这个问题,大家看代码。
本文中代码样例所使用的 Person 模型如下:
class Person(Model): Name = CharField() Age = IntegerField() Birthday = DateTimeField() Remarks = CharField(null=True)
Model.create向数据库中插入一条记录,并返回一个新的实例。p = Person.create(Name='张三', Age=30, Birthday=date(1990, 1, 1))
语法:save(force_insert=False, only=None)
参数:
示例:p1 = Person(Name='王五', Age=50, Birthday=date(1970, 1, 1)) p1.save()
这里说的比较简单,下面会详细说明。
insert只插入数据而不创建模型实例,返回新行的主键。Person.insert(Name='李四', Age=40, Birthday=date(1980, 1, 1)).execute()
语法:insert_many(rows, fields=None)
参数:
a、b、c,一个是
b、c、d,那么就取
b、c作为需要插入的字段。peewee 不会为缺失的字段做默认处理。2、当 rows 传递的是元组列表时,必须指定 fields,并且 fields 中字段名的顺序跟元组一致。元组中值的数量必须大于等于 fields 中字段的数量,一般建议是保持一致。
示例:
Person.insertmany([ ('张三', 30, date(1990, 1, 1)), ('李四', 40, date(1980, 1, 1)), ('王五', 50, date(1970, 1, 1)) ], ['Name', 'Age', 'Birthday'] ).execute() Person.insertmany([ {'Name': '张三', 'Age': 30, 'Birthday': date(1990, 1, 1)}, {'Name': '李四', 'Age': 40, 'Birthday': date(1980, 1, 1)}, {'Name': '王五', 'Age': 50, 'Birthday': date(1970, 1, 1)} ] ).execute()
对于批量操作,应该放在事务中执行:
with db.atomic(): Person.insert_many(data, fields=fields).execute()
在使用批量插入时,如果是 SQLite,SQLite3 版本必须为 3.7.11.0 或更高版本才能利用批量插入API。此外,默认情况下,SQLite 将 SQL 查询中的绑定变量数限制为 999。
SQLite 中,当批量插入的行数超过 999 时,就需要使用循环来将数据批量分组:
with db.atomic(): for idx in range(0, len(data), 100): Person.insert_many(data[idx: idx+100], fields=fields).execute()
Peewee 中带有一个分块辅助函数
chunked(),使用它可以有效地将通用迭代块分块为一系列批量迭代的迭代:
from peewee import chunked /# 一次插入 100 行. with db.atomic(): for batch in chunked(data, 100): Person.insert_many(batch).execute()
语法:bulkcreate(modellist, batch_size=None)
参数:
示例:简单来说,
insert_many使用字典或元组列表作为参数,而
model_list使用模型实例列表作为参数,就这区别。data = [Person(Name='张三~', Age=30, Birthday=date(1990, 1, 1)), Person(Name='李四~', Age=40, Birthday=date(1980, 1, 1))] with db.atomic(): Person.bulk_create(data)
注意:如果使用的是 Postgresql(支持该RETURNING子句),则先前未保存的模型实例将自动填充其新的主键值。
例如用的是 SQLite,执行上述代码之后,
print(data[0].id)显示的结果是
None。
这不是一个好的方法,来看下面的例子datadict = [{'Name': '张三', 'Age': 30, 'Birthday': date(1990, 1, 1)}, {'Name': '李四', 'Age': 40, 'Birthday': date(1980, 1, 1)}, {'Name': '王五', 'Age': 50, 'Birthday': date(1970, 1, 1)}] for row in db.batchcommit(data_dict, 100): p = Person.create(/*/*row)
查看 SQL 语句如下:
('BEGIN', None) ('INSERT INTO "person" ("Name", "Age", "Birthday") VALUES (?, ?, ?)', ['张三', 30, datetime.date(1990, 1, 1)]) ('INSERT INTO "person" ("Name", "Age", "Birthday") VALUES (?, ?, ?)', ['李四', 40, datetime.date(1980, 1, 1)]) ('INSERT INTO "person" ("Name", "Age", "Birthday") VALUES (?, ?, ?)', ['王五', 50, datetime.date(1970, 1, 1)])
其实,
batch_commit就是自动添加了一个事务,然后一条条的插入,所以返回的模型实例中能获取到主键。参数第一个是字典列表,第二个就是每多少条启用一个事务,大家可以把它改成 1 看下 SQL 语句就明白了。
使用 SELECT 查询作为源 INSERT 数据。此 API 应用于 INSERT INTO … SELECT FROM … 形式的查询。
语法:insert_from(query, fields)
参数:
注意: 因为是 INSERT INTO … SELECT FROM … 形式的,所以数据源的列跟要插入的列必须保持一致。
delete后加
where删除指定记录,如果不加
where,则删除全部记录。Person.delete().where(Person.Name=='王五').execute()
删除给定的实例。语法:deleteinstance(recursive=False, deletenullable=False)
示例:
p = Person.get(Person.Name=='张三') p.delete_instance()
delete_instance直接执行删除了,不用调用
execute()方法。
参数:一般我都是先讲参数再讲示例的,这次倒过来,示例其实很简单,一看就明白。但是这个参数缺需要好好讲下。
这两个参数都跟外键有关。我们修改一下测试用的模型。假设有这样两个模型,一个人员,一个部门,人员属于部门。class Department(Model): Name = CharField() class Meta: database = db class Person(Model): Name = CharField() Age = IntegerField() Birthday = DateTimeField() Remarks = CharField(null=True) Department = ForeignKeyField(Department, null=True) /# 这里外键可为空和不可为空是不一样的,下面说明 class Meta: database = db
① 当
recursive=False时,只删除了【部门】,【人员】没有影响,从 SQL 语句中可以看出。
d = Department.get(1) d.delete_instance(recursive=False) /# 执行的 SQL 语句 ('SELECT "t1"."id", "t1"."Name" FROM "department" AS "t1" WHERE ? LIMIT ? OFFSET ?', [1, 1, 0]) ('DELETE FROM "department" WHERE ("department"."id" = ?)', [1])
② 当
recursive=True,并且外键不可为空时,会先删除【部门】下的【人员】,再删除【部门】。
d = Department.get(1) d.deleteinstance(recursive=True) /# 执行的 SQL 语句 ('SELECT "t1"."id", "t1"."Name" FROM "department" AS "t1" WHERE ? LIMIT ? OFFSET ?', [1, 1, 0]) ('DELETE FROM "person" WHERE ("person"."Departmentid" = ?)', [1]) ('DELETE FROM "department" WHERE ("department"."id" = ?)', [1])
③ 当
recursive=True,并且外键可为空时,先将【人员】的【部门ID(外键字段)】置为了 NULL,再删除【部门】。
d = Department.get(1) d.deleteinstance(recursive=True) /# 执行的 SQL 语句 ('SELECT "t1"."id", "t1"."Name" FROM "department" AS "t1" WHERE ? LIMIT ? OFFSET ?', [1, 1, 0]) ('UPDATE "person" SET "Departmentid" = ? WHERE ("person"."Department_id" = ?)', [None, 1]) ('DELETE FROM "department" WHERE ("department"."id" = ?)', [1])
④
delete_nullable仅在
recursive=True且外键可为空时有效,和 ③ 一样,当
delete_nullable=True时,会删除【人员】,而不是将【人员的部门ID】置为 NULL。
d = Department.get(1) d.deleteinstance(recursive=True, deletenullable=True) /# 执行的 SQL 语句 ('SELECT "t1"."id", "t1"."Name" FROM "department" AS "t1" WHERE ? LIMIT ? OFFSET ?', [1, 1, 0]) ('DELETE FROM "person" WHERE ("person"."Department_id" = ?)', [1]) ('DELETE FROM "department" WHERE ("department"."id" = ?)', [1])
之前说过,
save()方法可以插入一条记录,一旦模型实例具有主键,任何后续调用
save()都将导致 UPDATE 而不是另一个 INSERT。模型的主键不会改变。p = Person(Name='王五', Age=50, Birthday=date(1970, 1, 1)) p.save() print(p1.id) p.Remarks = 'abc' p.save()
这个例子,第一次执行的
save是 INSERT,第二次是 UPDATE。
这里解释一下,
Person这个模型,我并没有指定主键,peewee 会自动增加一个名为 id 的自增列作为主键。在执行第一个
save()方法的时候,主键没值,所以执行 INSERT,
save()方法执行之后,自增列的值就返回并赋给了模型实例,所以第二次调用
save()执行的是 UPDATE。如果模型中一开始就用
PrimaryKeyField或
primary_key指定了主键,那么
save执行的永远都是
update,所以什么主键不存在则 INSERT,存在则 UPDATE 这种操作根本不存在,只能自己来写判断。
update用于批量更新,方法相对简单,以下三种写法都可以/# 方法一 Person.update({Person.Name: '赵六', Person.Remarks: 'abc'}).where(Person.Name=='王五').execute() /# 方法二 Person.update({'Name': '赵六', 'Remarks': 'abc'}).where(Person.Name=='张三').execute() /# 方法三 Person.update(Name='赵六', Remarks='abc').where(Person.Name=='李四').execute()
看这样的一个需求,有一张表,记录博客的访问量,每次有人访问博客的时候,访问量+1。
因为懒得新建模型,我们就以 Person 模型的 Age + 1 来演示。
我们可以这样来写:for p in Person.select(): p.Age += 1 p.save()
这样当然是可以实现的,但是这不仅速度慢,而且如果多个进程同时更新计数器,它也容易受到竞争条件的影响。
我们可以用
update方法来实现。Person.update(Age=Person.Age+1).execute()
Model.get()方法检索与给定查询匹配的单个实例。语法:get(/query, //*filters)
参数:
示例:p1 = Person.get(Name='张三')
或者
p2 = Person.get(Person.Name == '李四')
当获取的结果不存在时,报
Model.DoesNotExist异常。如果有多条记录满足条件,则返回第一条。
如果当获取的结果不存在时,不想报错,可以使用
Model.getornone()方法,会返回
None,参数和
get方法一致。
对于主键查找,还可以使用快捷方法
Model.getbyid()。Person.getbyid(1)
Peewee 有一个辅助方法来执行“获取/创建”类型的操作:
Model.getorcreate()首先尝试检索匹配的行。如果失败,将创建一个新行。p, created = Person.getorcreate(Name='赵六', defaults={'Age': 80, 'Birthday': date(1940, 1, 1)}) print(p, created) /# SQL 语句 ('SELECT "t1"."id", "t1"."Name", "t1"."Age", "t1"."Birthday", "t1"."Remarks" FROM "person" AS "t1" WHERE ("t1"."Name" = ?) LIMIT ? OFFSET ?', ['赵六', 1, 0]) ('BEGIN', None) ('INSERT INTO "person" ("Name", "Age", "Birthday") VALUES (?, ?, ?)', ['赵六', 80, datetime.date(1940, 1, 1)])
参数:
getorcreate的参数是
//kwargs,其中 defaults 为非查询条件的参数,剩余的为尝试检索匹配的条件,这个看执行时的 SQL 语句就一目了然了。对于“创建或获取”类型逻辑,通常会依赖唯一 约束或主键来防止创建重复对象。但这并不是强制的,比如例子中,我以
Name为条件,而
Name并非主键。只是最好不要这样做。
返回值:
getorcreate方法有两个返回值,第一个是“获取/创建”的模型实例,第二个是是否新创建。
使用
Model.select()查询获取多条数据。
select后可以添加
where条件,如果不加则查询整个表。
语法:select(/*fields)
参数:
示例:ps = Person.select(Person.Name, Person.Age).where(Person.Name == '张三')
select()返回结果是一个
ModelSelect对象,该对象可迭代、索引、切片。当查询不到结果时,不报错,返回
None。并且
select()结果是延时返回的。如果想立即执行,可以调用
execute()方法。
注意:
where中的条件不支持
Name='张三'这种写法,只能是
Person.Name == '张三'。
使用
.count()方法可以获取记录条数。Person.select().count()
也许你会问,用
len()方法可以吗?当然也是可以的,但是是一种不可取的方法。
len(Person.select())
这两者的实现方式天差地远。用
count()方法,执行的 SQL 语句是:
('SELECT COUNT(1) FROM (SELECT 1 FROM "person" AS "t1") AS "_wrapped"', [])
而用
len()方法执行的 SQL 语句却是:
('SELECT "t1"."id", "t1"."Name", "t1"."Age", "t1"."Birthday", "t1"."Remarks" FROM "person" AS "t1"', [])
直接返回所有记录然后获取长度,这种方法是非常不可取的。
Person.select().order_by(Person.Age)
排序默认是升序排列,也可以用
+或
asc()来明确表示是升序排列:
Person.select().orderby(+Person.Age) Person.select().orderby(Person.Age.asc())
用
或
desc()来表示降序:
Person.select().orderby(-Person.Age) Person.select().orderby(Person.Age.desc())
如要对多个字段进行排序,逗号分隔写就可以了。
当查询条件不止一个,需要使用逻辑运算符连接,而 Python 中的
and、
or在 Peewee 中是不支持的,此时我们需要使用 Peewee 封装好的运算符,如下:逻辑符 含义 样例 & andPerson.select().where((Person.Name == '张三') & (Person.Age == 30)) | orPerson.select().where((Person.Name == '张三') | (Person.Age == 30)) ~ notPerson.select().where(~Person.Name == '张三')特别注意:有多个条件时,每个条件必须用 () 括起来。
当条件全为 and 时,也可以用逗号分隔,get 和 select 中都可以:
Person.get(Person.Name == '张三', Person.Age == 30)
运算符 含义 == 等于 < 小于 <= 小于等于 > 大于 >= 大于等于 != 不等于 << x in y,其中 y 是列表或查询 >> x is y, 其中 y 可以是 None % x like y // x like y注意:由于 SQLite 的 LIKE 操作默认情况下不区分大小写,因此 peewee 将使用 SQLite GLOB 操作进行区分大小写的搜索。glob 操作使用星号表示通配符,而不是通常的百分号。如果您正在使用 SQLite 并希望区分大小写的部分字符串匹配,请记住使用星号作为通配符。
解释一下,在 SQLite 中,如果希望 like 的时候区分大小写,可以这么写:
Person.select().where(Person.Remarks % 'a/*')
如果不希望区分大小写,这么写:
Person.select().where(Person.Remarks // 'a%')