PY => Python-ORM之peewee:CRUD完整解析(二)

聲明

上篇地址:https://segmentfault.com/a/11...
雖然上一篇,已經說明,但仍是強調一下,peewee是 python-ORM(只支持 MySQL,Sqlite,postgresql )
雖然ORM能夠與多種數據庫無縫相接,而且兼容性好, 可是某些細微的語法並非數據庫共有的。
我用MySQL, 因此下面說的都是基於MySQL(其餘2種數據庫也差不了多少, 99%是同樣的)
總官檔地址:http://docs.peewee-orm.com/en...
官方Github地址:https://github.com/coleifer/p...php

增長數據

方式1:(推薦)
    zhang = Owner.create(name='Zhang', age=20)
    
方式2:
    zhang = Owner(name='Zhang1', age=18)
    zhang.save() 
    # 你能夠看見,它須要用save(),因此推薦用上一種方式。

方式3:(推薦)
    cython = Owner.insert(name='Cython', age=15).execute()
    # 方式1 和 方式2, 返回結果都是模型實例"(也就意味着建立了一個實例)"
    # 而本方式,返回結果是 最新插入的主鍵值"(也就意味着不會建立實例)"

若是存在外鍵關聯,假如存在 Pet類 引用的 Owner的主鍵,插入數據方式有2種:html

方式1: 用新建對象變量傳值:
    lin = Owner.create(name='lin', age=20)            
    tom1 = Pet.create(name='Tom', age=1, owner=lin)    # 注意 owner = lin
    
方式2: 手動維護主鍵 id,經過主鍵傳值(或者經過查詢id):
    lin = Owner.create(id=100, name='lin', age=20)    # id本身給的值爲 100
    tom1 = Pet.create(name='Tom', age=1, owner=100)   # 注意 owner=100

插入多條數據:(官檔有好幾種方法,我只說最提倡,最快速的方法(好處就是一次性提交,不用循環))python

方式1:
    """
        注意格式 [ {},{},{} ]
        每一個字典,對應一條記錄。
    """
    data = [
        {'name': 'Alice', 'age': 18},
        {'name': 'Jack', 'age': 17},
    ]
    Owner.insert_many(data).execute()
    
方式2: (就是不用在數據中都指定鍵了,方便一點)
    """
        注意格式 [ (),(),() ]
        每一個元組,對應一條記錄。
    """
    data = [
        ('Alice', 18),
        ('Jack', 17),
    ]
    User.insert_many(data, fields=[Owner.name, Owner.age]).execute()
注意一下:尾部都必需要帶一個execute()

若是數據量過大,可能會出現OOM等問題。你能夠手動分批,可是 peewee 給咱們提供了成品的 apimysql

from peewee import chunked
with mysql_db.atomic():    # 官檔建議用事務包裹
    for batch in chunked(data, 100):    # 一次100條, chunked() 返回的是可迭代對象
        Owner.insert_many(batch).execute()

防止數據重複插入的2種辦法(或者防止設置了主鍵,重複插入拋出異常,致使程序沒法運行):git

方法1: INGORE關鍵字  (這種方式是若是衝突了,就自動忽略)
    SQL:
        insert ignore  into owner (name,age) values ('lin',30);
    peewee:
        Owner.insert(name='lin', age=30).on_conflict_ignore()

方法2:用 ON DUPLICATE KEY UPDATE  (這種方式,是若是衝突了,你還能夠作一些操做)
    SQL:
        insert into owner (name,age) values ('lin',30) 
            ON DUPLICATE KEY 
                UPDATE name='lin', age=30;             # 若是衝突了,能夠從新設置值           
    peewee:
        Owner.insert(name='lin', age=30).on_conflict(
            preserve=[Owner.name, Owner.age],          # 若衝突,你想保留不變的字段
            update={Owner.name: 'lin', Owner.age: 30}  # 若衝突,你想更新什麼
        ).execute()
        # 注: preserve 和 update 按狀況用,通常設置一個用就好了。

