六、模型層(ORM)重難點
Django中內嵌了 ORM框架,不需要直接編寫SQL語句進行數據庫操作,而是通過定義模型類,操作模型來完成對數據庫中表的增刪改查和創建等操作。
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數據庫。
-
使用MySQL數據庫首先需要安裝驅動程序
pip3 install pymysql
-
在 django的工程同名子目錄的
__init__.py
文件中添加如下語句from pymysql import install_as_MySQLdb install_as_MySQLdb() # 讓pymysql以 MySQLDB的運行模式和django的ORM對接運行
作用是讓django 的 ORM能以mysqldb的方式來調用pymysql。
-
修改DATABASES配置信息
DATABASES = [ 'default': { 'ENGINE': 'django.db.backends.mysql', 'HOST': '127.0.0.1', # 數據庫主機 'PORT': 3306, # 數據庫端口 'USER': 'root', # 數據庫用戶名 'PASSWORD': '123', # 數據庫用戶密碼 'NAME': 'student', # 數據庫名字 } ]
-
在MySQL中創建數據庫
create database student; # mysql8.0默認就是utf8mb4; create database student default charset=utf8mb4; # mysql8.0之前的版本
-
注意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 |
- 當模型字段的 on_delete=CASCADE,刪除蔬菜(id=1),則在外鍵cid=1的商品 id1和3就被刪除。
- 當模型字段的 on_delete=PROTECT,刪除蔬菜,mysql自動檢查商品信息表,有沒有cid=1的記錄,有則提示必須先移除掉商品信息表中,id=1的所有記錄以后才能刪除蔬菜。
- 當模型字段的 on_delete=SET_NULL,刪除蔬菜以后,對應商品信息表,cid=1的數據的 cid 全部被改成 cid=null
- 當模型字段的 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
-- 作業:
-- 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"))
- 正向查詢按字段。
- 反向查詢按表名小寫,或者 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 項目練習
學生管理系統