Django 之 ORM(全)


六、模型層(ORM)重難點

Django中內嵌了 ORM框架,不需要直接編寫SQL語句進行數據庫操作,而是通過定義模型類,操作模型來完成對數據庫中表的增刪改查和創建等操作。

image

O是 object,也就是 類對象 的意思。

R是 relation,關系的意思,也就是關系數據庫中數據表的意思。

M是 mapping,是映射的意思。

映射:

類:sql語句 table表

類成員變量:table表中的字段類型和約束

類對象:sql表的表記錄

ORM的優點:

  • 數據模型類都在一個地方定義,更容易更新和維護,也利於代碼重用。
  • ORM有現成的工具,很多功能都可以自動完成,比如,數據消除,預處理,事務等等。
  • 它迫使你使用 MVC架構,ORM 就是天然的 Model,最終使代碼更清晰。
  • 基於 ORM 的業務代碼比較簡單,代碼量少,語義性好,容易理解。
  • 新手對於復雜業務容易寫出性能不佳的 SQL,有了 ORM不必編寫復雜的 SQL語句,只需要通過模型對象即可同步修改數據表中的數據。
  • 開發中應用 ORM 將來如果要切換數據庫,只需要切換 ORM 底層對接數據庫的驅動【修改配置文件的連接地址即可】

ORM也有缺點:

  • ORM 庫部署輕量級工具,需要花很多精力學習和設置,甚至不同的框架,會存在不同操作的 ORM。
  • 對於復雜的業務查詢,ORM表達起來比原生的 SQL要更加困難和復雜。
  • ORM 操作數據庫的性能要比原生的 SQL差。
  • ORM 抽象掉了數據庫層,開發者無法了解底層的數據庫操作,也無法定制一些特殊的SQL。【自己使用pymysql另外操作即可,用了ORM並不表示當前項目不能使用別的數據庫操作工具了】

我們可以通過以下步驟來使用django的數據庫操作

1. 配置數據庫連接信息
2. 在models.py 中定義模型類
3. 生成數據庫遷移文件並執行遷移文件# [注意:數據遷移是一個獨立的功能,這個功能在其他web框架未必和ORM一塊的]
4. 通過模型類對象提供的方法或屬性完成數據表的增刪改查操作

6.1 配置數據庫連接

在 settings.py 中保存了數據庫的連接配置信息,django默認初始配置使用sqlite數據庫。

  1. 使用MySQL數據庫首先需要安裝驅動程序

    pip3 install pymysql
    
  2. 在 django的工程同名子目錄的 __init__.py文件中添加如下語句

    from pymysql import install_as_MySQLdb
    install_as_MySQLdb()  # 讓pymysql以 MySQLDB的運行模式和django的ORM對接運行
    

    作用是讓django 的 ORM能以mysqldb的方式來調用pymysql。

  3. 修改DATABASES配置信息

    DATABASES = [
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'HOST': '127.0.0.1',  # 數據庫主機
            'PORT': 3306,  # 數據庫端口
            'USER': 'root',  # 數據庫用戶名
            'PASSWORD': '123',  # 數據庫用戶密碼
            'NAME': 'student',  # 數據庫名字
        }
    ]
    
  4. 在MySQL中創建數據庫

    create database student;  # mysql8.0默認就是utf8mb4;
    create database student default charset=utf8mb4;  # mysql8.0之前的版本
    
  5. 注意3:如果想打印 ORM轉換過程中的sql,需要在settings中進行如下配置。

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

6.2 定義模型類

定義模型類

  • 模型類被定義在 “子應用/model.py”文件中。
  • 模型類必須直接或者間接 繼承自 django.db.models.Model類。

接下來以學生管理為例進行演示。[系統大概 3-4個表,學生信息,課程信息,老師信息],創建子應用 student,注冊子應用並引入子應用路由。

settings.py,代碼:

INSTALLED_APPS = [
    # ...
    'student',
]

urls.py,總路由代碼:

urlpatterns = [
    # 省略,如果前面有重復的路由,改動如下。
    path('student/', include('student.urls')),
]

models.py,文件中定義模型類:

from django.db import models
from datetime import datetime


# 模型類必須要直接或者間接繼承於 models.Model
class BaseModel(models.Model):
    """公共模型[公共方法和公共字段]"""
    # created_time = models.IntegerField(default=0,verbose_name="創建時間")
    created_time = models.DateTimeField(auto_now_add=True, verbose_name="創建時間")
    # auto_now_add 當數據添加時,設置當前時間為默認值
    # auto_now= 當數據添加/更新時,設置當前時間為默認值
    updated_time = models.DateTimeField(auto_now=True)
    class Meta(object):
        abstract = True  # 設置當前模型為抽象模型,當系統運行時,不會任務這是一個數據表對應的模型。
        
   
