說明:peewee 中有很多方法是延時執行的,需要呼叫
execute()
方法使其執行。下文中不再特意說明這個問題,大家看程式碼。
本文中程式碼樣例所使用的 Person 模型如下:
class Person(Model):
Name = CharField()
Age = IntegerField()
Birthday = DateTimeField()
Remarks = CharField(null=True)
一、新增
1、create
Model.create
向資料庫中插入一條記錄,並返回一個新的例項。
p = Person.create(Name=`張三`, Age=30, Birthday=date(1990, 1, 1))
2、save
語法:
save(force_insert=False, only=None)
引數:
- force_insert:是否強制插入
- only(list):需要持久化的欄位,當提供此引數時,只有提供的欄位被持久化。
示例:
p1 = Person(Name=`王五`, Age=50, Birthday=date(1970, 1, 1))
p1.save()
這裡說的比較簡單,下面會詳細說明。
3、insert
insert
只插入資料而不建立模型例項,返回新行的主鍵。
Person.insert(Name=`李四`, Age=40, Birthday=date(1980, 1, 1)).execute()
4、insert_many
語法:
insert_many(rows, fields=None)
引數:
- rows:元組或字典列表,要插入的資料
- fields(list):需要插入的欄位名列表。
說明:
1、當 rows 傳遞的是字典列表時,fields 是不需要傳的,如果傳了,那麼,rows 中的欄位在字典中必須存在,否則報錯。如果沒有傳遞 fields 引數,那麼預設取所有字典的交集作為插入欄位。這個也好理解,比如一個字典的鍵是a、b、c
,一個是b、c、d
,那麼就取b、c
作為需要插入的欄位。peewee 不會為缺失的欄位做預設處理。
2、當 rows 傳遞的是元組列表時,必須指定 fields,並且 fields 中欄位名的順序跟元組一致。元組中值的數量必須大於等於 fields 中欄位的數量,一般建議是保持一致。
示例:
Person.insert_many([
(`張三`, 30, date(1990, 1, 1)),
(`李四`, 40, date(1980, 1, 1)),
(`王五`, 50, date(1970, 1, 1))
],
[`Name`, `Age`, `Birthday`]
).execute()
Person.insert_many([
{`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()
5、bulk_create
語法:
bulk_create(model_list, batch_size=None)
引數:
- model_list (iterable):未儲存的模型例項的列表或其他可迭代物件。
- batch_size (int):每次批量插入的行數。如果未指定,則一次性全部插入。
示例:
簡單來說,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
。
6、batch_commit
這不是一個好的方法,來看下面的例子
data_dict = [{`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.batch_commit(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 語句就明白了。
7、insert_from
使用 SELECT 查詢作為源 INSERT 資料。此 API 應用於 INSERT INTO … SELECT FROM … 形式的查詢。
語法:
insert_from(query, fields)
引數:
- query:SELECT查詢用作資料來源
- fields:要將資料插入的欄位,此引數必須要的
示例:我們將 Person 表按原結構複製一個 Person2 表出來,以做演示。
data = Person.select(Person.Name, Person.Age, Person.Birthday)
Person2.insert_from(data, [`Name`, `Age`, `Birthday`]).execute()
注意: 因為是 INSERT INTO … SELECT FROM … 形式的,所以資料來源的列跟要插入的列必須保持一致。
二、刪除
1、delete
delete
後加 where
刪除指定記錄,如果不加 where
,則刪除全部記錄。
Person.delete().where(Person.Name==`王五`).execute()
2、delete_instance
刪除給定的例項。
語法:
delete_instance(recursive=False, delete_nullable=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.delete_instance(recursive=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])
③ 當 recursive=True
,並且外來鍵可為空時,先將【人員】的【部門ID(外來鍵欄位)】置為了 NULL,再刪除【部門】。
d = Department.get(1)
d.delete_instance(recursive=True)
# 執行的 SQL 語句
(`SELECT "t1"."id", "t1"."Name" FROM "department" AS "t1" WHERE ? LIMIT ? OFFSET ?`, [1, 1, 0])
(`UPDATE "person" SET "Department_id" = ? 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.delete_instance(recursive=True, delete_nullable=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])
三、修改
1、save
之前說過,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 這種操作根本不存在,只能自己來寫判斷。
2、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()
3、原子更新
看這樣的一個需求,有一張表,記錄部落格的訪問量,每次有人訪問部落格的時候,訪問量+1。
因為懶得新建模型,我們就以 Person 模型的 Age + 1 來演示。
我們可以這樣來寫:
for p in Person.select():
p.Age += 1
p.save()
這樣當然是可以實現的,但是這不僅速度慢,而且如果多個程式同時更新計數器,它也容易受到競爭條件的影響。
我們可以用 update
方法來實現。
Person.update(Age=Person.Age+1).execute()
四、查詢
1、get
Model.get()
方法檢索與給定查詢匹配的單個例項。
語法:
get(*query, **filters)
引數:
- query:查詢條件
- filters:Mapping of field-name to value for Django-style filter. 我翻遍網上文章和官方文件都沒找到這玩意怎麼用!
示例:
p1 = Person.get(Name=`張三`)
或者
p2 = Person.get(Person.Name == `李四`)
當獲取的結果不存在時,報 Model.DoesNotExist
異常。如果有多條記錄滿足條件,則返回第一條。
2、get_or_none
如果當獲取的結果不存在時,不想報錯,可以使用 Model.get_or_none()
方法,會返回 None
,引數和 get
方法一致。
3、get_by_id
對於主鍵查詢,還可以使用快捷方法Model.get_by_id()
。
Person.get_by_id(1)
4、get_or_create
Peewee 有一個輔助方法來執行“獲取/建立”型別的操作: Model.get_or_create()
首先嚐試檢索匹配的行。如果失敗,將建立一個新行。
p, created = Person.get_or_create(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)])
引數:get_or_create
的引數是 **kwargs
,其中 defaults 為非查詢條件的引數,剩餘的為嘗試檢索匹配的條件,這個看執行時的 SQL 語句就一目瞭然了。對於“建立或獲取”型別邏輯,通常會依賴唯一 約束或主鍵來防止建立重複物件。但這並不是強制的,比如例子中,我以 Name
為條件,而 Name
並非主鍵。只是最好不要這樣做。
返回值:get_or_create
方法有兩個返回值,第一個是“獲取/建立”的模型例項,第二個是是否新建立。
5、select
使用 Model.select()
查詢獲取多條資料。select
後可以新增 where
條件,如果不加則查詢整個表。
語法:
select(*fields)
引數:
- fields:需要查詢的欄位,不傳時返回所有欄位。傳遞方式如下例所示。
示例:
ps = Person.select(Person.Name, Person.Age).where(Person.Name == `張三`)
select()
返回結果是一個 ModelSelect
物件,該物件可迭代、索引、切片。當查詢不到結果時,不報錯,返回 None
。並且 select()
結果是延時返回的。如果想立即執行,可以呼叫 execute()
方法。
注意:
where
中的條件不支援Name=`張三`
這種寫法,只能是Person.Name == `張三`
。
6、獲取記錄條數 count 方法
使用 .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"`, [])
直接返回所有記錄然後獲取長度,這種方法是非常不可取的。
7、排序 order_by 方法
Person.select().order_by(Person.Age)
排序預設是升序排列,也可以用 +
或 asc()
來明確表示是升序排列:
Person.select().order_by(+Person.Age)
Person.select().order_by(Person.Age.asc())
用 -
或 desc()
來表示降序:
Person.select().order_by(-Person.Age)
Person.select().order_by(Person.Age.desc())
如要對多個欄位進行排序,逗號分隔寫就可以了。
五、查詢條件
當查詢條件不止一個,需要使用邏輯運算子連線,而 Python 中的 and
、or
在 Peewee 中是不支援的,此時我們需要使用 Peewee 封裝好的運算子,如下:
邏輯符 | 含義 | 樣例 |
---|---|---|
& | and | Person.select().where((Person.Name == `張三`) & (Person.Age == 30)) |
| | or | Person.select().where((Person.Name == `張三`) | (Person.Age == 30)) |
~ | not | Person.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%`)