刪除數據

方法1:
    php = Owner.get(name='PHP')   # 獲取單條對象
    php.delete_instance()   
    # 注意:  delete_instance() 只能刪除單條對象, 若是用select()查出來的,須要遍歷才能刪
方法2:
    Owner.delete().where(Owner.name == 'lin').execute()
    # 注意這種方法和添加相似, 最後也必須有個 execute()

修改數據

方式1:(不推薦)
    owner= 查詢單個對象結果
    owner.name = 'Pack'
    owner.name = 50
    owner.save()        # 你能夠看見,咱們還須要手動調用一下save()
    
方式2:(推薦)
    query = Owner.update(name='Pack', age=50).where(Owner.name == 'Zhang')
    query.execute()

查詢數據

查詢單條數據 (特別注意,若是你有多條,它只會給你返回第一條)

"""存在則返回原有對象, 不存在則拋error"""
one_owner = Owner.get(name='Zhang2') 
print(one_woner.age)

擴展版1: get_or_create
    """存在則返回原有對象。不存在則插入數據,並返回新對象"""
    obj, status = Owner.get_or_create(name='Zhang23213',age=3123)
    print(obj.name, status)    
        # obj就是返回的新對象
        # status表示插入是否成功   True 或者 False
        
擴展版2: get_or_none
    """存在則返回原有對象, 不存在則返回 None  (不會拋error)"""
    Owner.get_or_none(name='abc')

查詢多條數據

正常查詢全部數據github

owners = Owner.select()        # 返回結果 owners 是對象集合,須要遍歷
for owner in owners:           # owner 是每一個對象(對應每條記錄)
    print(woner.name)

固然你能夠在查詢後轉爲 python 類dict格式:sql

owners = Owner.select().dicts()    # 返回結果 owners 是 "類字典對象集合"
for owner in owners:               # owner是每一個字典對象, (它 對應每條記錄)
    print(owner['name'])           # 字典語法取值,懂了吧,很少說了。

上面的查詢若是在數據大量的狀況下可能會致使OOM,所以可轉爲迭代:數據庫

"""再每一個查詢的最後加上 .iterator() 便可"""
eg:
    owners = Owner.select().iterator()
    owners = Owner.select().dicts().iterator()

條件查詢:

首先我先強調個,"MySQL是否區分大小寫" 的事:segmentfault

MySQL5.7+,是區分大小寫的; (MySQL8,和 MariaDB 我沒試, 應該和 5.7是同樣的)
但這個區分大小寫 僅僅僅僅僅僅 是 針對於 SQL語句的表名 "" 引號外面的(就是非字符串語法)
舉個例子:
    現有一表,名叫  owner
        desc owner    # 正確
        desc OWNER    # 錯誤,表不存在
    這種狀況下,由於不涉及字符串的 "" 引號操做,因此是嚴格區分大小寫的。
            