class Student(BaseModel):
    """Student模型類"""
    # 1. 字段[數據庫表字段對應]
    SEX_CHOICES = (
    	(0, '女'),
        (1, '男'),
        (2, '保密'),
    )
    
    # 字段名 = models.數據類型(約束選項1,約束選項2,verbose_name="注釋")
    # SQL: id bigint primary_key auto_increment not null comment="主鍵",
    # id = models.AutoField(primary_key=True, null=False, verbose_name="主鍵")
    # django會自動在創建數據表的時候生成id主鍵/還設置了一個調用別名:pk
    
    # SQL: name varchar(20) not null comment="姓名"
    # SQL: key(name),
    name = models.CharField(max_length=20, db_index=True, verbose_name="姓名")
    
    # SQL: age smallint not null comment="年齡"
    age = models.SmallIntegerField(verbose_name="年齡")
    
    # SQL: sex tinyint not null comment="性別"
    # sex = models.BooleanField(verbose_name="性別")
    sex = models.SmallIntegerField(choices=SEX_CHOICES, default=2)
    
    # SQL: class varchar(5) not null comment="班級"
    # SQL: key(class)
    classmate = models.CharField(db_column="class", max_length=5, db_index=True, verbose_name="班級")
    # SQL: description longtext default "" not null comment="個性簽名"
    description = models.TextField(default="", verbose_name="個性簽名")
    
    # 2. 數據表結構信息
    class Meta:
        db_table = 'tb_student'
        # 指明數據庫表名,如果沒有指定表名,則默認為子應用目錄名_模型名稱,例如:users_student
        verbose_name = "學生信息表"
        # 在admin站點中顯示的名稱
        
        verbose_name_plural = verbose_name  # 顯示的復數???名稱??
        
    
    # 3. 自定義數據庫操作方法
    def __str__(self):
        """定義每個數據對象的顯示信息"""
        return "<User %s>" % self.name

6.2.1 數據庫表名

模型類如果未指明表名 db_table,Django默認以 小寫app應用名_小寫模型類名 為數據庫表名。

可通過 db_table指明數據庫表名。

6.2.2 關於主鍵

django會為表創建自動增長的主鍵列,每個模型只能有一個主鍵列。

如果使用選項設置某個字段的約束屬性為 主鍵列(primary_key)之后,django不會再創建自動增長的主鍵列。

class Student(models.Model):
    # django會自動在創建數據表的時候生成id主鍵/還設置了一個調用別名 pk
    id = models.AutoField(primary_key=True, null=False, verbose_name="主鍵")  # 設置主鍵

默認創建的主鍵列屬性為id,可以使用pk代替,pk是primary key。

6.2.3 屬性命名限制

  • 不能是python的保留關鍵字。

  • 不允許使用連續的2個下划線,這是由django的查詢方式決定的。__是關鍵字來的,不能使用!!!!!

  • 定義屬性時需要指定字段類型,通過字段類型的參數指定選項,語法如下:

    屬性名 = models.字段類型(約束選項,verbose_name="注釋")
    

6.2.4 字段類型

類型 說明
AutoField 自動增長的IntegerField,通常不用指定,不指定時Django會自動創建屬性名為 id的自動增長屬性
BooleanField 布爾字段,值為True或False
NullBooleanField 支持Null,True,False三種值
CharField 字符串,參數max_length表示最大字符個數,對應MySQL中的 varchar
TextField 大文本字段,一般打斷文本(超過4000個字符)才使用。
IntegerField 整數
DecimalField 十進制浮點數,參數max_digits表示總位數,參數decimal_places表示小數位數,常用於表示分數和價格 Decimal(max_digits=7,decimal_places=2) ==> 99999.99 ~ 0.00
FloatField 浮點數
DateField 日期
參數auto_now表示每次保存對象時,自動設置該字段為當前時間。
參數auto_now_add表示當對象第一次被創建時自動設置當前。
參數auto_now_add和auto_now是相互排斥的,一起使用會發生錯誤。
TimeField 時間,參數同DateField
DateTimeField 日期時間,參數同DateFiled
FileField 上傳文件字段,django在文件字段中內置了文件上傳保存類,django可以通過模型的字段存儲自動保存上傳文件,但是,在數據庫中本質上保存的僅僅是文件在項目中的存儲路徑!!
ImageField 繼承於FileField,對上傳的內容進行校驗,確保是有效的圖片。

6.2.5 約束選項

選項 說明
null 如果為True,表示允許為空,默認值是False,相當於python的None
blank 如果為True,則該字段允許為空白,默認值是False,相當於python的空字符串,“ ”
db_column 字段的名稱,如果未指定,則使用屬性的名稱。
db_index 若值為True,則在表中會為此字段創建索引,默認值是False,相當於SQL語句中的key。
default 默認值,當不填寫數據時,使用該選項的值作為數據的默認值。
primary_key 如果為True,則該字段會成為模型的主鍵,默認值是False,一般不用設置,系統默認設置。
unique 如果為True,則該字段在表中必須有唯一值,默認值是False,相當於SQL語句中的unique

注意:null是數據庫范疇的概念,blank是表單驗證范疇的。

6.2.6 外鍵

