亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

經(jīng)驗拾憶(純手工)=> Python-ORM之peewee:CRUD完整解析

系統(tǒng) 1722 0

聲明

上篇地址:https://segmentfault.com/a/11...
雖然上一篇,已經(jīng)說明,但還是強調(diào)一下,peewee是 python-ORM(只支持 MySQL,Sqlite,postgresql )
雖然ORM可以與多種數(shù)據(jù)庫無縫相接,并且兼容性好, 但是某些細(xì)微的語法并不是數(shù)據(jù)庫共有的。
我用MySQL, 所以下面說的都是基于MySQL(其他2種數(shù)據(jù)庫也差不了多少, 99%是一樣的)
總官檔地址:http://docs.peewee-orm.com/en...
官方Github地址:https://github.com/coleifer/p...

增加數(shù)據(jù)

          
            方式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, 返回結(jié)果都是模型實例"(也就意味著創(chuàng)建了一個實例)"
    # 而本方式,返回結(jié)果是 最新插入的主鍵值"(也就意味著不會創(chuàng)建實例)"

          
        

如果存在外鍵關(guān)聯(lián),假如存在 Pet類 引用的 Owner的主鍵,插入數(shù)據(jù)方式有2種:

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

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

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

          
        

如果數(shù)據(jù)量過大,可能會出現(xiàn)OOM等問題。你可以手動分批,但是 peewee 給我們提供了成品的 api

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

防止數(shù)據(jù)重復(fù)插入的2種辦法(或者防止設(shè)置了主鍵,重復(fù)插入拋出異常,導(dǎo)致程序無法運行):

          
            方法1: INGORE關(guān)鍵字  (這種方式是如果沖突了,就自動忽略)
    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;             # 如果沖突了,可以重新設(shè)置值           
    peewee:
        Owner.insert(name='lin', age=30).on_conflict(
            preserve=[Owner.name, Owner.age],          # 若沖突,你想保留不變的字段
            update={Owner.name: 'lin', Owner.age: 30}  # 若沖突,你想更新什么
        ).execute()
        # 注: preserve 和 update 按情況用,一般設(shè)置一個用就行了。
          
        

刪除數(shù)據(jù)

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

修改數(shù)據(jù)

          
            方式1:(不推薦)
    owner= 查詢單個對象結(jié)果
    owner.name = 'Pack'
    owner.name = 50
    owner.save()        # 你可以看見,我們還需要手動調(diào)用一下save()
    
方式2:(推薦)
    query = Owner.update(name='Pack', age=50).where(Owner.name == 'Zhang')
    query.execute()
          
        

查詢數(shù)據(jù)

查詢單條數(shù)據(jù) (特別注意,如果你有多條,它只會給你返回第一條)

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

擴展版1: get_or_create
    """存在則返回原有對象。不存在則插入數(shù)據(jù),并返回新對象"""
    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')
          
        

查詢多條數(shù)據(jù)

正常查詢所有數(shù)據(jù)

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

當(dāng)然你可以在查詢后轉(zhuǎn)為 python 類dict格式:

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

上面的查詢?nèi)绻跀?shù)據(jù)大量的情況下可能會導(dǎo)致OOM,因此可轉(zhuǎn)為迭代:

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

條件查詢:

首先我先強調(diào)個,"MySQL是否區(qū)分大小寫" 的事:

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

          
        

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

常用操作符

與或非:

          
            與:&
    模型類.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的數(shù)據(jù) (前閉后閉)
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)

          
        

以..開頭 & 以..結(jié)尾

          
            以..開頭: startswith
以..結(jié)尾: endswith

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

模糊查詢:

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

正則查詢:

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

例子條件:
    假如我們有一個數(shù)據(jù) name為 Alice
    
regexp: 嚴(yán)格區(qū)分大小寫的正則
    # 用的是 regexp,區(qū)分大小寫,  條件給的是 al小寫, 所以當(dāng)然 查不出來,返回空
    for owner in Owner.select().where(Owner.name.regexp('al*')):
        print(owner.name)
iregexp:不區(qū)分大小寫的正則
    # 用的是 iregexp, 不區(qū)分大小寫。 因此即使 你給 al小寫, 也能夠?qū)?Alice查出來。
    for owner in Owner.select().where(Owner.name.iregexp('al*')):
        print(owner.name)
          
        

統(tǒng)計記錄數(shù) 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個參數(shù)為 第幾頁
    2. paginate 第2個參數(shù)為 一頁幾個數(shù)據(jù)
    3. paginate會自動根據(jù)查詢的所有記錄總數(shù) 和 你傳的 兩個 參數(shù)來為你自動分頁
"""
for obj in MyModel.select().paginate(1,3).dicts():  # 第一頁,每頁三個數(shù)據(jù)
    print(obj)   
    
# peewee提供給我們分頁就這么多,想要更多需求,需要我們自己發(fā)散思維。
# 下面是我自己粗略寫的一個笨拙的分頁。。可以參考下。。

def page(document_count=None, per_page_size=None, start_page=1):
    page_count = (document_count // per_page_size)  # 整除的頁數(shù)(可能有殘頁)
    is_rest = (document_count % per_page_size)  # 總數(shù)/每頁數(shù):是否能除盡
    
    # 除盡代表整頁直接返回,除不盡有殘頁 ,頁碼+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()    # 先獲取記錄總數(shù)
for obj in page(document_count=document_count, per_page_size=3, start_page=1):
    print(obj)
# 如果你有需求分頁切片或索引, 那么你可以封裝成類,然后實現(xiàn) __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

          
            # 默認(rèn)升序 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

          
            # 用姓名分組,統(tǒng)計人頭數(shù)大于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}的 人數(shù)為{owner.total_num}個')

分組注意事項,說幾點:
    1. 分組操作,和SQL的group by一樣, group by后面寫了什么字段, 前面select同時也必須包含
    2. .alias('統(tǒng)計結(jié)果字段名'),是給統(tǒng)計后的結(jié)果起一個新字段名。 
    3. SQL('total_num') 的作用是給臨時命名的查詢字符串,當(dāng)作臨時字段使用,支持,desc()等API
    4. peewee的API是高仿SQL寫的,方便使用者。因此我們最好同步SQL的語法規(guī)范,按如下順序:
         where > group_by > having > order_by
          
        

聚合原理

一會講peewee的fn聚合原理會涉及到 __getattr__(),如果你不了解,可以看下我之前寫過的文章。
https://segmentfault.com/a/11...

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

常用fn聚合函數(shù)

          
            fn.count()
    統(tǒng)計總?cè)祟^數(shù):
        for owner in Owner.select(fn.count(Owner.name).alias('total_num')):
            print(owner.total_num)
fn.lower() / fn.upper()
    名字轉(zhuǎn)小寫/大寫(注意是臨時轉(zhuǎn),并沒有真的轉(zhuǎn)),并查詢出來:
        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()    
    通常用于亂序查詢 (默認(rèn)是升序的哦): 
        for owner in  Owner.select().order_by()
            print(owner.name)   
          
        

關(guān)聯(lián)查詢前提數(shù)據(jù)準(zhǔn)備

          
            from peewee import *

mysql_db = MySQLDatabase('你的數(shù)據(jù)庫名', 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)
          
        

關(guān)聯(lián)查詢

方式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']},學(xué)生:{obj['student_name']},課程:{obj['course_name']}")
          
        

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

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

方式4:關(guān)聯(lián)子查詢
(說明:關(guān)聯(lián)子查詢的意思就是:之前我們join的是個表,而現(xiàn)在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版本如下:

          
            # 子查詢(以學(xué)生的老師外鍵分組,統(tǒng)計每個老師的學(xué)生個數(shù))
temp_query = Student.select(
    Student.teacher.alias('new_teacher'),             # 記住這個改名
    fn.count(Student.student_name).alias('stu_count') # 統(tǒng)計學(xué)生,記住別名,照應(yīng)下面.c語法
).group_by(Student.teacher)    # 以學(xué)生表中的老師外鍵分組
# 主查詢
query = Student.select(
    Student,                 # select 傳整個類代表,查詢
    temp_query.c.stu_count   # 指定查詢字段為 子查詢的字段, 所以需要用 .c 語法來指定
).join(
    temp_query,              # 關(guān)聯(lián) 子查詢
    on=(temp_query.c.new_teacher == Student.teacher) # 關(guān)聯(lián)條件
).dicts()

for obj in query:
    print(obj)
          
        

方式5: 無外鍵關(guān)聯(lián)查詢 (無外鍵也可以join哦,自己指定on就行了)
重新建立一個無外鍵的表,并插入數(shù)據(jù)

          
            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)
          
        

現(xiàn)在我們實現(xiàn)無外鍵關(guān)聯(lián)查詢:

          
            """查詢學(xué)生 對應(yīng)老師 的姓名"""
query = Student1.select(
    Student1,     # 上面其實已經(jīng)講過了,select里面?zhèn)髂匙侄尉筒槟匙侄危瑐黝惥筒樗凶侄?    Teacher1      # 因為后面是join了,但peewee默認(rèn)是不列出 Teacher1這張外表的。
                  # 所以需要手動指定Teacher1 (如果我們想查Teacher1表信息,這個必須指定)
).join(
    Teacher1,     # 雖然無外鍵關(guān)聯(lián),但是依舊是可以join的(原生SQL也如此的)
    on=(Student1.teacher_id==Teacher1.id)  #  這個 on必須手動指定了
                  # 強調(diào)一下,有外鍵的時候,peewee會自動為我們做on操作,所以我們不需要指定
                  # 但是,這個是無外鍵關(guān)聯(lián)的情況,所以必須手動指定on,  不然找不著
).dicts()
for obj in query:
    print(obj)    
          
        

方式6: 自關(guān)聯(lián)查詢

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

# 插入數(shù)據(jù)
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

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

# 開始查詢
Parent = Category.alias()   # 這是表的(臨時查詢)改名操作。 接受參數(shù) Parent 即為表名
                            # 因為自關(guān)聯(lián)嘛,自己和自己,復(fù)制一份(改名就相當(dāng)于臨時自我拷貝)
query = Category.select(
    Category,
    Parent
).join(
    Parent,
    join_type=JOIN.LEFT_OUTER,    # 因為頂部類為空,并且默認(rèn)連接方式為 inner
                                  # 所以最頂端的數(shù)據(jù)(grandfather)是查不到的
                                  # 所以查所有數(shù)據(jù)需要用 ==> 左連接
    # on=(Parent.id == Category.parent)    # 官檔說 on 需要指定,但我試了,不寫也能關(guān)聯(lián)上
).dicts()

          
        

至此,關(guān)聯(lián)查詢操作介紹結(jié)束!
接下來對以上六種全部方式的做一些強調(diào)和說明:

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

當(dāng)然你也可以以類對象的格式返回,(這種方式麻煩一點,我推薦還是用 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)         # 這行應(yīng)該沒問題吧。本身Teacher就有teacher_name字段
    # 注意了,按SQL原理來說,既然已經(jīng)做了join查詢,那么查詢結(jié)果就應(yīng)該直接具有所有表的字段的
    # 按理說 的確是這樣,但是peewee,需要我們先指定多表的表名,在跟寫多表的字段,正確寫法如下
    print(obj.student.student_name)  # 而不是 obj.student_name直接調(diào)用
    print(obj.course.course_name)    # 而不是 obj.course_name直接調(diào)用 
    
# 先埋個點, 如果你看到下面的 N+1查詢問題的實例代碼和這個有點像。
# 但我直接說了, 這個是用了預(yù)先join()的, 所以涉及到外表查詢后,不會觸發(fā)額外的外表查詢
# 自然也不會出現(xiàn)N+1的情況。 
# 但如果你沒有用join,但查詢中涉及了外表,那么就會觸發(fā)額外的外表查詢,就會出現(xiàn)N+1的情況。

          
        

關(guān)聯(lián)N+1查詢問題:

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

          
            # 數(shù)據(jù)沒有什么特殊的,假設(shè), 老師 和 學(xué)生的關(guān)系是一對多(注意,我們用了外鍵)。
class Teacher(BaseModel):
    teacher_name = CharField()

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

# 查詢
teachers = Teacher.select()            # 這是 1 次, 查出N個數(shù)據(jù)
for teacher_obj in teachers:
    for student in teacher_obj.student:  # 這是 N 次循環(huán)(N代表查詢的數(shù)據(jù))
        print(student.student_name)    
        # 每涉及一個外表屬性,都需要對外表進(jìn)行額外的查詢, 額外N次
# 所以你可以看到, 我們總共查詢 1+N次,  這就是 N+1 查詢。 
# (其實我們先做個 表連接,查詢一次就可解決問題了。。  這 N+1這種方式 屬實弟弟)
# 下面我們介紹2種避免 N+1 的方式
          
        

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

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

方式2: (peewee的prefetch)

          
            # 當(dāng)然,除了 join,你也可以使用peewee提供的下面這種方式
# 乍眼一看,你會發(fā)現(xiàn)和我們上面寫的 n+1 查詢方式的例子差不多,不一樣,你仔細(xì)看看
teacher = Teacher.select()    # 先預(yù)先把 主表 查出來
student = Student.select()    # 先預(yù)先把 從表 查出來
teacher_and_student = prefetch(teacher, student)    # 使用 prefetch方法  (關(guān)鍵)
for teacher in teacher_and_student:    # 下面就和N+1一樣了
    print(teacher.teacher_name)
    for student in teacher.student:
        print(student.student_name)
說明:
    0. prefetch, 原理是,將有外鍵關(guān)系的主從表,隱式"一次性"取出來。"需要時"按需分配即可。
    1. 使用prefetch先要把,有外鍵關(guān)聯(lián)的主從表查出來(注意,"必須必須要有外鍵,不然不好使")
    2. prefetch(主表,從表)    # 傳進(jìn)去就行,peewee會自動幫我們根據(jù)外鍵找關(guān)系
    3. 然后正常 以外鍵字段 為橋梁 查其他表的信息即可
    4. (
            題外話,djnago也有類似的prefetch功能,(反正都是避免n+1,優(yōu)化ORM查詢) 
            貌似給外鍵字段 設(shè)置select_related() 和 prefetch_related()  屬性
        )

          
        

未結(jié)束語

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


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 97成人精品视频在线播放 | 免费观看日本污污ww网站精选 | 91xav在线| 久久www免费人成高清 | 欧美日韩亚洲区久久综合 | 在线看一区二区 | 精品国产品香蕉在线观看75 | 另类亚洲视频 | 毛片爱做的片 | 一区二区三区网站在线免费线观看 | 久久国产免费一区二区三区 | 国产欧美综合一区二区 | 97精品高清一区二区三区 | 青青久久久 | 久久免费毛片 | 欧美日韩国产最新一区二区 | 4虎最新网址 | 7777奇米影视 | 国产日本欧美亚洲精品视 | 久草久草| 神马不卡 | 久久一日本道色综合久久m 久久一色本道亚洲 | 99r精品| 久草久草久草 | 每日更新国产精品视频 | 国产精品日韩欧美一区二区三区 | 国产精品毛片va一区二区三区 | 日日爽夜夜 | 久草在线这里只有精品 | 日本一区二区三区免费在线观看 | aaaa日本| 久久久精品免费 | 免费看一级黄色毛片 | 欧美日本在线视频 | 国内精品久久久久久 | 成人黄色网| 久久天天躁狠狠躁夜夜爽 | 亚洲精品国产一区二区三区在 | 欧美成人在线观看 | 日韩精品一区二区三区中文3d | 黄色免费在线观看 |