Django 模型層

Django ORM

   Django模型層的功能就是與數據庫打交道,其中最主要的框架為ORM

   ORM為對象關係映射,說白了就是將Python中的類映射成數據表,將表中每一條記錄映射成類的實例對象,將對象屬性映射成表中的字段。

   如果用原生的SQL語句結合pymysql來對數據庫進行操作無疑是非常繁瑣的,但是Django提供了非常強大的ORM框架來對數據庫進行操作,在增刪改查方面都有非常大的提升,學會使用ORM十分的必要。

   注意:儘管ORM十分方便,但是也請不要過分依賴它從而忘記原生SQL命令。

   ORM作為Django中最難的一章基礎知識點,應該是很多初學者的第一道門檻。

   那麼在學習ORM之前,我想寫一些我的心得體會,ORM的操作很方便,但是有些設計比較反人類,你可以使用原生SQL進行代替。

   條條大路通羅馬,不一定非要在一棵樹上弔死。

準備工作

原生語句

   如果想在操作ORM時還能看到原生的SQL語句,請在settings.py中添加上以下代碼

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
}

測試腳本

   Django中允許對py文件做單獨的測試,而不用啟動Django項目,這是非常方便的。

   注意:所有測試中的代碼都必須在if "__name__" == "__main__":下進行,這意味着你的from xx import xx不能放在頂行

  

from django.test import TestCase

# Create your tests here.
import os
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))  # 如果在pycharm中,這兩句可以省略

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project01.settings")

    import django
    django.setup()

    # 測試代碼均在下面進行
    from app01 import models

鏈接MYSQL

   Django中默認使用的數據庫是sqlit3,所以我們需要在配置文件中對其實行修改。

   大體分為兩個步驟

   1.修改默認鏈接為MySQL

   2.鏈接聲明,即聲明鏈接MySQL的模塊為pymysql(默認是MySQLdb

修改鏈接

   打開項目全局文件夾下的settings.py,找到以下代碼進行注釋

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',  # 默認鏈接sqlite3
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

   現在我們就要進行手動配置了,參照如下代碼

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',  # 默認鏈接sqlite3
        'NAME': 'db1', 			# 你的數據庫名稱
        'USER': 'root', 		# 你的登錄用戶名稱
        'PASSWORD': '123',		# 你的登錄密碼,如果沒有留空即可
        'HOST':	'localhost', 	# 鏈接地址
        'PORT': '3306',			# MySQL服務端端口號
        'CHARSET': 'utf8mb4',	# 默認字符編碼
    }
}

   現在,你的Django會拋出一個異常,不管他,直接進入下一個步驟

django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module: No module named 'MySQLdb'.
Did you install mysqlclient or MySQL-python?

鏈接聲明

   由於我們要將默認鏈接MySQL的模塊從MySQLdb修改為pymysql,所以你要先安裝pymysql模塊

pip install pymysql

   安裝完成後打開項目全局文件夾下的__init__(實際上任意APP下的__init__都可以),添加代碼如下:

import pymysql
pymysql.install_as_MySQLdb()

   現在我們的Django就可以使用MySQL進行連接了。

數據庫操作

   ORM為對象關係映射,類就相當於數據表,屬性就相當於字段。

   因此我們有兩條很常見的命令用於操作:

python manage.py makemigrations  # 創建模型映射表
python manage.py migrate # 同步模型表至數據庫中

   這兩條命令在對數據庫字段、數據表結構進行修改時都需要重新進行。

單表創建

   在項目的APP中,打開models.py,開始創建表(該文件下可以建立一個表,也可以建立多個表)。

   注意:ORM創建表時如果不指定主鍵字段,將會默認創建一個名為id的主鍵字段。並且我們在使用時可以使用pk來代指主鍵

from django.db import models

class User(models.Model): # 必須繼承
    # 自動創建 id 的主鍵
    username = models.CharField(max_length=16,verbose_name="用戶名",db_index=True)
    age = models.IntegerField()
    gender = models.BooleanField(choices=([0,"male"],[1,"famale"]),default=0)

   接下來要運行創建模型映射表的命令,以及運行數據庫同步命令。

python manage.py makemigrations  # 創建模型映射表
python manage.py migrate # 同步模型表至數據庫中

   當創建模型映射表命令運行完成後,會發現APP下的migrations文件夾中多一一個文件,文件中就存放的剛剛建立好的模型表。

   當數據庫同步命令執行完成後,MySQL中才會真正的創建出一張真實的物理表。

   模型表信息:

# project01項目/app01/migrations

# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2020-09-12 18:18
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='User',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('username', models.CharField(db_index=True, max_length=16, verbose_name='用戶名')),
                ('age', models.IntegerField()),
                ('gender', models.BooleanField(choices=[[0, 'male'], [1, 'famale']], default=0)),
            ],
        ),
    ]

   物理表(注意:物理表的命名會以APP名稱開始):

desc app01_user;

+----------+-------------+------+-----+---------+----------------+
| Field    | Type        | Null | Key | Default | Extra          |
+----------+-------------+------+-----+---------+----------------+
| id       | int(11)     | NO   | PRI | NULL    | auto_increment |
| username | varchar(16) | NO   | MUL | NULL    |                |
| age      | int(11)     | NO   |     | NULL    |                |
| gender   | tinyint(1)  | NO   |     | NULL    |                |
+----------+-------------+------+-----+---------+----------------+

字段操作

   所有關於字段的增刪改查都直接操縱對象屬性即可,這裡要提一下增加字段。

   如果需要新增一個字段,而數據表中又有一些數據,此時Django會提醒你為原本的老數據設置一個默認值。

   它會提供給你2個選項,選項1:立即為所有老數據設置默認值。選項2:放棄本次新增字段的操作,重新新增字段並設置默認值(也可以設置null=True

   示例如下:我們現在表中創建一條信息。

from app01 import models

res = models.User.objects.create(
	username= "雲崖",
	age=18,
	gender=0,
)

print(res)

   然後修改其字段,新增一個註冊時間。

from django.db import models

class User(models.Model): # 必須繼承
    # 自動創建 id 的主鍵
    username = models.CharField(max_length=16,verbose_name="用戶名",db_index=True)
    age = models.IntegerField()
    gender = models.BooleanField(choices=([0,"male"],[1,"famale"]),default=0)
    register_time = models.DateField(auto_now_add=True)

   在執行修改模型映射表的結構命令時,會讓你做出選擇。此時我們選擇1,並且給定一個默認值就好。

PS D:\project\project01> python manage.py makemigrations
You are trying to add the field 'register_time' with 'auto_now_add=True' to user without a default; the database needs something to populate existing rows.

 1) Provide a one-off default now (will be set on all existing rows)  # 1.新增一個默認值
 2) Quit, and let me add a default in models.py # 2.退出,自己手動添加default參數設置默認值
Select an option: 1
Please enter the default value now, as valid Python
You can accept the default 'timezone.now' by pressing 'Enter' or you can provide another value.
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
[default: timezone.now] >>> "2020-01-28"  # 選擇新增,給定一個時間
Migrations for 'app01':
  app01\migrations\0002_user_register_time.py
    - Add field register_time to user
PS D:\project\project01>

   記得最後運行數據庫同步

python manage.py migrate

返回信息

   其實在創建數據表時,我們都會為其加上__str__方法。

   這是為了方便查詢操作時看到那一條數據。

   如下,如果不加的話返回結果是QuerySet中套上列表+對象,十分不利於查看信息。

from app01 import models

res = models.User.objects.all()

print(res)  # <QuerySet [<User: User object>]>

   如果我們加上__str__方法,那麼就方便我們進行查看,到底查詢的有那些記錄。

   (只要不對模型表本身結構做出修改,都不用重新執行模型表和物理表的兩條命令)

from django.db import models

class User(models.Model): # 必須繼承
    # 自動創建 id 的主鍵
    username = models.CharField(max_length=16,verbose_name="用戶名",db_index=True)
    age = models.IntegerField()
    gender = models.BooleanField(choices=([0,"male"],[1,"famale"]),default=0)
    register_time = models.DateField(auto_now_add=True)

    def __str__(self) -> str:
        return "對象-%s"%self.username 
        # 每一個記錄對象都是User的實例化對象,所以我們返回一下self的username即可

   現在再來進行查詢

from app01 import models

res = models.User.objects.all()

print(res)  # <QuerySet [<User: 對象-雲崖>]>

記錄操作

新增記錄

   新增記錄語法有兩種,均可使用。

models.表名.objects.create(col='xxx', col='xxx')  # 注意,返回值是創建好的對象記錄本身

obj = models.表名(col='xxx', col='xxx')
obj.save()

  

   每次的創建,都會返回一個創建成功的新對象。一個實例對象中包含字段及字段數據,換而言之就是一條記錄。

   示例演示:

from app01 import models

# 方式一:推薦使用
obj1 = models.User.objects.create(
    # 主鍵自動添加,AUTOFILED
    username="及時雨",
    age=21,
    gender=0,
    # register_time 創建時自動添加
)

# 方式二:
obj2 = models.User(
    # 主鍵自動添加,AUTOFILED
    username="玉麒麟",
    age=22,
    gender=0,
    # register_time 創建時自動添加
)

obj2.save()

print(obj1, obj2)  # 對象-及時雨 對象-玉麒麟

修改記錄

   修改記錄有兩種方式,推薦使用第一種,因為第二種修改會將該條記錄的所有字段都進行修改,無論數據是否有更新。

models.表名.objects.filter(col='舊數據').update(col='新數據') 
# filter相當於where,不可以使用get,因為單一對象沒有update方法,只有QuerySet對象才有。

obj = models.表名.objects.get(col='舊數據') # get() 只獲取單個對象
obj.col = '新數據'
obj.save() 

   對於方式1而言,它可能修改多條記錄。所以每次修改後,都會返回一個int類型的數字,這代表受到影響的行數

  

   示例演示:

from app01 import models

# 方式一:推薦使用
num1 = models.User.objects.filter(pk=1).update(username="宋公明")

# 方式二:
obj = models.User.objects.get(pk=2)
obj.username="盧俊義"
obj.save()

print(num1)  # 1

刪除記錄

   語法介紹:

models.表名.objects.filter(col='xxx').delete()
# 刪除指定條件的數據 filter相當於where,不可以使用get,因為單一對象沒有delete方法,只有QuerySet對象才有。

   返回值是一個元組,包含刪除成功的行數。

  

   示例如下

from app01 import models

num_obj = models.User.objects.filter(pk=2).delete()

print(num_obj)  # (1, {'app01.User': 1})

批量增加

   如果要插入的數據量很大,則可以使用bulk_create來進行批量插入。

   它比單純的create效率更高。

book_list = []
for i in range(100000):
	book_obj = models.Book(title="第%s本書"%i)
	book_list.append(book_obj)
models.Book.objects.bulk_create(book_list)

單表查詢

truncate app01_user;

INSERT INTO app01_user(username, age, gender,register_time) VALUES
    ("雲崖",18,0,now()),
    ("及時雨",21,0,now()),
    ("玉麒麟",22,0,now()),
    ("智多星",21,0,now()),
    ("入雲龍",23,0,now()),
    ("大刀",22,0,now());

QuerySET

   在查詢時候,一定要區分開QuerySet對象與單條記錄對象的區別。

   比如:我們的單條記錄對象中保存有字段名等數據,均可進行.出來。它的格式應該是這樣的:obj[col1,col2,col3]

   而QuerySet對象是一個對象集合體,中間包含很多單條記錄對象,它的格式是:QuerySet<[obj1,obj2,obj3]>

   不同的對象有不同的方法,單條記錄對象中能.出字段,而QuerySet對象中能點出filter()/values()等方法。所以一定要區分開。

   QuerySet集合對象:暫時可以理解為一個列表,裏面可以包含很多記錄對象,但是不支持負向的索引取值,並且Django不太希望你使用index進行取值,而應該使用first()以及last()進行取出單條記錄對象。

基本查詢

   語法介紹:

models.表名.objects.all() # 拿到當前模型類映射表中的所有記錄,返回QuerySet集合對象
models.表名.objects.filter(col='xxx')	# 相當於whlie條件過濾,可拿到多條,返回QuerySet集合對象
models.表名.objects.get(col='xxx')	# 返回單個記錄對象。注意區分與QuerySet的區別

   示例演示:(仔細看結果,區分QuerySet與單個對象的區別)

from app01 import models

all_queryset = models.User.objects.all()

filter_queryset = models.User.objects.filter(pk__gt=3)

obj = models.User.objects.get(pk=1)

print(all_queryset)
# <QuerySet [<User: 對象-雲崖>, <User: 對象-及時雨>, <User: 對象-玉麒麟>, <User: 對象-智多星>, <User: 對象-入雲龍>, <User: 對象-大刀>]>
print(filter_queryset)
# <QuerySet [<User: 對象-智多星>, <User: 對象-入雲龍>, <User: 對象-大刀>]>
print(obj)
# 對象-雲崖

filter過濾

   filter()相當於where條件,那麼在其中可以進行過濾操作

   過濾使用__雙下劃線進行操作。

   注意:在filter()中,所有的條件都是AND關係

條件 說明
filter(col1=xxx,col2=xxx) 查詢符合多個條件的記錄
col__lt = xxx 字段數據小於xxx的記錄
col__lte = xxx 字段數據小於等於xxx的記錄
col_gt = xxx 字段數據大於xxx的記錄
col_gte = xxx 字段數據大於xxx的記錄
col_in = [x,y,z] 字段數據在[x,y,z]中的記錄
col_range = [1,5] 字段數據在1-5範圍內的記錄
col_startswith = “xxx” 字段數據以”xxx”開頭的記錄,區分大小寫
col_istartswith = “xxx” 字段數據以”xxx”開頭的記錄,不區分大小寫
col_endswith = “xxx” 字段數據以”xxx”結尾的記錄,區分大小寫
col_iendswith = “xxx” 字段數據以”xxx”結尾的記錄,不區分大小寫
col__contains = 」xxx” 字段數據包含”xxx”的記錄,區分大小寫
col__icontains = 」xxx” 字段數據包含”xxx”的記錄,不區分大小寫
col__year = 2020 日期字段在2020年中的記錄
col__year__lt = 2020 日期字段在2020年之後的記錄
col__date__gte = datetime.date(2017, 1, 1) 日期字段在2017年1月1日之後的記錄
col__month = 3 日期字段在3月的記錄
col__day = 3 日期字段在3天的記錄
col__hour = 3 日期字段在3小時的記錄
col__minute = 3 日期字段在3分鐘的記錄
col__second = 3 日期字段在3秒鐘的記錄
User.objects.filter(pk__lt=2)  # 查找id小於2的對象

User.objects.filter(pk__lte=2) # 查找id小於等於2的對象

User.objects.filter(pk__gt=2)  # 查找id大於2的對象

User.objects.filter(pk__gte=2) # 查找id大於等於2的對象

User.objects.filter(pk__in=[1,2,5]) # 查找id為1,2,5的對象。

User.objects.filter(pk__range=[1,5])  # 查找id為1-5之間的對象。(左右都為閉區間)

User.objects.filter(name__contains="shawn")  # 查找name中包含shawn的對象。類似於正則/%shawn%/

User.objects.filter(name__icontains="shawn")  # 查找name中包含shawn的對象。(忽略大小寫)

User.objects.filter(name__startswith="shawn")  # 查找name以shawn開頭的對象 

User.objects.filter(name__istartswith="shawn")  # 查找name以shawn開頭的對象(忽略大小寫)

User.objects.filter(name__endswith="shawn")  # 查找name以shawn結尾的對象 

User.objects.filter(name__iendswith="shawn")  # 查找name以shawn結尾的對象 (忽略大小寫)

User.objects.exclude(name__contains="shawn")  # 查找name不包含shawn的所有對象。
xx__date類型字段可以根據年月日進行過濾

User.objects.filter(birthday__year=2012)   # 查找出生在2012年的對象

User.objects.filter(birthday__year__gte=2012)  # 查找出生在2012年之後對象

User.objects.filter(birthday__date__gte=datetime.date(2017, 1, 1))  # 查找在2017,1,1之後出生的對象

User.objects.filter(birthday__month=3)  查找出生在3月份的對象

User.objects.filter(birthday__week_day=3) 

User.objects.filter(birthday__day=3)

User.objects.filter(birthday__hour=3)

User.objects.filter(birthday__minute=3)

User.objects.filter(birthday__second=3)

常用操作

   下面將例舉一些常用操作。

單詞 查詢操作 描述
all() models.表名.objects.all() <QuerySet [obj obj obj]>,相當於查詢該表所有記錄。返回值相當於列表套對象
filter() models.表名.objects.filter(col=xxx) <QuerySet [obj obj objJ]>,過濾條件可通過逗號分割,等同於where查詢。返回值相當於列表套對象
get() models.表名.objects.get(col=xxx) obj,單條記錄,即一個類的實例化對象,直接返回對象
以下方法多為配合第一個或第二個方法使用    
values() models.表名.objects.values(“col”) <QerySet [{col,x1},{col,x2}]>,返回記錄中所有字段的值。返回值相當於列表套字典
values_list() models.表名.objects.values_list(“col”) <QerySet [(x1),(x2)]>,返回記錄中所有字段的值。返回值相當於列表套元組
distinct() models.表名.objects.values(“col”).distinct() <QuerySet [{col,x1},{col,x2}]>,對指定字段去重。返回記錄中所有字段的值。返回值相當於列表套字典
order_by() models.類名.objects.order_by(“-col”) <QuerySet [obj obj objJ]>,對指定字段排序,如直接寫col是升序,-col則是降序。
reverse() models.類名.objects.order_by(“-col”).reverse() <QuerySet [obj obj obj]>,前置條件必須有order_by(col),對其進行反轉操作
exclude() models.表名.objects.exclude(col=”xxx”) <QuerySet [obj obj obj]>,除開col=xxx的記錄,其他記錄都拿
count() models.表名.objects.values(“col”).count() int,返回該字段記錄的個數
exists() models.表名.objects.filter(col=「data」).exists() bool,返回該記錄是否存在
first() models.表名.objects.first() obj,單條記錄,取QuerySet中第一條記錄
last() models.表名.objects.last() obj,單條記錄,取QuerySet中最後一條記錄
注意:獲取all()後想配合其他的一些方法,可直接使用models.類型.object.其他方法()。這等同於all()    

   示例演示如下:

、
from app01 import models

values_queryset_dict = models.User.objects.values("username")
print(values_queryset_dict)
# <QuerySet [{'username': '雲崖'}, {'username': '入雲龍'}, {'username': '及時雨'}, {'username': '大刀'}, {'username': '智多星'}, {'username': '玉麒麟'}]>

values_list_queryset_tuple = models.User.objects.values_list("username")
print(values_list_queryset_tuple)
# <QuerySet [('雲崖',), ('入雲龍',), ('及時雨',), ('大刀',), ('智多星',), ('玉麒麟',)]>

distict_queryset = models.User.objects.values("age").distinct()
print(distict_queryset)
# <QuerySet [{'age': 18}, {'age': 21}, {'age': 22}, {'age': 23}]>

order_by_queryset = models.User.objects.order_by("-age")
print(order_by_queryset)
# <QuerySet [<User: 對象-入雲龍>, <User: 對象-玉麒麟>, <User: 對象-大刀>, <User: 對象-及時雨>, <User: 對象-智多星>, <User: 對象-雲崖>]>

reverse_queryset = models.User.objects.order_by("-age").reverse()
print(reverse_queryset)
# <QuerySet [<User: 對象-雲崖>, <User: 對象-及時雨>, <User: 對象-智多星>, <User: 對象-玉麒麟>, <User: 對象-大刀>, <User: 對象-入雲龍>]>

exclude_queryset = models.User.objects.exclude(pk__gt=3)
print(exclude_queryset)
# <QuerySet [<User: 對象-雲崖>, <User: 對象-及時雨>, <User: 對象-玉麒麟>]>

num_pk = models.User.objects.values("pk").count()
print(num_pk)
# 6

bool_res = models.User.objects.filter(username="雲崖").exists()
print(bool_res)
# True

first_obj = models.User.objects.first()
print(first_obj)
# 對象-雲崖

last_obj = models.User.objects.last()
print(last_obj)
# 對象-大刀

多表關係

   Django中如果要實現多表查詢,必須要建立外鍵,因為Django的多表查詢是建立在外鍵關係基礎之上的。

   但是如果使用原生的SQL命令時我並不推薦使用外鍵做關聯,因為這樣會使得表與表之間的耦合度增加。

   值得一提的是Django中多對多關係創建則只需要兩張表即可,因為它會自動創建第三張表(當然也可以手動創建,有兩種手動創建的方式,下面也會進行介紹)。

一對一

   作者與作者詳情是一對一

   關鍵字:OneToOneField

   注意事項如下:

   1.如不指定關聯字段,自動關聯主鍵id字段

   2.關聯過後從表的關聯字段會自動加上 _id後綴

   3.一對一關係建立在任何一方均可,但是建議建立在查詢使用多的一方,如作者一方

class Author(models.Model):
    name = models.CharField(max_length=16, default="0")
    age = models.IntegerField()
    # 作者與作者詳情一對一
    author_detail = models.OneToOneField(
        to="Authordetail", on_delete=models.CASCADE)

    def __str__(self) -> str:
        return "對象-%s" % self.name


class Authordetail(models.Model):
    phone = models.BigIntegerField()
    # 手機號,用BigIntegerField,

    def __str__(self) -> str:
        return "對象-%s" % self.phone

   查看一對一關係(注意,外鍵上加了一個UNIQUE約束):

desc app01_author;

+------------------+-------------+------+-----+---------+----------------+
| Field            | Type        | Null | Key | Default | Extra          |
+------------------+-------------+------+-----+---------+----------------+
| id               | int(11)     | NO   | PRI | NULL    | auto_increment |
| age              | int(11)     | NO   |     | NULL    |                |
| author_detail_id | int(11)     | NO   | UNI | NULL    |                |
| name             | varchar(16) | NO   |     | NULL    |                |
+------------------+-------------+------+-----+---------+----------------+

一對多

   書籍與出版社是一對多

   關鍵字:ForeignKey

   注意事項如下:

   1.如不指定關聯字段,自動關聯主鍵id字段

   2.關聯過後從表的關聯字段會自動加上 _id後綴

   3.一對多關係的fk應該建立在多的一方

class Publish(models.Model):
    name = models.CharField(max_length=16)
    addr = models.CharField(max_length=64)
    email = models.EmailField()

    def __str__(self) -> str:
        return "對象-%s" % self.name


class Book(models.Model):
    title = models.CharField(max_length=16)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    publish_date = models.DateField(auto_now=False, auto_now_add=True)
    publish = models.ForeignKey(to="Publish", on_delete=models.CASCADE)
    
    def __str__(self) -> str:
        return "對象-%s" % self.title

   查看一對多關係:

decs app01_book;

+--------------+--------------+------+-----+---------+----------------+
| Field        | Type         | Null | Key | Default | Extra          |
+--------------+--------------+------+-----+---------+----------------+
| id           | int(11)      | NO   | PRI | NULL    | auto_increment |
| title        | varchar(16)  | NO   |     | NULL    |                |
| price        | decimal(5,2) | NO   |     | NULL    |                |
| publish_date | date         | NO   |     | NULL    |                |
| publish_id   | int(11)      | NO   | MUL | NULL    |                |
+--------------+--------------+------+-----+---------+----------------+

多對多

   書籍與作者是多對多

   關鍵字:ManyToManyFiled

   1.Django使用ManyToManyFiled會自動創建第三張表,而該字段就相當於指向第三張表,並且第三張表默認關聯其他兩張表的pk字段

   2.多對多關係建立在任何一方均可,但是建議建立在查詢使用多的一方

   3.多對多沒有級聯操作

class Book(models.Model):
    title = models.CharField(max_length=16)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    publish_date = models.DateField(auto_now=False, auto_now_add=True)
    publish = models.ForeignKey(to="Publish", on_delete=models.CASCADE)
    # 書籍和出版社是一對多/多對一
    authors = models.ManyToManyField("Author")
    # 書籍與作者是多對多關係

    def __str__(self) -> str:
        return "對象-%s" % self.title

  

   查看多對多關係,即第三張表:

desc app01_book_authors;

+-----------+---------+------+-----+---------+----------------+
| Field     | Type    | Null | Key | Default | Extra          |
+-----------+---------+------+-----+---------+----------------+
| id        | int(11) | NO   | PRI | NULL    | auto_increment |
| book_id   | int(11) | NO   | MUL | NULL    |                |
| author_id | int(11) | NO   | MUL | NULL    |                |
+-----------+---------+------+-----+---------+----------------+

自關聯

   自關聯是一個很特殊的關係,如評論表。

   一個文章下可以有很多評論,這些評論又分為根評論和子評論,那麼這個時候就可以使用自關聯建立關係。

#評論表
class Comment(models.Model):
    #評論的內容字段
    content=models.CharField(max_length=255)
    #評論的發佈時間
    push_time=models.DateTimeField(auto_now_add=True)
    #關聯父評論的id,可以為空,一定要設置null=True
    pcomment = models.ForeignKey(to='self',null=True) 
    def __str__(self):
        return self.content

級聯操作

   一對一,一對多關係均可使用級聯操作。

   在Django中,默認會開啟級聯更新,我們可自行指定級聯刪除on_delete的操作方式。

級聯刪除選項 描述
models.CASCADE 刪除主表數據時,從表關聯數據也將進行刪除
models.SET 刪除主表數據時,與之關聯的值設置為指定值,設置:models.SET(值),或者運行一個可執行對象(函數)。
models.SET_NUL 刪除主表數據時,從表與之關聯的值設置為null(前提FK字段需要設置為可空)
models.SET_DEFAULT 刪除主表數據時,從表與之關聯的值設置為默認值(前提FK字段需要設置默認值)
models.DO_NOTHING 刪除主表數據時,如果從表中有與之關聯的數據,引發錯誤IntegrityError
models.PROTECT 刪除主表數據時,如果從表中有與之關聯的數據,引發錯誤ProtectedError

   操作演示:

 publish = models.ForeignKey(to="Publish",on_delete=models.CASCADE)
    # 書籍和出版社是一對多/多對一

全部代碼

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=16,default="0")
    age = models.IntegerField()
    # 作者與作者詳情一對一
    author_detail = models.OneToOneField(to="Authordetail",on_delete=models.CASCADE)

    def __str__(self) -> str:
        return "對象-%s" % self.name


class Authordetail(models.Model):
    phone = models.BigIntegerField()
    # 手機號,用BigIntegerField,

    def __str__(self) -> str:
        return "對象-%s" % self.phone


class Publish(models.Model):
    name = models.CharField(max_length=16)
    addr = models.CharField(max_length=64)
    email = models.EmailField()

    def __str__(self) -> str:
        return "對象-%s" % self.name


class Book(models.Model):
    title = models.CharField(max_length=16)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    publish_date = models.DateField(auto_now=False, auto_now_add=True)
    publish = models.ForeignKey(to="Publish",on_delete=models.CASCADE)
    # 書籍和出版社是一對多/多對一
    authors = models.ManyToManyField("Author")
    # 書籍與作者是多對多關係

    def __str__(self) -> str:
        return "對象-%s" % self.title

多表操作

一對多

新增記錄

   對於增加操作而言有兩種

   1.使用fk_id進行增加,添加另一張表的id號(因為外鍵會自動添加_id前綴,所以我們可以直接使用)

   2.使用fk進行增加,添加另一張表的具體記錄對象

第一種:
	models.表名.objects.create(
		col = xxx,
		col = xxx,
		外鍵_id = int,
	)
第二種:
	# 先獲取對象
	obj = models.表名.objects.get(col=xxx)
	models.表名.objects.create(
		col = xxx,
		col = xxx,
		外鍵 = obj,
	)

   示例操作:

from app01 import models

# 添加出版社
models.Publish.objects.create(
    # id自動為1
    name="北京出版社",
    addr="北京市海淀區",
    email="[email protected]",
)

models.Publish.objects.create(
    # id自動為2
    name="上海出版社",
    addr="上海市蒲東區",
    email="[email protected]",
)

# 第一種:直接使用fk_id,添加id號
models.Book.objects.create(
    title="Django入門",
    price=99.50,
    # auto_now_add,不用填
    publish_id=1,  # 填入出版設id號
)

# 第二種,使用fk,添加對象
# 2.1 先獲取出版社對象
pub_obj = models.Publish.objects.get(pk=1)
    # 2.2 添加對象
    models.Book.objects.create(
    title="CSS攻略",
    price=81.50,
    # auto_now_add,不用填
    publish=pub_obj, # 填入出版社對象
) 

修改記錄

   修改記錄和增加記錄一樣,都是有兩種

   1.使用fk_id進行修改,改為另一張表的id號(因為外鍵會自動添加_id前綴,所以我們可以直接使用)

   2.使用fk進行修改,改為另一張表的具體記錄對象

第一種:
	models.表名.objects.filter(col=xxx).update(外鍵_id=int)
第二種:
	# 先獲取對象
	obj = models.表名.objects.get(col=xxx)
	models.表名.objects.filter(col=xxx).update(外鍵=obj)

   示例操作:

from app01 import models

# 第一種,直接使用fk_id  # 將第一本書改為上海出版社 # 注意,這裡只能使用filter,因為QuerySet對象才具有update方法
models.Book.objects.filter(pk=1).update(publish_id=2,)

# 第二種,獲取出版設對象,使用fk進行修改 # 將第二本書改為北京出版社
# 2.1獲取出版設對象
pub_obj = models.Publish.objects.get(pk=2)
# 2.2使用fk進行修改
models.Book.objects.filter(pk=2).update(publish=pub_obj)  #  注意,這裡只能使用filter,因為QuerySet對象才具有update方法

刪除記錄

   如果刪除一個出版社對象,與其關聯的所有書籍都將會被級聯刪除。

models.Publish.objects.filter(pk=1).delete()

多對多

   多對多的外鍵,相當於第三張表,必須要拿到第三張表才能進行操作。

   這一點只要能理解,那麼對多對多的操作就會十分便捷。

增加記錄

   增加記錄也是兩種操作,拿到第三張表後可以增加對象,也可以增加id

   注意:可以一次添加一個對象或fk_id,也可以添加多個

第一種:
   obj = models.表名.objects.get(col=xxx)
   obj.外鍵.add(id_1, id_2) # 可以增加一個,也可以增加多個
第二種:
	# 先獲取對象
	obj1 = models.表名.objects.get(col=xxx)
	obj2 = models.表名.objects.get(col=xxx)
	# 拿到第三張表
	obj = models.表名.objects.get(col=xxx)
	obj.外鍵.add(obj1, obj2) # 可以增加一個,也可以增加多個

# obj.外鍵 就是第三張表

   示例操作:

from app01 import models

# 創建作者詳細
a1 = models.Authordetail.objects.create(
	phone=12345678911
)

a2 = models.Authordetail.objects.create(
	phone=15196317676
)

# 創建作者
models.Author.objects.create(
    name="雲崖",
    age=18,
    author_detail=a1,  # a1是創建的記錄對象本身
)

models.Author.objects.create(
    name="傑克",
    age=18,
    author_detail=a2,  # a2是創建的記錄對象本身
)

# 為書籍添加作者
# 方式1 先拿到具體對象,通過外鍵字段拿到第三張表,添加作者的id
# 拿到書籍,通過書籍外鍵字段拿到第三張表(必須是具體對象,不是QuerySet)
book_obj = models.Book.objects.get(pk=1)
book_obj.authors.add(1, 2)  # 為第一部書添加兩個作者

# 方式2 先拿到作者的對象,再拿到第三張表,添加作者對象
author_1 = models.Author.objects.get(name="雲崖")
author_2 = models.Author.objects.get(name="傑克")

# 拿到書籍,通過書籍外鍵字段拿到第三張表(必須是具體對象,不是QuerySet)
book_obj = models.Book.objects.get(pk=2)
book_obj.authors.add(author_1, author_2)  # 為第一部書添加兩個作者

修改記錄

   修改記錄也是拿到第三張表進行修改,可以修改對象,也可以修改id

   注意:可以一次設置一個對象或id,也可以添加多個,但是要使用[]進行包裹

第一種:
    obj = models.表名.objects.get(col=xxx)
    book_obj.authors.set([1]) # 注意[]包裹,可以設置一個,也可以設置多個
第二種:
	# 先獲取對象
	obj1 = models.表名.objects.get(col=xxx)
	obj2 = models.表名.objects.get(col=xxx)
	# 拿到第三張表
	obj = models.表名.objects.get(col=xxx)
	obj.表名.set([obj1, obj2]) # 注意[]包裹,可以設置一個,也可以設置多個
	
# obj.外鍵 就是第三張表

   示例操作:

from app01 import models

# 用id進行設置
book_obj = models.Book.objects.get(pk=1)
book_obj.authors.set([1])  # 拿到第三張表,放入作者的id進行設置。注意[]包裹,可以設置一個,也可以設置多個

# 用對象進行設置
author_obj = models.Author.objects.first()
# 先獲取作者對象
book_obj = models.Book.objects.get(pk=2)
book_obj.authors.set([author_obj])  # 拿出第三張表,放入作者對象,注意[]包裹,可以設置一個,也可以設置多個

刪除記錄

   直接拿到第三張表進行刪除即可。可以刪除對象,也可以刪除id

   注意:可以一次性刪除單個,也可以一次性刪除多個

第一種:
	obj = models.表名.objects.get(col=xxx)
    obj.外鍵.remove(id_1,id_2) # 可以刪除一個,也可以設置多個
第二種:
	# 先獲取對象
	obj1 = models.表名.objects.get(col=xxx)
	obj2 = models.表名.objects.get(col=xxx)
	# 拿到第三張表
	obj = models.表名.objects.get(col=xxx)
	obj.外鍵.remove(obj1, obj2) # 可以刪除一個,也可以設置多個
	
# obj.外鍵 就是第三張表

   示例操作:

from app01 import models

# 用id進行刪除
book_obj = models.Book.objects.get(pk=1)
book_obj.authors.remove(1) # 拿到第三張表,放入作者的id進行刪除。可以刪除一個,也可以刪除多個

# 用對象進行刪除
author_obj = models.Author.objects.last()
# 先獲取作者對象
book_obj = models.Book.objects.get(pk=2)
book_obj.authors.remove(author_obj) # 拿出第三張表,放入作者對象,可以刪除一個,也可以刪除多個

清空記錄

   拿到第三張表,執行clear()則是清空操作。

book_obj = models.Book.objects.get(pk=1) # 拿到具體對象
book_obj.authors.clear() # 刪除其所有作者

多表查詢

數據準備

   還是用多表操作中的結構,數據有一些不同。

   書籍與出版社是一對多

   書籍與作者是多對多

   作者與作者詳情是一對一

select * from app01_publish;  -- 出版社