在設置外鍵時,需要通過,on_delete 選項指明主表刪除數據時,對於外鍵引用表數據如何處理,在django.db.models中包含了可選常量:

  • CASCADE級聯,刪除主表數據時連通一起刪除外鍵表中數據,

  • PROTECT保護,通過拋出 ProtectedError異常,來阻止刪除主表中被外鍵應用的數據。

  • SET_NULL 設置為NULL,僅在該字段 null=True允許為 null時可用

  • SET_DEFAULT 設置為默認值,僅在該字段設置了默認值時可用

  • SET() 設置為特定值或者調用特定方法,例如:

    from django.conf import settings
    from django.contrib.auth import get_user_model
    from django.db import medels
    
    
    def get_sentinel_user():
        return
    
    get_user_model().objects.get_or_create(username='deleted')[0]
    
    class UserModel(models.Model):
        user = models.ForeignKey(
        	settings.AUTH_USER_MODEL,
            on_delete=models.SET(get_sentinel_user),
        )
    
  • DO_NOTHING 不做任何操作,如果數據庫前置指明級聯性,此選項會拋出 IntegrityError 異常。

商品分類表

id category
1 蔬菜
2 電腦

商品信息表

id goods_name cid
1 冬瓜 1
2 筆記本 2
3 茄子 1
  1. 當模型字段的 on_delete=CASCADE,刪除蔬菜(id=1),則在外鍵cid=1的商品 id1和3就被刪除。
  2. 當模型字段的 on_delete=PROTECT,刪除蔬菜,mysql自動檢查商品信息表,有沒有cid=1的記錄,有則提示必須先移除掉商品信息表中,id=1的所有記錄以后才能刪除蔬菜。
  3. 當模型字段的 on_delete=SET_NULL,刪除蔬菜以后,對應商品信息表,cid=1的數據的 cid 全部被改成 cid=null
  4. 當模型字段的 on_delete=SET_DEFAULT,刪除蔬菜以后,對應商品信息表,cid=1的數據記錄的 cid 被設置默認值。

6.3 數據遷移

將模型類中定義表架構的代碼轉換成 SQL同步到數據庫中,這個過程就是數據遷移。django中的數據遷移,就是一個類,這個類提供了一系列的終端命令,幫我們完成數據遷移的工作。

1. 生成遷移文件

所謂的遷移文件,是類似模型類的遷移類,主要是描述了數據表結構的類文件。

python manage.py makemigrations

2. 同步到數據庫中

python manage.py migrate

補充: 在django內部提供了一系列的功能,這些功能也會使用到數據庫,所以在項目搭建以后第一次數據遷移的時候,會看到django項目中其他的數據表被創建了,其中就有一個 django內置的 admin站點管理。

# admin站點默認是開啟狀態的,我們可以通過http://127.0.0.1:8000/admin
# 這個站點必須有個管理員賬號登錄,所以我們可以在第一次數據遷移,有了數據表以后,就可以通過以下終端命令來創建一個超級管理員賬號。
python manage.py createsuperuser


image-20220214171252302

image-20220214171325673

-- 作業:
-- 1. 完成學生的創建,導入上面的數據。
-- 2. 使用原來學過的SQL語句,然后對上面導入的學生信息,完成增刪查改的操作。
   -- 2.0 查詢所有的學生信息(name,age)
       SELECT name,age FROM db_student
   -- 2.1 查詢年齡在18-20之間的學生信息[name,age,sex]
       select name,age,sex from db_student where age >=18 and age <=20;
   -- 2.2 查詢年齡在18-20之間的男生信息[name,age,sex]
       select name,age,if(gender=1,'男','女') as sex from db_student where age >=18 and age <=20 and gender=1;
   -- 2.3 查詢401-406之間所有班級的學生信息[name,age,sex,class]
       select name,age,sex,class from db_student where class between 401 and 406;
   -- 2.4 查詢401-406之間所有班級的總人數
       select count(1) as c from db_student where class between 401 and 406;
   -- 2.5 添加一個學生,男,劉德華,302班,17歲,"給我一杯水就行了。",'2020-11-20 10:00:00','2020-11-20 10:00:00'
       insert into db_student (name,sex,class, age, description, created_time,updated_time) values ('劉德華',1,'302', 17, "給我一杯水就行了。",'2020-11-20 10:00:00','2020-11-20 10:00:00');
   -- 2.6 修改劉德華的年齡,為19歲。
       update db_student set age=19 where name='劉德華';
   -- 2.7 劉德華畢業了,把他從學生表中刪除
       delete  from db_student where name='劉德華';
   -- 2.8 找到所有學生中,年齡最小的5位同學和年齡最大的5位同學
       select * from db_student order by age asc limit 5;
       select * from db_student order by age desc limit 5;
   -- 2.9 【進階】找到所有班級中人數超過4個人班級出來
       select class,count(id) as total from db_student group by class having total >= 4;
   -- 2.10【進階】把上面2.8的要求重新做一遍,改成一條數據實現
       (select * from db_student order by age asc limit 5) union all (select * from db_student order by age desc limit 5);

6.4 數據庫基本操作

6.4.1 添加記錄

注意:::

"在講django1.x版本時,增刪改查的模型類對象前都有一個 models 此處應該:"
student = models.Student.objects.filter(pk=1).first()
student.delete()


"模型類前面多了一個 models  "

(1)save方法

通過創建模型類對象,執行對象的save()方法,保存到數據庫中。

student = Student(
	name="劉德華",
    age = 50,
    gender=1,
    birthday="1968-12-23"
)
student.save()
print(student.id)  # 判斷是否新增有id

(2)create方法

通過模型類.objects.create()保存,返回生成的模型類對象。

student = Student.objects.create(
	name="jack",
    age=22,
    gender=1,
    birthday="2000-02-18"
)
print(student.id)

6.4.2 基礎查詢

ORM 中針對查詢結果的限制,提供了一個查詢集[QuerySet],這個QuerySet,是ORM 中針對查詢結果進行保存數據的一個類型,我們可以通過了解這個QuerySet進行使用,達到查詢優化,或者限制查詢結果數量的作用。

queryset很重要

"""
l1 = [1,2,3]
准確的說 l1 叫做 列表對象!!
"""


"""
all函數: 返回的是一個queryset ==> 非常接近列表數據,里面的元素是模型類對象

about queryset: QuerySet(查詢集)是一個類似於list的數據類型,里面的元素是統一類型,
如:模型類對象/字典...等

"""
(1)all()

查詢所有對象,返回queryset對象,查詢集,也稱查詢結果集,

QuerySet,表示從數據庫中獲取的對象集合。

# API 1. all函數: 返回的是一個queryset ==> 非常接近列表數據,里面的元素是模型類對象
# 查詢所有學生
student_list = Student.objects.all()
print("student>>>:", student_list)
(2)first() last()
" : 返回的是模型類對象!!!!!"

# 查詢第一個學生
stu = Student.objects.all()[0]
print(stu.name)
print(stu.age)

# print(student_list.name)  # 肯定是不可以的,內部的對象才可以用 .name

stu = Student.objects.first()
print(stu.name)
stu = Student.objects.last()
print(stu.name)
**(3)filter() **
"""
filter()方法:where語句  ==> 返回的是queryset查詢集
"""
# 查詢所有的女生
student_list = Student.objects.filter(gender=0)
student_list = Student.objects.filter(gender=0, age=22)  # 邏輯 與,
# student_list>>>: <QuerySet [<Student: 小雨 22>]>
print("student_list>>>:", student_list)
**(4)exclude() **
": 排除符合條件的記錄 與 filter相反"

# 查詢除了張三以外的所有學生記錄
student_list = Student.objects.exclude(name="張三")
print("student_list>>>:", student_list)
(5)get()
"查詢結果必須是有且只有一條符合條件的記錄,所以返回的是一個查詢到的模型類對象"


stu = Student.objects.get(gender=0)  # 結果多了報錯
stu = Student.objects.get(gender=2)  # 沒有結果報錯
stu = Student.objects.get(id=5)  # stu>>>: 王五 22
print("stu>>>:", stu)
(6)order_by()
" 是queryset類型的一個內置方法,返回是一個queryset"

# 將所有學生按照年齡從高到底排序
student_list = Student.objects.all().order_by('-age')
student_list = Student.objects.all().order_by('-age', '-id')  # 默認升序,加 - 變成降序排列
print("student_list>>>:", student_list)
(7)count()
"返回int對象,也是queryset的內置方法"

# 查詢學生個數
count = Student.objects.all().count()
print("count>>>:", count)
# 查詢女生的個數
count1 = Student.objects.filter(gender=0).count()
print("count>>>:", count1)
(8)exist()
"也是queryset的內置方法,判斷是否存在記錄,返回的是一個布爾值"
# 相比於我們的判斷,性能更高

# 查詢學生表中是否存在記錄
res = Student.objects.all().exists()
print("result>>>:", res)  # True
(9)values() 和 values_list()
"""
翻譯成的是sql中的 select語句
依然得到queryset,但是里面不是模型類對象,而是字典!!!
"""

student_list = Student.objects.all().values("name", "age")
student_list = Student.objects.all().values_list("name", "age")
print("student_list>>>:", student_list)
student_list>>>: <QuerySet [('張三', 18), ('李四', 33), ('王五', 22)]>

# values用的比較多!!!方便我們序列化
import json
print(json.dumps(list(student_list), ensure_ascii=False))

# 查詢所有男生的姓名和年齡
student_list = Student.objects.filter(gender=1).values("name", "age")
print("student_list>>>:", student_list)
student_list>>>: <QuerySet [{'name': '張三', 'age': 18}, {'name': '李四', 'age': 33}, {'name': '王五', 'age': 22}]>

**(10)distinct() **
"""
去重方法,queryset類型內置方法
已經包含id主鍵的時候根本不可能重復,所以也就沒有去重的必要
當查詢有重復可能的字段的時候,就有必要了。
"""
Student.objects.all().distinct()  # 無法去重

res = Student.objects.values("age").distinct()  # 此時可能重復,才能去重
print("result>>>:", res)

6.4.3 模糊查詢

(1)模糊查詢 之 contains

說明:如果要包含%無需轉義,直接寫即可。

例:查詢姓名包含 ’華‘ 的學生。

Student.objects.filter(name__contains="華")
(2)模式查詢 之 startswith,endswith

例:查詢姓名以 “文” 結尾的學生

Student.objects.filter(name__endswith="文")

以上運算符都區分大小寫,在這些運算符前加上 i 表示不區分大小寫,如:iexact,icontains,istartswith,iendswith。

(3)模糊查詢 之 isnull

例:查詢個性簽名不為空的學生。

Student.objects.filter(description__isnull=True)
(4)模糊查詢 之 比較查詢

gt --- greater then

gte--- greater then equal

lt --- less then

lte --- less then equal

# gt lt gte lte
# 查詢
stu1_list = Student.objects.filter(age__gt=30)  # 年齡大於30
stu2_list = Student.objects.filter(age__gte=30) # 年齡大於等於30
stu3_list = Student.objects.filter(age__lte=30) # 年齡小於等於30
stu4_list = Student.objects.filter(age__lt=30) # 年齡小於30
print(stu1_list)
print(stu2_list)
print(stu3_list)
print(stu4_list)
(5)模糊查詢 之 range
# 查詢年齡在20-30之間的學生
stu_list = Student.objects.filter(age__range=(20, 30))
print(stu_list)
(6)模糊查詢 之 in
# 查詢年齡是 22, 33的所有學生
stu_list = Student.objects.filter(age__in=[22, 30])
print(stu_list)
(7)模糊查詢 之 日期查詢

year、month、day、week_day、hour、minute、second:對日期時間類型的屬性進行運算。

# 查詢出生在1992年2月的學生
stu_list = Student.objects.filter(birthday__year=1992, birthday__month=2)
print(stu_list)

6.4.4 進階查詢

比較難,只是用的沒有上面兩種方式多

(1)F查詢

之前的查詢都是對象的屬性與常量值比較,兩個屬性怎么比較呢?

答:使用 F對象,被定義在django.db.models中。

語法如下:

"""F對象:2個字段的值比較"""
# 獲取從添加數據以后被改動過數據的學生
from django.db.models import F
# SQL: select * from db_student where created_time=updated_time;
student_list = Student.objects.exclude(created_time=F("updated_time"))
print(student_list)


# 1. F函數/對象:兩個屬性比較,使用F對象
查詢語文成績大於數學成績的學生
stu_list = Student.objects.filter(chinese_score__gt=F("math_score"))
print(stu_list)
(2)Q查詢

多個過濾器逐個調用表示邏輯與關系,同sql語句中where部分的and關鍵字。

# 邏輯判斷
stu_list = Student.objects.filter(gender=0, age=19)  # 表示且
stu1_list = Student.objects.filter(gender=0).filter(age=25)  # 表示且

# 年齡大於等於30 或 性別為女
stu2_list = Student.objects.filter(Q(age__gte=30) | Q(gender=0))

# 年齡大於等於30 且 性別非女
stu2_list = Student.objects.filter(Q(age__gte=30) & ~Q(gender=0))
print(stu2_list)

如果需要實現邏輯或or的查詢,需要使用Q()對象結合|運算符,Q對象被義在django.db.models中。

語法如下:

Q(屬性名__運算符=值)
Q(屬性名__運算符=值) | Q(屬性名__運算符=值)

Q對象可以使用&、|連接,&表示邏輯與,|表示邏輯或

例:查詢年齡大於20,或編號小於30的學生,只能使用Q對象實現

Student.objects.filter(Q(age__gt=20) | Q(pk__lt=30))

Q對象左邊可以使用~操作符,表示非not。但是工作中,我們只會使用Q對象進行或者的操作,只有多種嵌套復雜的查詢條件才會使用&和~進行與和非得操作

(3)聚合函數 aggregate (重點!!!!!)

使用aggregate()過濾器調用聚合函數。聚合函數包括:Avg 平均,Count 數量,Max 最大,Min 最小,Sum 求和,被定義在django.db.models中。

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

aggregate()  返回的是一個字典
# 查詢所有學生的平均年齡
ret1 = Student.objects.aggregate(avg_age=Avg("age"))  # 也可以自定鍵名
ret2 = Student.objects.aggregate(Avg("age"))
print(ret1)  # {'avg_age': 24.1667}
print(ret2)  # {'age__avg': 24.1667}

# 語文最高的成績
ret = Student.objects.aggregate(max_chi_score=Max("chinese_score"))
print(ret)  # {'max_chi_score': 100}
(4)分組函數 annotate() 重點!!!!
#annotate()  values在annotate前對應==> group by字段
# 查詢不同性別學生的語文平均成績
ret = Student.objects.values("gender").annotate(avg_chi=Avg("chinese_score"))
print(ret)
# <QuerySet [{'gender': 1, 'avg_chi': 95.3333}, {'gender': 0, 'avg_chi': 99.6667}]>

# 查詢每個班級的數學平均成績
ret = Student.objects.values("classmate").annotate(avg_math=Avg("math_score"))
print(ret)

# 思考:按照所有的group by,也就是主鍵分組,也就是每個學生單獨一個組,
# 在單表之下,沒有什么意義,只有在關聯表中才有意義
res = Student.objects.all().annotate(avg_math=Avg("math_score"))
print(res)
(5)原生SQL
# 原生sql,得到的結果只能循環提取,不能有很多其他操作

ret = Student.objects.raw("select id,name,age from db_student")
# 這樣執行獲取的結果無法通過queryset進行操作讀取,只能循環提取
print(ret, type(ret))
for item in ret:
    print(item, type(item), item.name)
   

6.4.5 修改記錄

(1)使用save更新數據
# 方式1:基於模型對象 save操作  不推薦
stu = Student.objects.get(name="李四")
print(stu.name, stu.age)
stu.chinese_score = 88
stu.save()  # 將stu中所有屬性都更新一邊,性能很差!!!!


# save之所以能提供給我們添加數據的同時,還可以更新數據的原因?
# save會找到模型的字段的主鍵id的值,
# 主鍵id的值如果是none,則表示當前數據沒有被數據庫,所以save會自動變成添加操作
# 主鍵id有值,則表示當前數據在數據庫中已經存在,所以save會自動變成更新數據操作


(2)update更新(推薦)

使用模型類.objects.filter().update(),會返回受影響的行數

# 方式2:queryset對象的update方法,queryset才能調用,
# update是全局更新,只要符合更新的條件,則全部更新,因此強烈建議加上條件!!!

stu_list = Student.objects.filter(name="李四").update(chinese_score=100, math_score=99)
stu2_list = Student.objects.filter(age__gt=30).update(chinese_score=101, math_score=101)
print(stu_list)
print(stu2_list)

# 將年齡小於30歲的學生,語文成績降低20分
from django.db.models import F, Q
Student.objects.filter(age__lt=30).update(chinese_score=F("chinese_score")-20)

6.4.6 刪除記錄

刪除記錄也有兩種方法

(1)模型類對象.delete
# 1:基於模型類對象刪除,內置於模型類對象中的delete方法
pk2=2 == id=2
    # 
(2)模型類.objects.filter().delete()
# 2:基於queryset刪除,兩個delete不同,內置於queryset中的delete方法
Student.objects.filter(chinese_score__lt=80).delete()

代碼:

# 1. 先查詢到數據模型對象。通過模型對象進行刪除
# student = Student.objects.filter(pk=13).first()
# student.delete()


# 2. 直接刪除
ret = Student.objects.filter(pk=100).delete()
print(ret)
# 務必寫上條件,否則變成了清空表了。ret = Student.objects.filter().delete()

6.5 創建關聯模型

-- student

id name  age class_name class_tutor class_num
1  rain  22  s12        jack        1
2  alvin 24  s12        jack        1


-- 一對多,一定是在多的表中創建關聯字段


-- 多對多,多對多的關系的確立是通過創建第三張關系表來完成的


-- 一對一,垂直分割,絕對的一對一
# 相當於把一個表分割成兩個及以上部分,


create table student(
    id int primary key auto_increment,
    name varchar(32),
    class_id int not null,
    foreign key (class_id) references class(id) on delete cascade
);

create table class(
    id int primary key auto_increment,
    name varchar(32)
);

create table course(
    id int primary key auto_increment,
    name varchar(32)
);

create table stu2course(
    id int primary key auto_increment,
    student_id int not null,
    course_id int not null,
    foreign key (student_id) references student(id),
    foreign key (course_id)  references course(id)
);

實例:我們來假定下面這些概念,字段和關系

  • 班級模型:班級名稱,導師。
  • 課程模型:課程名稱,講師。
  • 學生模型:學生有 姓名,年齡,只有一個班級,所以和班級表是一對多的關系(one-to-many),選修了多個課程,所以和課程表是多對多的關系(many-to-many)
  • 學生詳情:學生的家庭地址,手機號,郵箱等詳細信息,和學生模型應該是一對一的關系(one-to-one)。

模型建立如下:

from django.db import models

# Create your models here.


class Clas(models.Model):
    name = models.CharField(max_length=32, unique=True, verbose_name="班級名稱")
    
    class Meta:
        db_table = "db_class"
   

class Course(models.Model):
    
    title = models.CharField(max_length=32, verbose_name="課程名稱")
    
    # 多對多時關系表的創建在哪個表中都可以
    # students = models.ManyToManyField("Student", db_table="db_stu2course")
    
    class Meta:
        db_table = "db_course"
  

class Student(models.Model):
    
    gender_choices = (
    	(0, '女'),
        (1, '男'),
        (2, '保密'),
    )
    
    name = models.CharField(max_length=32, unique=True, verbose_name="姓名")
    age = models.SmallIntegerField(verbose_name="年齡", default=18)
    gender = models.SmallIntegerField(choices=gender_choices)
    
    # 建立一對多的關系:會在數據庫中創建一個關聯字段,名稱叫做clas_id
    # 一對多的關系必須在多的表中建立,多對多則不用
    clas = models.ForeignKey(to="Clas", on_delete=models.CASCADE, db_constraint=False)
    # db_constraint=False表示不加約束,則僅僅創建一個clas_id的字段,沒有外鍵約束
    
    # 建立多對多的關系:創建第三張關系表
    courses = models.ManyToManyField("Course", db_table="db_stu2course")
    # 這句話可以幫我們創建第三張關系表,里面有三個字段。
    
    # 一對一的關系:建立關聯字段,在數據庫中創建一個關聯字段,名稱叫做stu_detail_id
    stu_detail = models.OneToOneField("StudentDetail", on_delete=models.CASCADE)
    
    class Meta:
        db_table = "db_student"
  

class StudentDetail(models.Model):
    
    tel = models.CharField(max_length=11)
    addr = models.CharField(max_length=32)

    class Meta:
        db_table = "db_stu_detail"
    

6.6 關聯添加

6.6.1 一對一 與 一對多

# 添加記錄:一對多 與 一對一的關聯屬性

stu = Student.objects.create(name="張三", age=22, gender=1, clas_id=2, stu_detail_id=1,)
stu = Student.objects.create(name="李四", age=24, gender=1, clas_id=2, stu_detail_id=2,)
print(stu.name)
print(stu.age)
print(stu.clas_id)  # 2
print(stu.stu_detail_id)  # 2
print(stu.clas)  # 模型類對象,因為一個學生只能有一個班級類對象 Clas objects (2)

# 該學生的班級對象的名稱
print(stu.clas.name)  # Clas.objects.get(pk=2)
# 計算機2班

stu = Student.objects.get(name="李四")
print(stu.name)  # 李四
print(stu.age)  # 24
print(stu.clas_id)  # 2
print(stu.clas.name)  # ORM可以幫調出來 計算機2班
print(stu.stu_detail.addr)  # 北京
print(stu.stu_detail.tel)  # 110

6.6.2 多對多

# ################多對多的關聯記錄的:增刪改查!!!!

# 方式一:新添加的學生,綁定課程
stu = Student.objects.create(name="rain", age=28, gender=1, clas_id=3, stu_detail_id=3)
# stu是Student類的對象,所以就會有courses屬性,stu.courses是第三張表的入口
# 添加多對多的數據,比如stu這個學生綁定兩門課程,思修和邏輯學25
c1 = Course.objects.get(title="思修")
c2 = Course.objects.get(title="邏輯學")
stu.courses.add(c1, c2)  # 這句話ORM會找到關系表,添加記錄c1和c2,stuid 3綁定兩個課程2和5


# 添加多對多方式二:給查詢出來的 張三綁定課程
stu = Student.objects.get(name="張三")
stu.courses.add(3, 4)  # 籃球和毛概,直接放課程id即可


# 添加多對多方式三
stu = Student.objects.get(name="李四")
stu.courses.add(*[1, 3])  # 學生綁定的課程不是寫死的,后端處理完格式之后就是列表,所以就用此方法比較多


# 刪除多對多記錄
stu = Student.objects.get(name="李四")
stu.courses.remove(1)  # 刪除李四的課程1近代史


# clear清除方法
stu = Student.objects.get(name="rain")
stu.courses.clear()


# set 重置方法,將原來已有的綁定去掉,重新綁定新的
stu = Student.objects.get(name="李四")
stu.courses.set([1, 2])


# all  查
# 查詢李四所報課程的名稱
stu = Student.objects.get(name="李四")
courses = stu.courses.all()  # 找到stu_id2的,查到course_id為1,2再去course中
print(courses)
# <QuerySet [<Course: Course object (1)>, <Course: Course object (2)>]>
# <QuerySet [<Course: 近代史>, <Course: 思修>]>  因為在course類中定義了str方法
print(courses.values("title"))

6.7 關聯查詢

6.7.1 基於對象查詢(子查詢)

"""
    正向查詢:通過關聯屬性查詢 屬於正向查詢,反之則成為反向查詢

    反向查詢

    基於對象的關聯查詢(子查詢)
    :param request:
    :return:
"""

"# ####################一對多的關聯,學生和班級"

# 查詢張三所在的的班級名稱(正向)
stu = Student.objects.get(name="張三")
print(stu.clas.name)  # 基於對象的關聯字段

# 查詢計算機2班有哪些學生 (反向)
clas = Clas.objects.get(name="計算機2班")
ret = clas.student_set.all()  # _setORM要求加的,表名這是一個集合變量
# 反向查詢方式一: 按照表名小寫_set
print(ret)  # <QuerySet [<Student: 張三>, <Student: 李四>]>

# 反向查詢方式二::related_name
clas = Clas.objects.get(name="計算機2班")
print(clas.stu_list.all()) # <QuerySet [<Student: 張三>, <Student: 李四>]>
# stu_list是Student類中clas的related_name值,自定義的


"# ##########################  一對一的關聯"

# 查詢李四的手機號,正向查詢
stu = Student.objects.get(name="李四")
print(stu.stu_detail.tel)

# 查詢手機號為120的學生姓名和年齡,反向查詢
stu_d = StudentDetail.objects.get(tel="120")

# 方式一:反向查詢,表名小寫
print(stu_d.student.name)
print(stu_d.student.age)

# 方式二:related_name
stu_d = StudentDetail.objects.get(tel=120)
print(stu_d.stu_det.name)
print(stu_d.stu_det.age)


"# ######################### 多對多關聯查詢"

# 查詢張三所報課程的名稱,正向
stu = Student.objects.get(name="張三")
print(stu.courses.all())

# 查詢報名近代史的學生的姓名和年齡,反向
course = Course.objects.get(title="近代史")
# 反向查詢方式一:表名小寫_set
stu_lis = course.student_set.all()
print(stu_lis)

# 反向查詢方式二:related_name
course = Course.objects.get(title="近代史")
print(course.stus_list.all().values("name", "age"))


  1. 正向查詢按字段。
  2. 反向查詢按表名小寫,或者 related_name

6.7.2 基於雙下划線查詢(join查詢)

"""
    基於雙下划線 -- join
    :param request:
    :return:
"""
# ############# 一對多案例

# 查詢張三的年齡
ret = Student.objects.filter(name="張三").values("age")
print(ret)  # <QuerySet [{'age': 22}]>

# 1. 查詢年齡大於22的學生的姓名以及所在班級名稱

# 方式一:Student作為基表,此時正向查詢
ret = Student.objects.filter(age__gt=22).values("name", "clas__name")
print(ret)
# 方式二:Clas作為基表
ret2 = Clas.objects.filter(stu_list__age__gt=22).values("stu_list__name", "name")
print(ret2)


# 2. 查詢計算機2班有哪些學生
ret = Clas.objects.filter(name="計算機2班").values("stu_list__name")
print(ret)


# #################### 多對多案例

# 3. 查詢張三所報課程名稱
ret = Student.objects.filter(name="張三").values("courses__title")
print(ret)

# 4. 查詢選修了近代史這門課程的學生的姓名和年齡
ret = Course.objects.filter(title="近代史").values("stus_list__name","stus_list__age")
print(ret)

# 5. 一對一,查詢李四的手機號
ret = Student.objects.filter(name="李四").values("stu_detail__tel")
print(ret)


# ################# 進階

# 6. 查詢手機號是120的學生的姓名和所在班級
# 方式1: 以StudentDetail作為基表,這種方式存在連續跨表的情況
ret = StudentDetail.objects.filter(tel="120").values("stu_det__name", "stu_det__clas__name")
print(ret)

# 方式2: 以Student作為基表就不存在連續跨表的問題
ret = Student.objects.filter(stu_detail__tel=110).values("name", "clas__name")
print(ret)

6.7.3 關聯分組查詢

# ############### 分組查詢
from django.db.models import Avg, Count, Max,Min
ret = Student.objects.values("gender").annotate(c=Count("name"))
print(ret)  # <QuerySet [{'gender': 0, 'c': 1}, {'gender': 1, 'c': 3}]>

# 1. 查詢每一個班級的名稱以及學生個數
from django.db.models import Avg, Count, Max, Min
res = Clas.objects.values("name").annotate(c=Count("stu_list__name"))
print(res)
"""
<QuerySet [{'name': '網絡工程1班', 'c': 1}, {'name': '網絡工程2班', 'c': 0}, 
{'name': '計算機1班', 'c': 1}, {'name': '計算機2班', 'c': 2}, {'name': '軟件1班', 'c': 0}]>
"""

# 2. 查詢每一個學生的姓名以及選修課程的個數
from django.db.models import Avg, Count, Max, Min
res = Student.objects.values("name").annotate(c=Count("courses__title"))
print(res)

# 2.1 查詢每一個學生的姓名,年齡以及選修課程的個數
from django.db.models import Avg, Count, Max, Min
res = Student.objects.values("name", "age").annotate(c=Count("courses__title"))
print(res)
# 更優解法
res1 = Student.objects.all()
res2 = Student.objects.all().annotate(c=Count("courses__title"))
print(res1)  # <QuerySet [<Student: 張三>, <Student: 李四>, <Student: rain>, <Student: 小雨>]>
print(res2)  # 和上面的結果看似一樣,但是實際上對象內部有一個c屬性
"所以可以這樣:"
res3 = Student.objects.all().annotate(c=Count("courses__title")).values("name", "age", "gender", "c")
print(res3)


# 3. 查詢每一個課程名稱以及選修學生的個數
res = Course.objects.all().annotate(c=Count("stus_list__name")).values("title", "c")
print(res)


"where分組之前過濾,having分組之后過濾"

# 4. 查詢選修課程個數大於 1 的學生姓名以及 選修課程個數
res = Student.objects.all().annotate(c=Count("courses__title")).filter(c__gt=1).values("name", "c")
print(res)


# 5. 查詢每一個學生的姓名以及選修的課程個數 並 按照選修課程個數進行排序低到高
res = Student.objects.all().annotate(c=Count("courses__title")).order_by("c").values("name", "c")
print(res)

6.8 項目練習

學生管理系統


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM