Django - ORM操作
目錄
ORM介紹
ORM的兩種方式
db first 先連線資料庫 -> ...
code first 先建立類 -> sqlachemy、Django、大多數都是
Django ORM
ORM:Object Relational Mapping(關係物件對映)
類名 ->> 資料庫中的表名 類屬性 ->> 資料庫裡的欄位 類例項 ->> 資料庫表裡的一行資料 obj.name..... ->> 類例項物件的屬性
Django orm的優勢:Django的orm操作本質上會根據對接的資料庫引擎,翻譯成對應的sql語句;所有使用Django開發的專案無需關心程式底層使用的是MySQL、Oracle、sqlite....,如果資料庫遷移,只需要更換Django的資料庫引擎即可
QuerySet資料型別介紹
QuerySet特點:
可迭代的
可切片
惰性計算:等於一個生成器,.objects.all()或者.filter()等都只是返回了一個QuerySet的查詢結果集物件,它並不會馬上執行sql,而是當呼叫QuerySet的時候才執行。
快取機制:每一次資料庫查詢結果QuerySet都會對應一塊快取,再次使用該QuerySet時,不會發生新的SQL操作
這樣減小了頻繁操作資料庫給資料庫帶來的壓力
但是有時候取出來的資料量太大會撐爆快取,可以使用迭代器解決這個問題:
models.Publish.objects.all().iterator()
建立ORM類
1. 在models裡建立表的類
/app/models.py from django.db import models # 表名為app01_userinfo class UserInfo(models.Model): # 自動建立id列,自增,主鍵 # 列名,字串型別,指定長度 username = models.CharField(max_length=32) password = models.CharField(max_length=64) email = models.EmailField(max_length=19)
類的欄位和引數詳見[欄位和引數]
2. 註冊APP
/./settings.py
INSTALLED_APPS = [
...,
'app01',
]
3. 執行命令,每次更改表結構都要重複一遍
python manage.py makemigrations -> 生成表結構的快取
python manage.py migrate -> 建立表結構
4. 預設使用sqlite3資料庫,可修改為mysql
/./settings.py -> DATABASES
********** 注意 ***********
Django預設使用MySQLdb模組連結MySQL
主動修改為pymysql,在project同名資料夾下的__init__檔案中新增如下程式碼即可:
import pymysql
pymysql.install_as_MySQLdb()
增刪改查
1.增
/app/views.py
from app01 import models
def orm(request):
# 直接傳入引數
models.UserInfo.objects.create(username='root',password='123')
# 傳入字典
dic = {'username': 'eric', 'password': '666'}
models.UserInfo.objects.create(**dic)
# 另一種增加方式
obj = models.UserInfo(username='alex',password='123')
obj.save()
2.查
result = models.UserInfo.objects.all()
result = models.UserInfo.objects.filter(user='root',psd='123') -> filter傳入字典也可 **dic
=> QuerySet, Django的一種列表, [], 內部元素是.obj => [obj(id,username),obj]
# 轉化為字典輸出
.all().values('id','caption') -> [{'id:1,'username':'alex'},{},{}]
# 轉化為tuple輸出
.all().values_list('id','caption') -> [(1,'alex'),(),()]
# 取第一個obj
.filter(xxx).first() -> 不存在返回None
=> 用get取單條資料,如果不存在,直接報錯
=> models.UserInfo.objects.get(id=nid)
# 計數
.filter(name='seven').count()
# 切片
.all()[10:20]
.all()[::2]
.all()[6] # 索引
# 去重
.distinct()
# 排序
.filter(name='seven').order_by('id') -> asc
.filter(name='seven').order_by('-id') -> desc
3.刪
models.UserInfo.objects.filter(username="alex").delete()
4.改
models.UserInfo.objects.filter(id=3).update(password="69") # 可新增**kwargs形式
# 或者先查詢物件再修改儲存
obj = models.tb.objects.get(id=1)
obj.c1 = '111'
obj.save() # 修改單條資料
特殊的判斷語句(神奇的雙下劃線1)
# 大於小於
.filter(id__gt=1) -> > 1
.filter(id=1) -> = 1
.filter(id__lt=1) -> < 1
.filter(id__lte=1) -> <= 1
.filter(id__gte=1) -> >= 1
.exclude(id__gt=1) -> != 1 exclude 除了...與filter相反
.filter(id__gt=1, id__lt=10) -> 1< x <10
# 範圍range
.filter(id__range=[1,3]) -> [1~3] bettwen + and
# 範圍in
.filter(id__in=[1,2,3]) -> in [1,2,3]
.exclude(id__in=[1,2,3]) -> in [1,2,3]
# 是否為空
.filter(name__isnull=True)
# 包含、開頭、結尾 __startswith, istartswith, endswith, iendswith
.filter(name__contains="ven")
.filter(name__icontains="ven") # i 忽略大小寫
# regex正則匹配,iregex 不區分大小寫
.get(title__regex=r'^(An?|The) +')
.get(title__iregex=r'^(an?|the) +')
# date
.filter(pub_date__date=datetime.date(2005, 1, 1))
.filter(pub_date__date__gt=datetime.date(2005, 1, 1))
# year、month、day、week_day
.filter(pub_date__year=2005)
.filter(pub_date__year__gte=2005)
# hour、minute、second
.filter(timestamp__hour=23)
.filter(time__hour=5)
.filter(timestamp__hour__gte=12)
進階查詢
F模組,用於獲取物件中的某一欄位(列)的值,並且對其進行操作;
from django.db.models import F # 首先匯入F模組 models.Book.objects.all().update(price=F('price')+1) # 每一本書的價格上調1塊錢
Q模組,用於構造複雜的查詢條件,使用邏輯關係(&與、|或、~非)組合進行多條件查詢;
雖然filter中可以使用 , 隔開表示關係與,但沒法表示或非的關係
from django.db.models import Q # 匯入Q模組 # 方式一: .filter( Q(id__gt=10) ) -> .filter( Q(id=8) | Q(id__gt=10) ) -> or .filter( Q( Q(id=8) | Q(id__gt=10) ) & Q(caption='root') ) -> and, or # 方式二: # 可以組合巢狀 # q1裡面的條件都是or的關係 q1 = Q() q1.connector = 'OR' q1.children.append(('id', 1)) q1.children.append(('id', 10)) q1.children.append(('id', 9)) # q2裡面的條件都是or的關係 q2 = Q() q2.connector = 'OR' q2.children.append(('c1', 1)) q2.children.append(('c1', 10)) q2.children.append(('c1', 9)) # con通過and的條件把q1和q2聯絡到一塊 con = Q() con.add(q1, 'AND') con.add(q2, 'AND') models.tb.objects.filter(con)
例項:查詢作者姓名中包含 方/少/偉/3字,書名不包含偉,並且出版社地址以山西開頭的書
book=models.Book.objects.filter( Q( Q(author__name__contains='方') | Q(author__name__contains='少') | Q(author__name__contains='偉') | Q(title__icontains='偉') ) & Q(publish__addr__contains='山西') ).values('title')
注意:Q查詢和非Q查詢混合使用,非Q查詢一定要放在Q查詢後面
extra方法
對不同的資料庫引擎可能存在移植問題(因為你在顯式的書寫SQL語句),儘量避免使用extra
a.對映 - select={'new_id':select count(1) from app01_usertype where id>%s'} - select_params=[1,] # 例: models.UserInfo.objects.all().extra( select={ 'n':"select count(1) from app01_utype WHERE id=%s or id=%s", 'm':"select count(1) from app01_uinfo WHERE id=%s or id=%s", }, select_params=[1,2,3,4] ) b.條件 - where=["foo='a' OR bar = 'a'", "baz = '%s'"], - params=['Lennon',] c.表 - tables=["app01_usertype"] d.排序 - order_by = ['-id'] # 例1: models.UserInfo.objects.extra( select={'new_id':select count(1) from app01_usertype where id>%s'}, select_params=[1,], where=['age>%s'], params=[18,], order_by=['-age'], tables=["app01_usertype'] ) -> 相當於: ''' select app01_userinfo.id, (select count(1) from app01_usertype where id>1) as new_id from app01_userinfo, app01_usertype where app01_userinfo.age>18 order by app01_userinfo.age desc ''' # 例2: current_user = models.UserInfo.objects.filter(username=username).first() # 當前使用者 1、models.Article.objects.all() # 查出每一篇文章 2、models.Article.objects.all().filter(user=current_user) # 查出當前使用者的所有文章 3、models.Article.objects.all().filter(user=current_user).extra(select={"filter_create_date":"strftime(‘%%Y/%%m‘,create_time)"}).values_list("filter_create_date") # 查出當前使用者的所有文章的create_time,並且只取出年份和月份
執行原生SQL的三種方式
1.使用extra方法
結果集修改器,一種提供額外查詢引數的機制 依賴model模型
2.使用raw方法
執行原始sql並返回模型 依賴model多用於查詢 book = Book.objects.raw("select * from hello_book") for item in book: print(item.title)
3.使用cursor遊標
不依賴model from django.db import connection, connections cursor = connection.cursor() # 或cursor = connections['default'].cursor() # 其中'default'是django資料庫配置的default,也可取別的值 cursor.execute("""SELECT * from auth_user where id = %s""", [1]) row = cursor.fetchone()
類的欄位和引數
欄位:字串、數字、時間、二進位制
AutoField(Field) -> 自定義自增列(必須加primary_key=True)
IntegerField(Field) -> 整數列
BooleanField(Field) -> 布林
GenericIPAddressField(Field) -> IP驗證(僅限django admin)
URLField(CharField) -> url驗證(僅限django admin)
# Django裡有很多的欄位型別在資料庫中都是Char型別,只是用於django admin便於區分
更多詳見:武沛齊的部落格 - Django
欄位的引數:
null -> db中是否可以為空
default='' -> 預設值
primary_key -> 是否主鍵
db_column -> 列名
db_index -> 是否可索引
unique -> 是否可唯一索引
unique_for_date -> 【日期】部分是否可索引
unique_for_month -> 【月】部分是否可索引
unique_for_year -> 【年】部分是否可索引
auto_now_add -> 建立時,自動生成時間
auto_now -> 更新時,自動更新為當前時間
# update方式不生效,先獲取再更改才生效
ctime = models.DateTimeField(auto_now_add=True)
UserGroup.objects.filter(id=1).update(caption='CEO') -> 不生效
obj = UserGroup.objects.filter(id=1).first()
obj.caption = "CEO" -> 生效,自動更新更改時間
obj.save()
# django admin中才生效的欄位
blank -> django admin是否可以為空
verbose_name='' -> django admin顯示欄位中文
editable -> django admin是否可以被編輯
help_text -> django admin幫助提示
choices=[] -> django admin中顯示下拉框
# 可避免連表查詢,提高效率,一般用於基本不變的選項
user_type_choices = (
(1, '超級使用者'),
(2, '普通使用者'),
(3, '普普通使用者'),
)
user_type_id = models.IntegerField(choices=user_type_choices,default=1)
error_messages -> 自定義錯誤資訊(字典型別)
# 字典的鍵:null, blank, invalid, invalid_choice, unique, unique_for_date
# 例:error_messages = {'null': "不能為空", 'invalid': '格式錯誤'}
validators -> django form ,自定義錯誤資訊(列表型別)
# 例:
from django.core.validators import RegexValidator
from django.core.validators import EmailValidator,URLValidator,DecimalValidator,\
MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator
error_messages={
'c1': '優先錯資訊1',
'c2': '優先錯資訊2',
'c3': '優先錯資訊3',
},
validators=[
RegexValidator(regex='root_\d+', message='錯誤了', code='c1'),
RegexValidator(regex='root_112233\d+', message='又錯誤了', code='c2'),
EmailValidator(message='又錯誤了', code='c3'), ]
更多錯誤資訊的使用方法參考武沛齊 - FORM
建立 Django admin使用者: python manage.py createsuperuser
Meta元資訊
class UserInfo(models.Model):
...
class Meta:
# 定義資料庫中生成的表名稱 預設 app名稱 + 下劃線 + 類名
db_table = "table_name"
# 聯合索引
index_together = [("pub_date", "deadline"),]
# 聯合唯一索引,一旦三者都相同,則會被Django拒絕建立。
可以同時設定多組約束。為了方便,對於只有一組約束的情況下,可以簡單地使用一維元素
unique_together = (("driver", "restaurant"),)
# admin後臺中顯示的表名稱
verbose_name = '使用者資訊'
# verbose_name加s,複數形式,不指定自動加s
verbose_name_plural =
# 預設排序
ordering=['-order_date'] # 按訂單降序排列,-表示降序,不加升序,加?表示隨機
ordering=['-pub_date','author'] # 以pub_date為降序,再以author升序排列
更多:https://docs.djangoproject.com/en/1.10/ref/models/options/
Admin拓展知識
觸發Model中的驗證和錯誤提示有兩種方式:
a. Django Admin中的錯誤資訊會優先根據Admin內部的ModelForm錯誤資訊提示,如果都成功,才來檢查Model的欄位並顯示指定錯誤資訊 b. 呼叫Model物件的 clean_fields 方法,如: # models.py class UserInfo(models.Model): username = models.CharField(max_length=32) email = models.EmailField(error_messages={'invalid': '格式錯了.'}) # views.py def index(request): obj = models.UserInfo(username='11234', email='uu') try: print(obj.clean_fields()) except Exception as e: print(e) return HttpResponse('ok') # Model的clean方法是一個鉤子,可用於定製操作,如:上述的異常處理。
Admin中修改錯誤提示
# admin.py from django.contrib import admin from model_club import models from django import forms class UserInfoForm(forms.ModelForm): username = forms.CharField(error_messages={'required': '使用者名稱不能為空.'}) email = forms.EmailField(error_messages={'invalid': '郵箱格式錯誤.'}) age = forms.IntegerField(initial=1, error_messages={'required': '請輸入數值.', 'invalid': '年齡必須為數值.'}) class Meta: model = models.UserInfo # fields = ('username',) fields = "__all__" class UserInfoAdmin(admin.ModelAdmin): form = UserInfoForm admin.site.register(models.UserInfo, UserInfoAdmin)
ORM連表的幾種型別
ORM一對多
當一張表中建立一行資料時,有一個單選的下拉框(可以被重複選擇)
建立表結構時關聯外來鍵
user_group = models.ForeignKey("UserGroup",to_field='uid') ->> obj(UserGroup) # 自動建立user_group_id列,存的是數字(關聯主鍵)
新增資料時關聯id或物件
方式一:建立資料時新增id關聯 models.UserInfo.object.create(name='root', user_group_id=1) 方式二:查詢obj物件進行關聯 user_group = models.UserGroup.objects.filter(id=1).first()
一對多自關聯
由原來的2張表,變成一張表!
# 例:回覆評論
class Comment(models.Model):
news_id = models.IntegerField() -> 新聞ID
content = models.CharField(max_length=32) -> 評論內容
user = models.CharField(max_length=32) -> 評論者
reply = models.ForeignKey('Comment',null=True,blank=True,related_name='xxxx') -> 回覆ID
# 注意:回覆的id必須是已經存在的評論的id
ORM多對多
在某表中建立一行資料是,有一個可以多選的下拉框
兩種建立方式
以下兩種建立方式建議都用,自動建立的只能關聯兩個表,自定義的可以不斷關聯
方式一:自定義關係表
可以直接操作第三張表,但無法通過欄位跨表查詢,查詢麻煩
class UserInfo(models.Model): ... class UserGroup(models.Model): ... # 建立中間表 class UserInfoToUserGroup(models.Model): user_info_obj = models.ForeignKey(to='UserInfo',to_field='nid') group_obj = models.ForeignKey(to='UserGroup',to_field='id') # 新增關聯資料: UserInfoToApp.objects.create(user_info_obj_id=1,group_obj_id=2)
方式二:Django自動建立關係表
可以使用欄位跨表查詢,但無法直接操作第三張表
class UserInfo(models.Model): ... # ManyToManyField欄位 class UserGroup(models.Model): user_info = models.ManyToManyField("UserInfo")
方式三:既自定義第三張關係表 也使用ManyToManyField欄位(雜交型別)
既可以使用欄位跨表查詢,也可以直接操作第3張關係表
注意:obj.m.all() 只有查詢和清空方法
# 例:博主粉絲關係 class UserInfo(AbstractUser): ... fans = models.ManyToManyField(to='UserInfo', through='UserFans', -> 指定關係表表名 through_fields=('user', 'follower')) -> 指定關係表字段 class UserFans(models.Model): ... user = models.ForeignKey(to='UserInfo', to_field='nid', related_name='users') follower = models.ForeignKey(to='UserInfo', to_field='nid', related_name='followers') class Meta: unique_together = [('user', 'follower'),]
多對多自關聯
(由原來的3張表,變成只有2張表)
把兩張表通過 choices 欄位合併為一張表
使用ManyToManyField欄位
1、查詢第三張關係表前面那一列:obj.m
2、查詢第三張關係表後面那一列:obj.userinfo_set
class Userinfo(models.Model):
sex=((1,'男'),(2,'女'))
gender=models.IntegerField(choices=sex)
m=models.ManyToManyField('Userinfo')
# 通過男士查詢女生
boy_obj=models.Userinfo.objects.filter(id=4).first()
res=boy_obj.m.all()
# 通過女士查詢男生
girl_obj=models.Userinfo.objects.filter(id=4).first()
res=girl_obj.userinfo_set.all()
ORM一對一
在某表中建立一行資料時,有一個單選的下拉框(下拉框中的內容被用過一次就消失了)
例如:原有含10列資料的一張表儲存相關資訊,經過一段時間之後,10列無法滿足需求,需要為原來的表再新增5列資料
r = models.OneToOneField(...)
# 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)
ORM連表操作
欄位引數
一對多ForeignKey()
to -> 要關聯的表名 to_field='uid', -> 要關聯的欄位,不寫預設關聯主鍵 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(func) def func(): return 10 class MyModel(models.Model): user = models.ForeignKey(...,on_delete=models.SET(func)) related_name=None, -> 反向操作時,使用的欄位名,用於替換【表名_set】 如: obj.表名_set.all() related_query_name=None, -> 反向操作時,使用的連線字首,用於替換【表名】 如: ...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中是否顯示關聯資料
多對多ManyToManyField()
symmetrical=None, -> 僅用於多對多自關聯時,指定內部是否建立反向操作的欄位 => 做如下操作時,不同的symmetrical會有不同的可選欄位 models.BB.objects.filter(...) => 可選欄位有:code, id, m1 class BB(models.Model): code = models.CharField(max_length=12) m1 = models.ManyToManyField('self',symmetrical=True) => 可選欄位有: code, id, m1, bb class BB(models.Model): code = models.CharField(max_length=12) m1 = models.ManyToManyField('self',symmetrical=False) through=None, -> 自定義第三張表時,用於指定關係表 through_fields=None, -> 自定義第三張表時,用於指定關係表中哪些欄位做多對多關係表 db_constraint=True, -> 是否在資料庫中建立外來鍵約束 db_table=None, -> 預設建立第三張表時,資料庫中表的名稱
一對一OneToOneField()
to -> 要關聯的表名 to_field='uid', -> 要關聯的欄位,不寫預設關聯主鍵 on_delete=None, -> 刪除關聯表中的資料時,當前表與其關聯的行的行為
跨表查詢(神奇的雙下劃線2)
獲取值時使用 . 連線
group_obj = models.UserGroup.objects.filter(id=1).first() # orm連表必須取單個物件 # 增 group_obj.user_info.add(1) -> 新增一個 group_obj.user_info.add(2,3,4) -> 新增多個 group_obj.user_info.add(*[1,2,3,4]) -> 新增*列表 # 刪 group_obj.user_info.remove(1) group_obj.user_info.remove(2,4) group_obj.user_info.remove(*[1,2,3]) group_obj.user_info.clear() -> 清除當前物件關聯的多對多資料 # 改 group_obj.user_info.set([3,5,7]) -> (不加*)只保留1-3,1-5,1-7,其它刪除 # 查 group_obj.user_info.all() -> 獲取所有相關的主機obj 的QuerySet group_obj.user_info.filter() ......
搜尋條件使用 __ 連線 (value、value_list、fifter)
obj = models.UserGroup.objects.filter(id=1).value('name','user_info__name').first() 在html裡也用obj.user_group__name
反查
# . 操作,獲取物件的QuerySet,表名小寫_set
user_info_obj.usergroup_set.add(group_obj)
user_info_obj.usergroup_set.remove(group_obj)
user_info_obj.usergroup_set.all()
user_info_obj.usergroup_set.filter()
......
# __操作,搜尋屬性,表名小寫__屬性
obj = models.UserInfo.objects.filter('usergruop__name').first()
設定反向查詢別名
related_query_name -> 反向查詢時用 obj.別名_set.all(),保留了_set
relatedname -> 反向查詢時用 obj.別名.all()
# 例如:
'''把男女表混合在一起,在程式碼層面控制第三張關係表的外來鍵關係'''
# models.py
class UserInfo(models.Model):
...
sex=((1,'男'),(2,'女'))
gender=models.IntegerField(choices=sex)
class U2U(models.Model):
b=models.ForeignKey(Userinfo,related_name='boy')
g=models.ForeignKey(Userinfo,related_name='girl')
# 寫到此處問題就來了,原來兩個外來鍵 對應2張表 2個主鍵,可以識別男女
# 現在兩個外來鍵對應1張表,反向查詢,無法區分男女了了
# object物件女.U2U.Userinfo.set object物件男.U2U.Userinfo.set
# 所以要加related_name設定反向查詢命名 對錶中主鍵加以區分
# 查詢方法
# 男:obj.a.all()
# 女:obj.b.all()
# views.py
def index(request):
#查詢 ID為1男孩 相關的女孩
boy_obj=models.UserInfo.objects.filter(id=1).first()
res = boy_obj.boy.all() # 得到U2U的物件再正向跨表
for obj in res:
print(obj.girl.name)
return HttpResponse('OK')
分組和聚合查詢
aggregate() 聚合函式
通過對QuerySet進行計算,返回一個聚合值的字典。
aggregate()中每一個引數都指定一個包含在字典中的返回值。即在查詢集上生成聚合。from django.db.models import Avg,Sum,Max,Min # 求書籍的平均價 ret = models.Book.objects.all().aggregate(Avg('price')) # {'price__avg': 145.23076923076923} # 參與西遊記著作的作者中最老的一位作者 ret = models.Book.objects.filter(title__icontains='西遊記').values('author__age').aggregate(Max('author__age')) # {'author__age__max': 518}
annotate() 分組函式
# 檢視每一位作者出過的書中最貴的一本 # (按作者名分組 values(),然後 annotate() 分別取每人出過的書價格最高的) ret=models.Book.objects.values('author__name').annotate(Max('price')) # < QuerySet[ # {'author__name': '吳承恩', 'price__max': Decimal('234.000')}, # {'author__name': '呂不韋','price__max': Decimal('234.000')}, # {'author__name': '姜子牙', 'price__max': Decimal('123.000')}, # ] >
淺談ORM查詢效能
普通跨表查詢
obj_list=models.Love.objects.all() for row in obj_list: # for迴圈10次傳送10次資料庫查詢請求 print(row.b.name)
原理:第一次傳送查詢請求,每for迴圈一次也會發送查詢請求
select_related
結果為物件,query_set型別的物件都有該方法
原理:select_related查詢時主動完成連表形成一張大表,for迴圈時不用額外發請求
試用場景:節省硬碟空間,資料量少的時候適用,相當於做了一次資料庫查詢;
obj_list=models.Love.objects.all().select_related('b') # 查詢時關聯b表 for row in obj_list: print(row.b.name)
prefetch_related:
結果為物件
原理:select_related雖好,但是做連表操作依然會影響查詢效能,prefetch_related不做連表,多次單表查詢外來鍵表,去重之後顯示,2次單表查詢(有N個外來鍵做1+N次單表查詢)
適用場景:效率高,資料量大的時候使用
obj_list=models.Love.objects.all().prefetch_related('b') for obj in obj_list: print(obj.b.name)
update()和物件.save()修改方式的效能PK
# 方式1 models.Book.objects.filter(id=1).update(price=3) # 執行結果 (0.000) BEGIN; args=None (0.000) UPDATE "app01_book" SET "price" = '3.000' WHERE "app01_book"."id" = 1; args=('3.000', 1) # 方式2 book_obj=models.Book.objects.get(id=1) book_obj.price=5 book_obj.save() # 執行結果 (0.000) SELECT "app01_book"."id", "app01_book"."title", "app01_book"."price", "app01_book"."date", "app01_book"."publish_id", "app01_book"."classify_id" FROM "app01_book" WHERE "app01_book"."id" = 1; args=(1,) (0.000) BEGIN; args=None (0.000) UPDATE "app01_book" SET "title" = '我的奮鬥', "price" = '5.000', "date" = '1370-09-09', "publish_id" = 4, "classify_id" = 3 WHERE "app01_book"."id" = 1; args=('我的奮鬥', '5.000', '1370-09-09', 4, 3, 1) # 結論: update() 比 obj.save()效能好
Django自帶ContentType表
Django程式啟動後自帶的一張表,記錄了Django程式的所有APP下model中的表名和所在app的名稱
通過ContentType中的app名和表名,查詢到Django model中所有表;
from django.contrib.contenttypes.models import ContentType def test(request): c = ContentType.objects.get(app_label='app01',model='boy') print(c) -> boy print(c.model_class()) -> app01.models.Boy
解決 1張表 同時與其他N張表建立外來鍵,並且多個外來鍵中只能選擇1個的複雜問題
場景1:現有N種優惠券,每1種優惠券分別對應N門課程中的一門課程,怎麼設計表結構呢?
場景2:學生的學習成績如何獎懲、 作業如何獎懲、學習進度如何獎懲...# 例:場景1 from django.db import models from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation class DegreeCourse(models.Model): name = models.CharField(max_length=128, unique=True) # GenericRelation 自動連表查詢 xxx = GenericRelation('Coupon') class Course(models.Model): name = models.CharField(max_length=128, unique=True) class Coupon(models.Model): """優惠券生成規則 ID 優惠券名稱 content_type_id(表) object_id(表中資料ID) 1 通用 null null 2 滿100-10 8 1 3 滿200-30 8 2 4 滿200-30 9 1 """ name = models.CharField(max_length=64, verbose_name="活動名稱") # course_type 代指哪張表 注意該欄位必須為 content_type content_type = models.ForeignKey(ContentType,blank=True,null=True) # 代指物件ID 該欄位必須為 object_id object_id = models.PositiveIntegerField(blank=True, null=True, help_text="可以把優惠券跟課程繫結") # GenericForeignKey 通過 content_type 直接建立外來鍵關係,不會生成額外的列 content_object = GenericForeignKey('content_type','object_id') # 給學位課1,建立優惠券100 # 方式1: # 1、在學位課表中 ,找到學位課1 d1 = models.DegreeCourse.objects.get(id=1) # 2、在ContentType找到學位課表 c1 = ContentType.objects.get(app_label='app01',model='degreecourse') # 3、給學位課1,建立優惠券100 models.Coupon.objects.create(name='優惠券',brief='100',content_type=c1,object_id=d1.id) # 方式2: d1 = models.DegreeCourse.objects.get(id=1) models.Coupon.objects.create(name='優惠券',brief='100',content_object=d1) # 查詢關聯的所有優惠券 d1 = models.DegreeCourse.objects.get(id=1) print(d1.xxx.all()) v = models.DegreeCourse.objects.values('name','xxx__brief','xxx__name') print(v)
其他小技巧
資料庫表刪除重建:
先到資料庫把表刪掉:
drop table
註釋django中對應的Model
執行以下命令:
python manage.py makemigrations python manage.py migrate --fake -> 只記錄變化,不提交資料庫操作
去掉註釋重新遷移
python manage.py makemigrations python manage.py migrate
字典key替換
# 把value傳給新key並同時刪除舊key
row['delivery'] = [row.pop('投遞')]
獲取欄位名和verbose_name
fields_data = Group._meta.fields
for key in data:
# 這裡是將當前的資料轉換成資料字典,方便後面修改後提交
data_dict = Group.__dict__
for field in fields_data:
# 這樣或輸出這條記錄的所有欄位名,需要的話還可以輸出verbose_name
print(field.name)
if field.name == key:
#進行匹配,將前端傳來的欄位匹配到,然後修改資料庫裡面的資料
data_dict[key] = data[key]
# 儲存資料到資料庫,這樣的好處就是提高效率,避免過多重複操作