"而引號裏面" (其實就是涉及字符串)的數據語法,是 不區分 大小寫的。
    舉個例子(由於下面例子都有 "" 字符串操做,因此都 不區分 大小寫):
        SQL:
            查詢例子:
                select * from owner where name='zHang'
                select * from owner where name='ZHANG'
                他們倆查詢的是同一個數據。
            插入例子:
                insert into owner values("zhaNg")
                insert into owner values("zhang")
                他們倆 插入的 也是同一個數據                    
        peewee:
            查詢例子:
                ...where(name="zhang")  
                ...where(name="ZHaNg")
                他們倆查詢的是 同一個數據。
            插入例子:
                ...insert({'name':'Zhang')
                ...insert({'name': 'zhANG')
                他們倆 插入的 也是同一個數據

官檔-條件操做符:http://docs.peewee-orm.com/en...
上邊的鏈接是官檔操做符大全,下面我把部分經常使用摘出來講一下。api

經常使用操做符

與或非:

與:&
    模型類.where( (User.is_active == True) & (User.is_admin == True) )
或:|
    模型類.where( (User.is_admin) | (User.is_superuser) )
非:~
    模型類.where( ~(User.username.contains('admin')) )

我說兩句,方便記憶:
    1. SQL語句中"與或非" 是 "and or not" 語法, 爲啥peewee不遵循?
        答: 由於,"python原語法"也是這三個。。。衝突, 因此 peewee改了。
    2. 看上面的例子, 每一個條件操做符 "兩邊"的代碼 都用 "()"  括起來了

範圍:

# 查詢年齡18到20的數據 (前閉後閉)
for owner in Owner.select().where(Owner.age.between(18,20)): 
    print(owner.age)

包含&不包含:

不包含:not_in  (同下)
不包含:in_

# 將姓名包含 Alice和Tom的記錄找出來
for owner in Owner.select().where(Owner.name.in_(['Alice', 'Tom'])): 
    print(owner.name)

是否爲null:

# True  就表明把全部 name 爲 null 的 記錄都查出來
# False 就表明把全部 name 爲 非null 的 記錄都查出來

for owner in Owner.select().where( Owner.name.is_null(True) ):
    print(owner.name)

以..開頭 & 以..結尾

以..開頭: startswith
以..結尾: endswith

# 把以 ali  開頭的 都查詢出來
for owner in Owner.select().where(Owner.name.startswith('ali')):
    print(owner.name)

模糊查詢:

# 將包含 li 字符串的數據查詢出來
for owner in Owner.select().where(Owner.name.contains('li')):
    print(owner.name)

正則查詢:

這個就有意思了。前面咱們強調過,MySQL帶引號字符串是不區分大小寫的。
而正則功能提供給咱們區分大小寫的API。(這是個特例,只有正則區分大小寫的功能。記住)

例子條件:
    假如咱們有一個數據 name爲 Alice
    
regexp: 嚴格區分大小寫的正則
    # 用的是 regexp,區分大小寫,  條件給的是 al小寫, 因此固然 查不出來,返回空
    for owner in Owner.select().where(Owner.name.regexp('al*')):
        print(owner.name)
iregexp:不區分大小寫的正則
    # 用的是 iregexp, 不區分大小寫。 所以即便 你給 al小寫, 也可以將 Alice查出來。
    for owner in Owner.select().where(Owner.name.iregexp('al*')):
        print(owner.name)

統計記錄數 count

print(MyModel.select().count())

offset & limit

"""跳過前2行,從第2+1行開始,取1條, 其實取出的就是第3行"""
for x in Owner.select().offset(2).limit(1).dicts():
    print(x)

分頁 paginate

"""
    1. paginate 第1個參數爲 第幾頁
    2. paginate 第2個參數爲 一頁幾個數據
    3. paginate會自動根據查詢的全部記錄總數 和 你傳的 兩個 參數來爲你自動分頁
"""
for obj in MyModel.select().paginate(1,3).dicts():  # 第一頁,每頁三個數據
    print(obj)   
    
# peewee提供給咱們分頁就這麼多,想要更多需求,須要咱們本身發散思惟。
# 下面是我本身粗略寫的一個笨拙的分頁。。能夠參考下。。

def page(document_count=None, per_page_size=None, start_page=1):
    page_count = (document_count // per_page_size)  # 整除的頁數(可能有殘頁)
    is_rest = (document_count % per_page_size)  # 總數/每頁數:是否能除盡
    
    # 除盡表明整頁直接返回,除不盡有殘頁 ,頁碼+1 返回
    page_count = page_count if not is_rest else page_count + 1  
    for page in range(start_page, page_count + 1):
        for obj in MyModel.select().paginate(page, per_page_size).dicts().iterator():
            yield obj    
            
document_count = MyModel.select().count()    # 先獲取記錄總數
for obj in page(document_count=document_count, per_page_size=3, start_page=1):
    print(obj)
# 若是你有需求分頁切片或索引, 那麼你能夠封裝成類,而後實現 __getitem__ 方法

document_count = MyModel.select().count()
for obj in page(document_count=document_count, per_page_size=3, start_page=1):

print(obj)

排序 order_by

# 默認升序 asc()
for owner in Owner.select().order_by(Owner.age):
    print(owner.age)

# 降序 desc()
for owner in Owner.select().order_by(Owner.age.desc()):
    print(owner.age)

分組 group_by

# 用姓名分組,統計人頭數大於1的全部記錄,降序查詢  
query = Owner.select(Owner.name, fn.count(Owner.name).alias('total_num')) \
    .group_by(Owner.name) \
    .having(fn.count(Owner.name) > 1) \
    .order_by(SQL('total_num').desc())
    
for owner in query:
    print(f'名字爲{owner.name}的 人數爲{owner.total_num}個')

分組注意事項,說幾點:
    1. 分組操做,和SQL的group by同樣, group by後面寫了什麼字段, 前面select同時也必須包含
    2. .alias('統計結果字段名'),是給統計後的結果起一個新字段名。 
    3. SQL('total_num') 的做用是給臨時命名的查詢字符串,看成臨時字段使用,支持,desc()等API
    4. peewee的API是高仿SQL寫的,方便使用者。所以咱們最好同步SQL的語法規範,按以下順序:
         where > group_by > having > order_by

聚合原理

一會講peewee的fn聚合原理會涉及到 __getattr__(),若是你不瞭解,能夠看下我以前寫過的文章。
https://segmentfault.com/a/11...

聚合原理以下:   (以上面分組的 fn.count() 爲例)
    fn是我事先導入進來的(開篇我就說過   from peewee import * )就導入了一切(建議練習使用)
    fn可使用聚合操做,我看了一下源碼:講解下思路(不必定特別正確):
        fn是 Function類實例的出的對象
        Function() 定義了 __getattr__方法,(__getattr__開頭我已經給連接了,不懂的能夠傳送)
        
    當你使用 fn.xx() :
        xx 就會被看成字符串傳到 __getattr__ ,
        __getattr__裏面用裝飾器模式,將你 xx 這個字符串。
            通過一系列操做,映射爲同名的SQL語句 (這系列操做包括大小寫轉換等)
            因此你用  fn.count 和 fn.CoUNt 是同樣的
        說到底 fn.xx() ,  的意思就是 fn 把 xx 看成字符串映射到SQL語句,能映射到就能執行

經常使用fn聚合函數

fn.count()
    統計總人頭數:
        for owner in Owner.select(fn.count(Owner.name).alias('total_num')):
            print(owner.total_num)
fn.lower() / fn.upper()
    名字轉小寫/大寫(注意是臨時轉,並無真的轉),並查詢出來:
        for owner in Owner.select(fn.Upper(Owner.name).alias('lower_name')):
            print(owner.lower_name)
fn.sum()
    年齡求和:
        for owner in Owner.select(fn.sum(Owner.age).alias('sum_age')):
            print(owner.sum_age)
fn.avg()
    求平均年齡:
        for owner in Owner.select(fn.avg(Owner.age).alias('avg_age')):
            print(owner.avg_age)
fn.min() / fn.max()
    找出最小/最大年齡:
        for owner in Owner.select(fn.max(Owner.age).alias('max_age')):
            print(owner.max_age)
fn.rand()    
    一般用於亂序查詢 (默認是升序的哦): 
        for owner in  Owner.select().order_by()
            print(owner.name)

關聯查詢前提數據準備

from peewee import *

mysql_db = MySQLDatabase('你的數據庫名', user='你的用戶名', password='你的密碼',
                         host='你的IP', port=3306, charset='utf8mb4')
class BaseModel(Model):
    class Meta:
        database = mysql_db

class Teacher(BaseModel):
    teacher_name = CharField()

class Student(BaseModel):
    student_name = CharField()
    teacher = ForeignKeyField(Teacher, backref='student')

class Course(BaseModel):
    course_name = CharField()
    teacher = ForeignKeyField(Teacher, backref='course')
    student = ForeignKeyField(Student, backref='course')

mysql_db.create_tables([Teacher, Student, Course])
data = (
    ('Tom', ('stu1', 'stu2'), ('Chinese',)),
    ('Jerry', ('stu3', 'stu4'), ('English',)),
)

for teacher_name, stu_obj, course_obj in data:
    teacher = Teacher.create(teacher_name=teacher_name)
    for student_name in stu_obj:
        student = Student.create(student_name=student_name, teacher=teacher)
        for course_name in course_obj:
            Course.create(teacher=teacher, student=student, course_name=course_name)

關聯查詢

方式1:join (鏈接順序 Teacer -> Student , Student -> Course)

# 注意: 你不用寫 on ,由於peewee會自動幫你配對
query = Teacher.select(Teacher.teacher_name, Student.student_name, Course.course_name) \
    .join(Student, JOIN.LEFT_OUTER). \       #  Teacer -> Student
    join(Course, JOIN.LEFT_OUTER) \          #  Student -> Course
    .dicts()
for obj in query:
    print(f"教師:{obj['teacher_name']},學生:{obj['student_name']},課程:{obj['course_name']}")

方式2:switch (鏈接順序 Teacer -> Student , Teacher -> Course)

# 說明,我給的數據例子,可能並不適用這種方式的語義,只是單純拋出語法。
query = Teacher.select(Teacher.teacher_name, Student.student_name, Course.course_name) \
    .join(Student) \                    # Teacher -> Student
    .switch(Student) \                  # 注意這裏,把join上下文權力還給了 Teacher
    .join(Course, JOIN.LEFT_OUTER) \    # Teacher -> Course
    .dicts()
for obj in query:
    print(f"教師:{obj['teacher_name']},學生:{obj['student_name']},課程:{obj['course_name']}")

方式3:join_from(和方式2是同樣的效果,只不過語法書寫有些變化)

query = Teacher.select(Teacher.teacher_name, Student.student_name, Course.course_name) \
    .join_from(Teacher, Student) \                    # 注意這裏,直接指明鏈接首尾對象
    .join_from(Teacher, Course, JOIN.LEFT_OUTER) \    # 注意這裏,直接指明鏈接首尾對象
    .dicts()
for obj in query:
    print(f"教師:{obj['teacher_name']},學生:{obj['student_name']},課程:{obj['course_name']}")

方式4:關聯子查詢
(說明:關聯子查詢的意思就是:以前咱們join的是個表,而如今join後面不是表,而是子查詢。)
SQL版本以下:

SELECT `t1`.`id`, `t1`.`student_name`, `t1`.`teacher_id`, `t2`.`stu_count` 
FROM `student` AS `t1` 
INNER JOIN (
    SELECT `t1`.`teacher_id` AS `new_teacher`, count(`t1`.`student_name`) AS `stu_count` 
    FROM `student` AS `t1` GROUP BY `t1`.`teacher_id`
) AS `t2` 
ON (`t2`.`new_teacher` = `t1`.`teacher_id`

peewee版本以下:

# 子查詢(以學生的老師外鍵分組,統計每一個老師的學生個數)
temp_query = Student.select(
    Student.teacher.alias('new_teacher'),             # 記住這個更名
    fn.count(Student.student_name).alias('stu_count') # 統計學生,記住別名,照應下面.c語法
).group_by(Student.teacher)    # 以學生表中的老師外鍵分組
# 主查詢
query = Student.select(
    Student,                 # select 傳整個類表明,查詢
    temp_query.c.stu_count   # 指定查詢字段爲 子查詢的字段, 因此須要用 .c 語法來指定
).join(
    temp_query,              # 關聯 子查詢
    on=(temp_query.c.new_teacher == Student.teacher) # 關聯條件
).dicts()

for obj in query:
    print(obj)

方式5: 無外鍵關聯查詢 (無外鍵也能夠join哦,本身指定on就好了)
從新創建一個無外鍵的表,並插入數據

class Teacher1(BaseModel):
    teacher_name = CharField()

class Student1(BaseModel):
    student_name = CharField()
    teacher_id = IntegerField()
    
mysql_db.create_tables([Teacher1, Student1])
data = (
    ('Tom', ('zhang1', 1)),
    ('Jerry', ('zhang2', 2)),
)
for teacher_name, student_obj in data:
    Teacher1.create(teacher_name=teacher_name)
    student_name, teacher_id = student_obj
    Student1.create(student_name=student_name, teacher_id=teacher_id)

如今咱們實現無外鍵關聯查詢:

"""查詢學生 對應老師 的姓名"""
query = Student1.select(
    Student1,     # 上面其實已經講過了,select裏面傳某字段就查某字段,傳類就查全部字段
    Teacher1      # 由於後面是join了,但peewee默認是不列出 Teacher1這張外表的。
                  # 因此須要手動指定Teacher1 (若是咱們想查Teacher1表信息,這個必須指定)
).join(
    Teacher1,     # 雖然無外鍵關聯,可是依舊是能夠join的(原生SQL也如此的)
    on=(Student1.teacher_id==Teacher1.id)  #  這個 on必須手動指定了
                  # 強調一下,有外鍵的時候,peewee會自動爲咱們作on操做,因此咱們不須要指定
                  # 可是,這個是無外鍵關聯的狀況,因此必須手動指定on,  否則找不着
).dicts()
for obj in query:
    print(obj)

方式6: 自關聯查詢

# 新定義個表
class Category(Model):
    name = CharField()
    parent = ForeignKeyField('self', backref='children')  
    # 注意一下,外鍵引用這裏寫的是 "self" ,這是是固定字符串哦 ;backref是反向引用,說過了。
# 建立表
mysql_db.create_tables([Category])

# 插入數據
data = ("son", ("father", ("grandfather", None)))
def insert_self(data):
    if data[1]:
        parent = insert_self(data[1])
        return Category.create(name=data[0], parent=parent)
    return Category.create(name=data[0])
insert_self(data)    # 這是我本身定義的一個遞歸插入的方式。。可能有點low

# 可能有點繞,我把插入結果直接貼出來吧
mysql> select * from category;
    +----+-------------+-----------+
    | id | name        | parent_id |
    +----+-------------+-----------+
    |  1 | grandfather |      NULL |
    |  2 | father      |         1 |
    |  3 | son         |         2 |
    +----+-------------+-----------+

# 開始查詢
Parent = Category.alias()   # 這是表的(臨時查詢)更名操做。 接受參數 Parent 即爲表名
                            # 由於自關聯嘛,本身和本身,複製一份(更名就至關於臨時自我拷貝)
query = Category.select(
    Category,
    Parent
).join(
    Parent,
    join_type=JOIN.LEFT_OUTER,    # 由於頂部類爲空,而且默認鏈接方式爲 inner
                                  # 因此最頂端的數據(grandfather)是查不到的
                                  # 因此查全部數據須要用 ==> 左鏈接
    # on=(Parent.id == Category.parent)    # 官檔說 on 須要指定,但我試了,不寫也能關聯上
).dicts()

至此,關聯查詢操做介紹結束!
接下來對以上六種所有方式的作一些強調和說明:

你能夠看見我以前六種方式都是用的dicts(),返回的是類字典格式。(此方式的字段名符合SQL規範)

固然你也能夠以類對象的格式返回,(這種方式麻煩一點,我推薦仍是用 dicts() )
若是想返回類對象,見以下代碼(下面這種方式多了點東西):
query = Teacher.select(Teacher.teacher_name, Student.student_name, Course.course_name) \
    .join_from(Teacher, Student) \
    .join_from(Teacher, Course, JOIN.LEFT_OUTER)  #  注意,我沒有用dicts()
    
for obj in query:
    print(obj.teacher_name)         # 這行應該沒問題吧。自己Teacher就有teacher_name字段
    # 注意了,按SQL原理來講,既然已經作了join查詢,那麼查詢結果就應該直接具備全部表的字段的
    # 按理說 的確是這樣,可是peewee,須要咱們先指定多表的表名,在跟寫多表的字段,正確寫法以下
    print(obj.student.student_name)  # 而不是 obj.student_name直接調用
    print(obj.course.course_name)    # 而不是 obj.course_name直接調用 
    
# 先埋個點, 若是你看到下面的 N+1查詢問題的實例代碼和這個有點像。
# 但我直接說了, 這個是用了預先join()的, 因此涉及到外表查詢後,不會觸發額外的外表查詢
# 天然也不會出現N+1的狀況。 
# 但若是你沒有用join,但查詢中涉及了外表,那麼就會觸發額外的外表查詢,就會出現N+1的狀況。

關聯N+1查詢問題:

什麼是N+1 query? 看下面例子:

# 數據沒有什麼特殊的,假設, 老師 和 學生的關係是一對多(注意,咱們用了外鍵)。
class Teacher(BaseModel):
    teacher_name = CharField()

class Student(BaseModel):
    student_name = CharField()
    teacher_id = ForeignKeyField(Teacher, backref='student')

# 查詢
teachers = Teacher.select()            # 這是 1 次, 查出N個數據
for teacher_obj in teachers:
    for student in teacher_obj.student:  # 這是 N 次循環(N表明查詢的數據)
        print(student.student_name)    
        # 每涉及一個外表屬性,都須要對外表進行額外的查詢, 額外N次
# 因此你能夠看到, 咱們總共查詢 1+N次,  這就是 N+1 查詢。 
# (其實咱們先作個 錶鏈接,查詢一次就可解決問題了。。  這 N+1這種方式 屬實弟弟)
# 下面咱們介紹2種避免 N+1 的方式

peewee解決N+1問題有兩種方式:
方式1:(join)

用 join 先鏈接好,再查詢(前面說了6種方式的join,總有一種符合你需求的)
由於 peewee是支持用戶顯示調用join語法的, 因此 join是個 特別好的解決 N+1 的問題

方式2: (peewee的prefetch)

# 固然,除了 join,你也可使用peewee提供的下面這種方式
# 乍眼一看,你會發現和咱們上面寫的 n+1 查詢方式的例子差很少,不同,你仔細看看
teacher = Teacher.select()    # 先預先把 主表 查出來
student = Student.select()    # 先預先把 從表 查出來
teacher_and_student = prefetch(teacher, student)    # 使用 prefetch方法  (關鍵)
for teacher in teacher_and_student:    # 下面就和N+1同樣了
    print(teacher.teacher_name)
    for student in teacher.student:
        print(student.student_name)
說明:
    0. prefetch, 原理是,將有外鍵關係的主從表,隱式"一次性"取出來。"須要時"按需分配便可。
    1. 使用prefetch先要把,有外鍵關聯的主從表查出來(注意,"必須必需要有外鍵,否則很差使")
    2. prefetch(主表,從表)    # 傳進去就行,peewee會自動幫咱們根據外鍵找關係
    3. 而後正常 之外鍵字段 爲橋樑 查其餘表的信息便可
    4. (
            題外話,djnago也有相似的prefetch功能,(反正都是避免n+1,優化ORM查詢) 
            貌似給外鍵字段 設置select_related() 和 prefetch_related()  屬性
        )

未結束語

本篇主要講了,CRUD, 特別是針對查詢作了大篇幅說明。
我還會有下一篇來介紹peewee的擴展功能。
上一篇傳送門:https://segmentfault.com/a/11...
下一篇傳送門:https://segmentfault.com/a/11...

相關文章
相關標籤/搜索