+----+-----------------+-----------------------------+--------------------+
| id | name            | addr                        | email              |
+----+-----------------+-----------------------------+--------------------+
|  1 | 北京出版社      | 北京市海淀區                | [email protected]  |
|  2 | 上海出版社      | 上海浦東區                  | [email protected] |
|  3 | 西藏出版社      | 烏魯木齊其其格樂區          | [email protected]   |
+----+-----------------+-----------------------------+--------------------+

select * from app01_author;  -- 作者

+----+--------------+-----+------------------+
| id | name         | age | author_detail_id |
+----+--------------+-----+------------------+
|  1 | 雲崖         |  18 |                1 |
|  2 | 浪里白條     |  17 |                2 |
|  3 | 及時雨       |  21 |                3 |
|  4 | 玉麒麟       |  22 |                4 |
|  5 | 入雲龍       |  21 |                5 |
+----+--------------+-----+------------------+

select * from app01_book;  -- 書籍

+----+-------------------+--------+--------------+------------+
| id | title             | price  | publish_date | publish_id |
+----+-------------------+--------+--------------+------------+
|  1 | Django精講        |  99.23 | 2020-09-11   |          1 |
|  2 | HTML入門          |  78.34 | 2020-09-07   |          2 |
|  3 | CSS入門           | 128.00 | 2020-07-15   |          3 |
|  4 | Js精講            | 152.00 | 2020-06-09   |          1 |
|  5 | Python入門        |  18.00 | 2020-08-12   |          1 |
|  6 | PHP全套           |  73.53 | 2020-09-15   |          2 |
|  7 | GO入門到精通      | 166.00 | 2020-07-14   |          3 |
|  8 | JAVA入門          | 123.00 | 2020-04-22   |          2 |
|  9 | C語言入門         |  19.00 | 2020-02-03   |          3 |
| 10 | FLASK源碼分析     | 225.00 | 2019-11-06   |          1 |
+----+-------------------+--------+--------------+------------+


select * from app01_book_authors;  -- 作者書籍關係

+----+---------+-----------+
| id | book_id | author_id |
+----+---------+-----------+
|  1 |       1 |         1 |
|  2 |       2 |         1 |
|  3 |       2 |         2 |
|  4 |       2 |         3 |
|  5 |       2 |         5 |
|  6 |       3 |         4 |
|  7 |       3 |         5 |
|  8 |       4 |         2 |
|  9 |       5 |         2 |
| 10 |       6 |         2 |
| 11 |       7 |         1 |
| 12 |       7 |         2 |
| 13 |       7 |         3 |
| 14 |       8 |         4 |
| 15 |       9 |         1 |
| 16 |       9 |         5 |
| 17 |      10 |         5 |
+----+---------+-----------+

select * from app01_authordetail;  -- 作者詳情

+----+-------------+
| id | phone       |
+----+-------------+
|  1 | 15163726474 |
|  2 | 17738234753 |
|  3 | 15327787382 |
|  4 | 13981080124 |
|  5 | 13273837482 |
+----+-------------+

正反向

   正反向的概念就是看外鍵建立在那張表之上。

   book(fk) —> publish : 正向 publish —> book(fk) : 反向

   切記一句話:

   正向查詢用外鍵

   反向查詢用 表名_set

對象跨表

   使用對象跨表語法如下:

正向:
	book_obj = models.書籍.objects.get(col=xxx)
	publish_obj = book_obj.fk_出版社		 # 這裡publish_obj就是與book_obj相關的出版社了
	publish_obj.all()/.filter(col=xxx)/.get(col_xxx)  
反向:
	publish_obj = models.出版社.objects.get(col=xxx)
	book_obj = publish_obj.book_set		   # 這裡book_obj就是與publish_obj相關的書籍了
	book_obj.all()/.filter(col=xxx)/.get(col_xxx)

   查找id為1的出版社出版的所有書籍,反向

from app01 import models

publish = models.Publish.objects.get(pk=1)
book_queryset = publish.book_set
res = book_queryset.all()
print(res)

# <QuerySet [<Book: 對象-Django精講>, <Book: 對象-Js精講>, <Book: 對象-Python入門>, <Book: 對象-FLASK源碼分析>]>

   查找id為2的書籍作者,正向

from app01 import models

book = models.Book.objects.get(pk=2)
authors_queryset = book.authors
res = authors_queryset.all()
print(res)

雙下跨表

   雙下劃線也可以進行正向或者反向的跨表,並且支持跨多張表。

   注意:雙下劃線能在filter()中使用,也能在values()中使用。拿對象用filter(),拿單一字段用values()

   查找id為1的出版社出版的所有書籍,反向,拿對象

from app01 import models

res_queryset = models.Book.objects.filter(publish__id=1)
print(res_queryset)

# <QuerySet [<Book: 對象-Django精講>, <Book: 對象-Js精講>, <Book: 對象-Python入門>, <Book: 對象-FLASK源碼分析>]>

   查找id為2的書籍作者姓名,正向,拿字段

from app01 import models

res_queryset = models.Book.objects.filter(id=2).values("authors__name")
print(res_queryset)

# <QuerySet [{'authors__name': '雲崖'}, {'authors__name': '浪里白條'}, {'authors__name': '及時雨'}, {'authors__name': '入雲龍'}]>

   查找入雲龍出的所有書籍,正向,拿對象

from app01 import models

res_queryset = models.Book.objects.filter(authors__name="入雲龍")
print(res_queryset)

# <QuerySet [<Book: 對象-HTML入門>, <Book: 對象-CSS入門>, <Book: 對象-C語言入門>, <Book: 對象-FLASK源碼分析>]>

   查找入雲龍出的所有書籍,拿對象,反向查(這個需要配合對象跨表才行)

from app01 import models

res_queryset = models.Author.objects.filter(name="入雲龍").first().book_set.all()
print(res_queryset)

# <QuerySet [<Book: 對象-HTML入門>, <Book: 對象-CSS入門>, <Book: 對象-C語言入門>, <Book: 對象-FLASK源碼分析>]>

   跨多表,從pk為1的出版社中查到其所有作者的手機號,拿字段。

from app01 import models

res_queryset = models.Publish.objects.filter(pk=1).values("book__authors__author_detail__phone")
print(res_queryset)

# <QuerySet [{'book__authors__author_detail__phone': 15163726474}, {'book__authors__author_detail__phone': 17738234753}, {'book__authors__author_detail__phone': 17738234753}, {'book__authors__author_detail__phone': 13273837482}]>

聚合查詢

聚合函數

   使用聚合函數前應進行導入,在ORM中,單獨使用聚合查詢的關鍵字是aggregate()

from django.db.models import Min,Max,Avg,Count,Sum

   聚合函數應該配合分組一起使用,而不是單獨使用。

   單獨使用一般都是對單表進行操作。

   注意:單獨使用聚合函數後,返回的並非一個QuerySet對象,這代表filter()/values()等方法將無法繼續使用。

   查詢書籍的平均價格:

from app01 import models

from django.db.models import Max,Min,Avg,Count,Sum
res = models.Book.objects.aggregate(Avg("price")) # 如不進行分組,則整張表為一組
print(res) # {'price__avg': 108.21}  # 單獨使用聚合,返回的是字典

   查詢最貴的書籍,以及最便宜的書籍。

from app01 import models

from django.db.models import Max, Min, Avg, Count, Sum
res = models.Book.objects.aggregate(Max("price"),Min("price"))
print(res)  # {'price__max': Decimal('225.00'), 'price__min': Decimal('18.00')}

分組查詢

   在ORM中,分組查詢使用關鍵字annotate()

   切記以下幾點:

   1.只要對錶使用了annotate(),則按照pk進行分組

   2.如果分組前指定values(),則按照該字段進行分組,如values("name").annotate()

   3.分組函數中可以使用聚合函數,並且不同於使用aggregate()後的返回對象是一個字典,使用分組函數進行聚合查詢後,返回依舊是一個QuerySet

   統計每本書的作者個數

from app01 import models

from django.db.models import Max, Min, Avg, Count, Sum

# authors_num 別名
res = models.Book.objects.annotate(authors_num=Count("authors__pk"),).values('title','authors_num')
print(res)  
# <QuerySet [{'title': 'Django精講', 'authors_num': 1}, {'title': 'HTML入門', 'authors_num': 4}, {'title': 'CSS入門', 'authors_num': 2}, {'title': 'Js精講', 'authors_num': 1}, {'title': 'Python入門', 'authors_num': 1}, {'title': 'PHP全套', 'authors_num': 1}, {'title': 'GO入門到精通', 'authors_num': 3}, {'title': 'JAVA入門', 'authors_num': 1}, {'title': 'C語言入門', 'authors_num': 2}, {'title': 'FLASK源碼分析', 'authors_num': 1}]>

   統計每個出版社中賣的最便宜的書籍

from app01 import models

from django.db.models import Max, Min, Avg, Count, Sum

# book_name 別名
res = models.Publish.objects.annotate(book_name=Min("book__price"),).values('name','book_name')
print(res)  

# <QuerySet [{'name': '北京出版社', 'book_name': Decimal('18.00')}, {'name': '上海出版社', 'book_name': Decimal('73.53')}, {'name': '西藏出版社', 'book_name': Decimal('19.00')}]>

   統計不止一個作者的圖書

from app01 import models

from django.db.models import Max, Min, Avg, Count, Sum

# authors_num 別名   只要返回的QuerySet對象,就可以使用filter
res = models.Book.objects.annotate(authors_num=Count("authors__pk"),).values('title','authors_num').filter(authors_num__gt=1)
print(res)  

# <QuerySet [{'title': 'HTML入門', 'authors_num': 4}, {'title': 'CSS入門', 'authors_num': 2}, {'title': 'GO入門到精通', 'authors_num': 3}, {'title': 'C語言入門', 'authors_num': 2}]>

   查詢每個作者出書的總價

from app01 import models

from django.db.models import Max, Min, Avg, Count, Sum

# sum_price 別名
res = models.Author.objects.annotate(sum_price=Sum("book__price"),).values('name','sum_price')
print(res)

# <QuerySet [{'name': '雲崖', 'sum_price': Decimal('362.57')}, {'name': '浪里白條', 'sum_price': Decimal('487.87')}, {'name': '及時雨', 'sum_price': Decimal('244.34')}, {'name': '玉麒麟', 'sum_price': Decimal('251.00')}, {'name': '入雲龍', 'sum_price': Decimal('450.34')}]>

F&Q過濾

F查詢

   F查詢能夠拿到字段的數據。使用前應該先進行導入

form django.db.moduls import F

   注意:如果要操縱的字段是字符類型,要對其進行拼接操作還需導入Concat進行拼接

  

   將所有書籍的價格提升50元

from app01 import models

from django.db.models import Max, Min, Avg, Count, Sum
from django.db.models import F, Q

res = models.Book.objects.update(price=F("price")+50)
print(res)  # 10

   在每本書名字後面加上爆款二字(注意:操縱字符類型,需要用到ConcatValue函數)

from app01 import models

from django.db.models import Max, Min, Avg, Count, Sum
from django.db.models import F, Q
# 必須配合下面的兩個進行使用
from django.db.models.functions import Concat
from django.db.models import Value

res = models.Book.objects.update(title=Concat(F("title"), Value("爆款")))
print(res)  # 10

Q查詢

   在filter()中,只要多字段篩選中用逗號進行分割都是AND鏈接,如何進行ORNOT呢?此時就要用到Q,還是要先進行導入該功能。

form django.db.moduls import Q
符號 描述
filter(Q(col=xxx),Q(col=xxx)) AND關係,只要在filter()中用逗號分割,就是AND關係
filter(Q(col=xxx)!Q(col=xxx)) OR關係,用|號進行鏈接
filter(~Q(col=xxx)!Q(col=xxx)) NO關係,用~號進行連接

   查詢書籍表中入雲龍或雲崖出版的書籍

from app01 import models

from django.db.models import Max, Min, Avg, Count, Sum
from django.db.models import F, Q

res = models.Book.objects.filter(Q(authors__name='雲崖')|Q(authors__name="入雲龍"))
print(res)
# <QuerySet [<Book: 對象-Django精講爆款>, <Book: 對象-HTML入門爆款>, <Book: 對象-GO入門到精通爆款>, <Book: 對象-C語言入門爆款>, <Book: 對象-HTML入門爆款>, <Book: 對象-CSS入門爆款>, <Book: 對象-C語言入門爆款>, <Book: 對象-FLASK源碼分析爆款>]>

   查詢書籍表中不是雲崖和入雲龍出版的書籍

from app01 import models

from django.db.models import Max, Min, Avg, Count, Sum
from django.db.models import F, Q


res = models.Book.objects.filter(~Q(authors__name='雲崖'),Q(authors__name="入雲龍"))
print(res)
# <QuerySet [<Book: 對象-CSS入門爆款>, <Book: 對象-FLASK源碼分析爆款>]>

Q進階

   如果我們的Q查詢中,能去讓用戶來輸入字符串指定字段進行查詢,那麼就非常厲害了。

   其實這也是能夠實現。

from app01 import models

from django.db.models import Max, Min, Avg, Count, Sum
from django.db.models import F, Q

q = Q() # 實例化出一個對象
q.connector = "and"  # 指定q的關係

# 兩個Q
q.children.append(("price__lt","120")) 
q.children.append(("price__gt","50"))

res = models.Book.objects.filter(q)
# 相當於: models.Book.objects.filter(Q(price__lt=120),Q(price__gt=50))

print(res)
# <QuerySet [<Book: 對象-Python入門爆款>, <Book: 對象-C語言入門爆款>]>

查詢優化

   使用單表或多表時,可以使用以下方法進行查詢優化。

only

   only取出的記錄對象中只有指定的字段,沒有其他字段。

   因此查詢速度會非常快。

   默認會拿出所有字段。

from app01 import models

obj = models.Book.objects.all().first()

print(obj.title)
print(obj.price)

SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id` FROM `app01_book` ORDER BY `app01_book`.`id` ASC LIMIT 1;

   使用only只會拿出指定字段,但是如果要拿指定字段以外的字段。則會再次進行查詢

from app01 import models

obj = models.Book.objects.only("title").first()

print(obj.title)
print(obj.price)

SELECT `app01_book`.`id`, `app01_book`.`title` FROM `app01_book` ORDER BY `app01_book`.`id` ASC LIMIT 1; 
SELECT `app01_book`.`id`, `app01_book`.`price` FROM `app01_book` WHERE `app01_book`.`id` = 1; 

defer

   deferonly正好相反,拿除了指定字段以外的其他字段。

   但是如果要拿指定字段的字段。則會再次進行查詢

from app01 import models

obj = models.Book.objects.defer("title").first()


print(obj.title)
print(obj.price)

SELECT `app01_book`.`id`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id` FROM app01_book` ORDER BY `app01_book`.`id` ASC LIMIT 1;
SELECT `app01_book`.`id`, `app01_book`.`title` FROM `app01_book` WHERE `app01_book`.`id` = 1;

select_related

   原本的跨表,尤其是使用對象跨表,都會查詢兩次。

   但是select_related指定連表,則只會查詢一次。

   注意!select_related中指定的連表,只能是外鍵名。並且不能是多對多關係

   原本跨表,兩次查詢語句。

from app01 import models

obj = models.Book.objects.all().first()
obj_fk = obj.publish
print(obj)
print(obj_fk)

SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id` FROM `app01_book` ORDER BY `app01_book`.`id` ASC LIMIT 1;

SELECT `app01_publish`.`id`, `app01_publish`.`name`, `app01_publish`.`addr`, `app01_publish`.`email` FROM `app01_publish` WHERE `app01_publish`.`id` = 1;

   如果是使用select_related,則只需要查詢一次。

from app01 import models

obj = models.Book.objects.select_related("publish").first()
obj_fk = obj.publish
print(obj)
print(obj_fk)


SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id`, `app01_publish`.`id`, `app01_publish`.`name`, `app01_publish`.`addr`, `app01_publish`.`email` FROM `app01_book` INNER JOIN `app01_publish` ON (`app01_book`.`publish_id` = `app01_publish`.`id`) ORDER BY `app01_book`.`id` ASC LIMIT 1;

prefetch_related

   prefetch_related是子查詢。它會默認走兩次SQL語句,相比於select_related,它的查詢次數雖然多了一次,但是量級少了很多,不用拼兩張表全部內容。

   注意!prefetch_related中指定的連表,只能是外鍵名。並且不能是多對多關係

from app01 import models

obj = models.Book.objects.prefetch_related("publish").first()
obj_fk = obj.publish
print(obj)
print(obj_fk)

SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id` FROM `app01_book` ORDER BY `app01_book`.`id` ASC LIMIT 1;

SELECT `app01_publish`.`id`, `app01_publish`.`name`, `app01_publish`.`addr`, `app01_publish`.`email` FROM `app01_publish` WHERE `app01_publish`.`id` IN (1);  # 注意in,只查這一條

   如果要使用兩張表中許多數據,則使用select_related拼出整表。

   如果連表需要的數據不多,可使用prefetch_related子查詢。

原生SQL

   如果你覺得ORM有一些查詢搞不定,就可以使用原生SQL語句。

   官方文檔://docs.djangoproject.com/zh-hans/3.1/topics/db/sql/

connection

   這和pymysql的使用基本一致,但是並不提供返回dict類型的數據。所以我們需要自定義一個函數來進行封裝。

from app01 import models
from django.db import connection, connections

def dictfetchall(cursor):
    "Return all rows from a cursor as a dict"
    columns = [col[0] for col in cursor.description]
    return [
    dict(zip(columns, row))
    for row in cursor.fetchall()
]

print(help(connection.cursor))

# 1.1 獲取游標對象
cursor = connection.cursor()  # 默認連接default數據庫。
cursor.execute(
    """
    SELECT * FROM app01_book INNER JOIN app01_publish
    on app01_book.publish_id = app01_publish.id
    where app01_book.id = 1;
    """
,()) #第二參數,字符串拼接。與pymysql使用相同,防止sql注入。

# 1.2 返回封裝結果
row = dictfetchall(cursor)
print(row)

# [{'id': 1, 'title': 'Django精講', 'price': Decimal('99.23'), 'publish_date': datetime.date(2020, 9, 11), 'publish_id': 1, 'name': '北京出版社', 'addr': '北京市海淀區', 'email': '[email protected]'}]

raw

   raw是借用一個模型類,返回結果將返回至模型類中。

   返回結果是一個RawQuerySet對象,這種用法必須將主鍵取出

from app01 import models

res_obj = models.Book.objects.raw("select id,name from app01_author",params=[]) # 必須取出其他表的主鍵
# params = 格式化。防止sql注入

for i in res_obj:
	print(i.name)  # 雖然返回的Book實例化,但是其中包含了作者的字段。可以通過屬性點出來

# 雲崖
# 浪里白條
# 及時雨
# 玉麒麟
# 入雲龍

   你可以用 raw()translations 參數將查詢語句中的字段映射至模型中的字段。這是一個字典,將查詢語句中的字段名映射至模型中的字段名。說白了就是as一個別名。

   例如,上面的查詢也能這樣寫:

from app01 import models
name_map = {'pk': 'aid', 'name': 'a_name',}  # 不要使用id,使用pk。它知道是id  

res_obj = models.Book.objects.raw("select * from app01_author", params=[],translations=name_map)
# params = 格式化。防止sql注入

for i in res_obj:
	print(i.a_name) 

# 雲崖
# 浪里白條
# 及時雨
# 玉麒麟
# 入雲龍

extra

   原生與ORM結合,這個可用於寫子查詢。

   我沒怎麼用過這個,很少使用。

def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
    # 構造額外的查詢條件或者映射,如:子查詢

    Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,))
    Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
    Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
    Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid'])

開啟事務

   Django中開啟事務非常簡單,因為又with()上下文管理器的情況,所以我們只需要在沒出錯的時候提交,出錯後回滾即可。

from django.db import transaction

 
def func_views(request):
    try:
        with transaction.atomic():      
			# 操作1
			# 操作2
			# 如果沒發生異常自動提交事務
            # raise DatabaseError 數據庫異常,說明sql語句有問題    
    except DatabaseError:     # 自動回滾,不需要任何操作
            pass
            

詳解QuerySet

惰性求值

   QuerySet的一大特性就是惰性求值,即你不使用數據時是不會進行數據庫查詢。

from app01 import models
res_queryset = models.Book.objects.all()
# 無打印SQL

緩存機制

   QuerySet具有緩存機制,下次再使用同一變量時,不會走數據庫查詢而是使用緩存。

from app01 import models
res_queryset = models.Book.objects.filter(pk=1)

for i in res_queryset:
	print(i)

for i in res_queryset:
	print(i)

# (0.001) SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id` FROM `app01_book` WHERE `app01_book`.`id` = 1; args=(1,)
# 對象-Django精講
# 對象-Django精講

避免臟數據

   因為QuerySet的緩存機制,所以使得可能出現臟數據。

   即數據庫中修改了某個數據,但是QuerySet緩存中依舊是舊數據。

   避免這種問題的解決方案有兩種:

   1.不要重複使用同一變量。每次使用都應該重新賦值(雖然增加查詢次數,但是保證數據準確)

   2.使用迭代器方法,不可重複用

from app01 import models
res_queryset = models.Book.objects.filter(pk=1)

for i in res_queryset.iterator(): # 迭代器
	print(i)

for i in res_queryset:  # 緩存沒有,重新獲取
	print(i)

# (0.001) SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id` FROM `app01_book` WHERE `app01_book`.`id` = 1; args=(1,)
# 對象-Django精講
# (0.003) SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id` FROM `app01_book` WHERE `app01_book`.`id` = 1; args=(1,)
# 對象-Django精講

常用字段及參數

mysql對照

Django MySQL
AutoField integer AUTO_INCREMENT
BigAutoField bigint AUTO_INCREMENT
BinaryField longblob
BooleanField bool
CharField varchar(%(max_length)s)
CommaSeparatedIntegerField varchar(%(max_length)s)
DateField date
DecimalField numeric(%(max_digits)s, %(decimal_places)s)
DurationField bigint
FileField varchar(%(max_length)s)
FilePathField varchar(%(max_length)s)
IntegerField integer
BigIntegerField bigint
IPAddressField char(15)
GenericIPAddressField char(39)
NullBooleanField bool
OneToOneField integer
PositiveIntegerField nteger UNSIGNED
SlugField varchar(%(max_length)s)
SmallIntegerField smallint
TextField longtext
TimeField time
UUIDField char(32)

數值類型

Django字段 描述 是否有注意事項
AutoField int自增列,必須填入參數 primary_key=True 有,見描述
BigAutoField bigint自增列,必須填入參數 primary_key=True 有,見描述
SmallIntegerField 小整數 -32768 ~ 32767  
PositiveSmallIntegerField 正小整數 0 ~ 32767  
IntegerField 整數列(有符號的) -2147483648 ~ 2147483647  
PositiveIntegerField 正整數 0 ~ 2147483647  
BigIntegerField 長整型(有符號的) -9223372036854775808 ~ 9223372036854775807  
BooleanField 布爾值類型  
NullBooleanField 可以為空的布爾值  
FloatField 浮點型  
DecimalField 10進制小數 有,見說明
BinaryField 二進制類型  

   說明補充:

DecimalField(Field)
- 10進制小數
- 參數:
	max_digits,小數總長度
	decimal_places,小數位長度

   其他補充:自定義無符號整數類型

class UnsignedIntegerField(models.IntegerField): # 必須繼承
	def db_type(self, connection):
		return 'integer UNSIGNED' # 返回真實存放的類型

字符類型

Django字段 描述 是否有注意事項
CharField 必須提供max_length參數, max_length表示字符長度 有,見描述
EmailField 字符串類型,Django Admin以及ModelForm中提供驗證機制  
IPAddressField 字符串類型,Django Admin以及ModelForm中提供驗證 IPV4 機制  
GenericIPAddressField 字符串類型,Django Admin以及ModelForm中提供驗證 Ipv4和Ipv6 有,見說明
URLField 字符串類型,Django Admin以及ModelForm中提供驗證 URL  
SlugField 字符串類型,Django Admin以及ModelForm中提供驗證支持 字母、數字、下劃線、連接符(減號)  
CommaSeparatedIntegerField 字符串類型,格式必須為逗號分割的數字  
UUIDField 字符串類型,Django Admin以及ModelForm中提供對UUID格式的驗證  
FilePathField 字符串,Django Admin以及ModelForm中提供讀取文件夾下文件的功能 有,見說明
FileField 字符串,路徑保存在數據庫,文件上傳到指定目錄 有,見說明
ImageField 字符串,路徑保存在數據庫,文件上傳到指定目錄 有,見說明

   說明補充:

GenericIPAddressField(Field)
- 字符串類型,Django Admin以及ModelForm中提供驗證 Ipv4和Ipv6
- 參數:
    protocol,用於指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
    unpack_ipv4, 如果指定為True,則輸入::ffff:192.0.2.1時候,可解析為192.0.2.1,開啟刺功能,需要protocol="both"

FilePathField(Field)
- 字符串,Django Admin以及ModelForm中提供讀取文件夾下文件的功能
- 參數:
    path,                      文件夾路徑
    match=None,                正則匹配
    recursive=False,           遞歸下面的文件夾
    allow_files=True,          允許文件
    allow_folders=False,       允許文件夾

FileField(Field)
- 字符串,路徑保存在數據庫,文件上傳到指定目錄
- 參數:
    upload_to = ""      上傳文件的保存路徑
    storage = None      存儲組件,默認django.core.files.storage.FileSystemStorage


ImageField(FileField)
- 字符串,路徑保存在數據庫,文件上傳到指定目錄
- 參數:
    upload_to = ""      上傳文件的保存路徑
    storage = None      存儲組件,默認django.core.files.storage.FileSystemStorage
    width_field=None,   上傳圖片的高度保存的數據庫字段名(字符串)
    height_field=None   上傳圖片的寬度保存的數據庫字段名(字符串)

時間類型

Django字段 描述 是否有注意事項
DateTimeField 日期+時間格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] 有,見說明
DateField 日期格式 YYYY-MM-DD 有,見說明
TimeField 時間格式 HH:MM[:ss[.uuuuuu]]  
DurationField 長整數,時間間隔,數據庫中按照bigint存儲,ORM中獲取的值為datetime.timedelta類型  

   說明補充:

時間類型的字段都有兩個參數:
auto_now=False  # 當記錄更新時是否自動更新當前時間
auto_now_add=False # 當記錄創建時是否自動更新當前時間

條件參數

條件參數 描述
null 數據庫中字段是否可以為空,接受布爾值
db_column 數據庫中字段的列名,接受字符串
default 數據庫中字段的默認值,接受時間,數值,字符串
primary_key 數據庫中字段是否為主鍵,接受布爾值(一張表最多一個主鍵)
db_index 數據庫中字段是否可以建立索引,接受布爾值
unique 數據庫中字段是否可以建立唯一索引,接受布爾值
unique_for_date 數據庫中字段【日期】部分是否可以建立唯一索引,接受布爾值
unique_for_month 數據庫中字段【月】部分是否可以建立唯一索引,接受布爾值
unique_for_year 數據庫中字段【年】部分是否可以建立唯一索引,接受布爾值

choices

   choices是一個非常好用的參數。它允許你數據庫中存一個任意類型的值,但是需要使用時則是使用的它的描述。

   如下:

gender = models.BooleanField(choices=((0,"male"),(1,"female")),default=0)
# 實際只存0和1,但是我們取的時候會取male或者female

   取出方法:

get_col_display()

後端示例:
	obj = models.User.objects.get(pk=1)
	gender = obj.get_gender_display()
	print(gender)  # male
	
前端示例:
	{{obj.get_gender_display}}  <!-- 前端不用加括號,自己調用 -->

元信息

class UserInfo(models.Model):
        nid = models.AutoField(primary_key=True)
        username = models.CharField(max_length=32)
        class Meta:
            # 數據庫中生成的表名稱 默認 app名稱 + 下劃線 + 類名
            db_table = "table_name"

            # 聯合索引
            index_together = [
                ("pub_date", "deadline"),
            ]

            # 聯合唯一索引
            unique_together = (("driver", "restaurant"),)

            # admin中顯示的表名稱
            verbose_name

            # verbose_name加s
            verbose_name_plural

多表關係參數

ForeignKey(ForeignObject) # ForeignObject(RelatedField)
        to,                         # 要進行關聯的表名
        to_field=None,              # 要關聯的表中的字段名稱
        on_delete=None,             # 當刪除關聯表中的數據時,當前表與其關聯的行的行為
                                        - models.CASCADE,刪除關聯數據,與之關聯也刪除
                                        - models.DO_NOTHING,刪除關聯數據,引發錯誤IntegrityError
                                        - models.PROTECT,刪除關聯數據,引發錯誤ProtectedError
                                        - models.SET_NULL,刪除關聯數據,與之關聯的值設置為null(前提FK字段需要設置為可空)
                                        - models.SET_DEFAULT,刪除關聯數據,與之關聯的值設置為默認值(前提FK字段需要設置默認值)
                                        - models.SET,刪除關聯數據,
                                                      a. 與之關聯的值設置為指定值,設置:models.SET(值)
                                                      b. 與之關聯的值設置為可執行對象的返回值,設置:models.SET(可執行對象)

                                                        def func():
                                                            return 10

                                                        class MyModel(models.Model):
                                                            user = models.ForeignKey(
                                                                to="User",
                                                                to_field="id"
                                                                on_delete=models.SET(func),)
        related_name=None,          # 反向操作時,使用的字段名,用於代替 【表名_set】 如: obj.表名_set.all()
        related_query_name=None,    # 反向操作時,使用的連接前綴,用於替換【表名】     如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
        limit_choices_to=None,      # 在Admin或ModelForm中顯示關聯數據時,提供的條件:
                                    # 如:
                                            - limit_choices_to={'nid__gt': 5}
                                            - limit_choices_to=lambda : {'nid__gt': 5}

                                            from django.db.models import Q
                                            - limit_choices_to=Q(nid__gt=10)
                                            - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
                                            - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
        db_constraint=True          # 是否在數據庫中創建外鍵約束
        parent_link=False           # 在Admin中是否顯示關聯數據


    OneToOneField(ForeignKey)
        to,                         # 要進行關聯的表名
        to_field=None               # 要關聯的表中的字段名稱
        on_delete=None,             # 當刪除關聯表中的數據時,當前表與其關聯的行的行為

                                    ###### 對於一對一 ######
                                    # 1. 一對一其實就是 一對多 + 唯一索引
                                    # 2.當兩個類之間有繼承關係時,默認會創建一個一對一字段
                                    # 如下會在A表中額外增加一個c_ptr_id列且唯一:
                                            class C(models.Model):
                                                nid = models.AutoField(primary_key=True)
                                                part = models.CharField(max_length=12)

                                            class A(C):
                                                id = models.AutoField(primary_key=True)
                                                code = models.CharField(max_length=1)

    ManyToManyField(RelatedField)
        to,                         # 要進行關聯的表名
        related_name=None,          # 反向操作時,使用的字段名,用於代替 【表名_set】 如: obj.表名_set.all()
        related_query_name=None,    # 反向操作時,使用的連接前綴,用於替換【表名】     如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
        limit_choices_to=None,      # 在Admin或ModelForm中顯示關聯數據時,提供的條件:
                                    # 如:
                                            - limit_choices_to={'nid__gt': 5}
                                            - limit_choices_to=lambda : {'nid__gt': 5}

                                            from django.db.models import Q
                                            - limit_choices_to=Q(nid__gt=10)
                                            - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
                                            - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
        symmetrical=None,           # 僅用於多對多自關聯時,symmetrical用於指定內部是否創建反向操作的字段
                                    # 做如下操作時,不同的symmetrical會有不同的可選字段
                                        models.BB.objects.filter(...)

                                        # 可選字段有:code, id, m1
                                            class BB(models.Model):

                                            code = models.CharField(max_length=12)
                                            m1 = models.ManyToManyField('self',symmetrical=True)

                                        # 可選字段有: bb, code, id, m1
                                            class BB(models.Model):

                                            code = models.CharField(max_length=12)
                                            m1 = models.ManyToManyField('self',symmetrical=False)

        through=None,               # 自定義第三張表時,使用字段用於指定關係表
        through_fields=None,        # 自定義第三張表時,使用字段用於指定關係表中那些字段做多對多關係表
                                        from django.db import models

                                        class Person(models.Model):
                                            name = models.CharField(max_length=50)

                                        class Group(models.Model):
                                            name = models.CharField(max_length=128)
                                            members = models.ManyToManyField(
                                                Person,
                                                through='Membership',
                                                through_fields=('group', 'person'),
                                            )

                                        class Membership(models.Model):
                                            group = models.ForeignKey(Group, on_delete=models.CASCADE)
                                            person = models.ForeignKey(Person, on_delete=models.CASCADE)
                                            inviter = models.ForeignKey(
                                                Person,
                                                on_delete=models.CASCADE,
                                                related_name="membership_invites",
                                            )
                                            invite_reason = models.CharField(max_length=64)
        db_constraint=True,         # 是否在數據庫中創建外鍵約束
        db_table=None,              # 默認創建第三張表時,數據庫中表的名稱字段以及參數

M2M創建

   M2M的創建方式有三種,分別是全自動,半自動,全手動。

   全自動使用最方便,但是擴展性最差。

   半自動介於全自動與全手動之間。

   全手動使用最麻煩,但是擴展性最好。

全自動

   自動創建的第三張表,即為全自動。

   最上面多表關係中的多對多,使用的便是全自動創建。

   全自動創建操縱及其方便,如add/set/remove/clear

半自動

   手動創建第三張表,並指定此表中那兩個字段是其他兩張表的關聯關係。

   可使用__跨表,正反向查詢。

   但是不可使用如add/set/remove/clear

   在實際生產中,推薦使用半自動。它的擴展性最強

class Book(models.Model):
	name = models.CharField(max_length=32)
	authors = models.ManyToManyField(
		to="Author", # 與作者表創建關係。
		through="M2M_BookAuthor", # 使用自己創建的表
		through_fields=('book','author'), # 這兩個字段是關係  注意!那張表上創建多對多,就將字段放在前面
	)
	
class Author(models.Model):
	name = models.CharField(max_length=32)
	# book = models.ManyToManyField(
		# to="Book",
		# through="M2M_BookAuthor",
		# through_fields=('author','book'), Author創建,所以author放在前面
	
	# )
	
class M2M_BookAuthor(models.Model):
	book = models.ForeignKey(to="Book")
	author = models.ForeignKey(to="Author")
	# 兩個fk,自動添加_id後綴。
	
    class Meta:
    	unique_together = (("author", "book"),)
    	# 聯合唯一索引

全手動

   不可使用add/set/remove/clear,以及__跨表,正反向查詢。

   所有數據均手動錄入。

class Book(models.Model):
	name = models.CharField(max_length=32)

	
class Author(models.Model):
	name = models.CharField(max_length=32)
	
class M2M_BookAuthor(models.Model):
	book = models.ForeignKey(to="Book")
	author = models.ForeignKey(to="Author")
	# 兩個fk,自動添加_id後綴。
	
    class Meta:
    	unique_together = (("author", "book"),)
    	# 聯合唯一索引
Tags: