對象關系映射模型是通過面向對象的方式來操作數據庫,這就需要對應的關系映射,數據中可以分為庫,表,字段信息,一條條數據,而需要用面向對象的關系去對應。於是就有了下面對應關系。
數據庫 -- 面向對象模型 表 <--> 類 字段 <--> 類屬性 記錄 <--> 每個實例
Django中的關系映射
使用面向對象的方式描述數據庫的關系模型,Django采用了以下的方式。
class Employees(models.Model): # 類名 class Meta: # Meta類中指定表元數據 db_table = "Employees" # 指定數據庫中的表名,否則為類名小寫 # 每一個model的Field類型屬性都對應數據庫表中的一條字段。數據類型通過不同類型的Field屬性指定,約束條件通過參數傳遞的方式 emp_no = models.AutoField(primary_key=True, null=False) birth_data = models.DateField(null=False) # 對應日期類型 first_name = models.CharField(null=False, max_length=14) last_name = models.CharField(null=False, max_length=16) # 對應varchar 類型 gender = models.SmallIntegerField(null=False) # 對應int類型 hire_data = models.DateField(null=False)
Fieldl類型
常用類型 | 說明 |
AutoField | 自增整數類型 |
BigAutoField | 更大自增整數類型 |
BigIntegerField | 大整數 |
BooleanField | 布爾類型True或者False |
CharField | 字符串類型 |
DateField | 日期類型 |
DateTimeField | 日期時間 |
DecimalField | 使用python的Decimal實例表示10進制浮點數,需要極高精度的數值使用該字段 |
EmailField | 能做Email檢驗,基於CharField,最大254 |
FileField | 文件字段 |
FilePathField | 文件路徑 |
FloatField | 浮點數 |
ImageField | 繼承了FileField,但是對上傳的文件進行檢驗,確保為一個圖片 |
IntegerField | int類型 |
GenericIPAddressField | Ipv4/Ipv6校驗 |
NullBooleanField | 布爾值可以為空 |
PositiveIntegerField | 正整數 |
TextField | 大文本,超過4000字符 |
TimeField | 時間類型 |
URLField | 能做URL檢驗,最大200 |
關系映射
ForeignKey一對多關系
關系類型字段可以將兩張表進行關聯,該字段在一對多方字段建立。例如官方的實例,一個Manufacture可以生產多個車型的汽車,所以在車輛表中建立一個外鍵(多端),標識該汽車類屬於哪一個Manufacture
class Manufacturer(models.Model): # ... pass class Car(models.Model): manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE) # ...
創建外鍵至少需要指定兩個參數,指定關聯的類和on_delete,主表字段和從表字段之間聯系,上面指定為級聯,即主表中的某個數據刪除,從表中關聯了該主表數據的數據將會被刪除。
上面Car中的manufacturer字段與Manufacture類進行了關聯,在生成的manufacture表中,將會多出一個Car_set的字段,可以通過一條Manufacture信息查詢出多條Car信息。而Car中manufacturer字段將被設置為manufacture_id,該數據將會關聯到Manufacture中對應的數據。
ManyToManyField多對多關系
官網的示例:一種Topping可能存在於多個Pizza中,並且每個Pizza含有多種Topping,這就是一個多對多的關系
class Topping(models.Model): # ... pass class Pizza(models.Model): # ... toppings = models.ManyToManyField(Topping)
這個屬性可以在關聯的任何一個類中創建,不能同時在連個類中添加該字段。一般來講,應該把 ManyToManyField 實例放到需要在表單中被編輯的對象中
在定義的字段類型中,指定某些參數選項,可以將這個字段做一些特殊的設置或者做一些特殊的驗證。主要包括以下的字段選項。
字段參數選項
選項 | 說明 |
null | 是否可以null |
blank | 是否可以為空 |
choices | 類似枚舉 |
db_column | 指定數據庫列名 |
db_index | 是否在該字段建索引 |
db_tablespace | |
default | 默認值 |
editable | |
error_messages | 指定后覆蓋默認的錯誤信息 |
help_text | 指定幫助信息 |
primary_key | 定義為主鍵 |
unique | 定義為唯一鍵 |
unique_for_date | |
verbose_name | 字段備注名 |
validators | models.IntegerField(validators=[validate_even]),添加驗證函數 |
Manager管理器對象
管理器對象用於實現表的增刪改查。管理器對象是類型為django.db.models.manager.Manager類型
,管理器對象可以在定義模型類中創建,如果沒有創建,默認會創建一個objects管理器,手動創建后將不會自動創建。
管理器Django查詢數據庫的接口,每一個模型都至少有一個管理器,用戶自定義管理器模型需要繼承於django.db.models.manager.Manager
類。
內部測試 manager管理器
根下創建一個Python 文件,寫入以下內容即可使用
# 從wsgi復制 import os from django.core.wsgi import get_wsgi_application os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ORM.settings') application = get_wsgi_application() # 將模型類導入,也就是定義模型類的model文件, from salary.models import Employees # object 是Employees自帶的一個管理器,這樣我們可以使用這個管理進行查詢 mgr = Employees.objects print(mgr.all())
配置日志輸出
import os LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console': { 'class': 'logging.StreamHandler', }, }, 'loggers': { 'django': { 'handlers': ['console'], 'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'), }, }, }
查詢
使用管理器對象查詢,將會返回一個查詢集,Django內部將這些結果封裝為一個Django.db.models.manager.query.Query類型。這個結果是惰性(只有需要時才會真的查詢),可以進行迭代。查詢的結果不會緩存,再次使用必須再次從數據庫查詢。
mgr = Employees.objects result_sset = mgr.all() # 查詢所有結果,將整個表查詢返回結果集
print(mgr.all()) # 獲取數據時,才會執行sql查詢
並且由於我們的日志配置,我們可以在日志中看到執行sql語句信息,這樣方便我們對查詢進行調優。
查詢方法
方法 | 說明 |
all() | 查詢所有結果的查詢集 |
filter() | 過濾查詢,類似於where |
exclude() | 過濾的結果取反 |
order_by() | 排序結果 類似於order by |
values() | 返回這個查詢集,每一個元素是{字段:值}的字典 |
方法 | 說明 |
get(pk=1) | 返回單個值,沒有數據和多條數據都將報錯 |
count() | 返回查詢的總條數 |
first() | 返回第一個對象 |
last() | 返回最后一個對象 |
exist() | 如果能查到返回True |
條件表達式
filter,exclude,get等方法中需要控制查詢條件。django使用了一套條件表達式作為查詢方法中的參數,實現查詢條件篩選。
表達式參數 | 說明 |
=, exact iexact |
等於,mgr.filter(id=1)
不等於 mgr.filter(id__iexact=3) |
gt, gte | 大於,大於等於, mge.filter(id__gt=10001) |
lt, lte | 小於,小於等於 mgr.exclude(id__lte=10010) |
startwith endswith |
首尾匹配,大小寫敏感 |
isnull isnotnull |
是否為null |
in | 在某個范圍,mgr.filter(pk__in=(1,2,3)) |
contains icontains |
包含指定的值,%值%,效率極低 |
iexact icontains istartwith iendswith |
i,表示忽略大小寫 |
year month |
判斷一個時間,需要時時間類型的字段,mgr.filter(birth_date__year=2000) 年份為2000的 |
Q 對象
在一個函數中連續寫條件表示這些條件是and(與)關系,並不能表示(or)或(!)非的關系,此時需要使用Q對象,Q對象可以使用 |
或, &
與, ~
非;來描述條件之間的關系。
Q 對象的類型:django.db.models.Q
from django.db.models import Q # 使用 Q 對象處理這個條件,並使用 | 連接,表示或關系,& 表示與 mgr.filter( Q(id__lt=10005) | Q(lastname__startswith="K") & Q(age=12)) # 與或 mgr.filter( ~Q(id__lt=10005)) # ~ 表示取反
分組,聚合
聚合函數
from django.db.models import Q, Avg, Sum, Max, Min, Count
aggregate
將所有列的數據分為一組進行聚合
UseInfo.objects.all().aggregate(Sum("age")) # 所有年齡之和,自動使用主鍵分組 返回一個字典{"age__sum": 120}, 如果想控制age_sum這個key的名字,使用aggregate(agesum=Sum("age"))key將會替換成agesum。
annotate()
annotate可以和value組合實現分組聚合
from django.db.models import Q, Avg, Sum, Max, Min, Count # emp_no 員工編號 # salary 工資 # 使用emp_no分組,計算每個員工的工資總和 s2 = sal.all().values("emp_no").annotate(Sum("salary")) # 返回查詢后的結果集 # 只會顯示兩個字段 # <QuerySet [{'emp_no': 1, 'salary__sum': 1281612}, {'emp_no': 2, 'salary__sum': 413127}]}> # 顯示所有字段使用values,或者在values中指定想要的字段,沒有使用聚合函數的行使用第一行數據為准 s2.values() # 還可以對其繼續排序。等操作 s2.values().order_by("salary_sum")
Raw
直接使用原生的sql語句
empmgr = Employee.objects raw_query = "select * from Employee where id > %s" emp = empmgr.raw(raw_query, params=None)
extra
表示添加額外的部分,select參數表示再select處添加字段以及值,所以使用字段key-value,where表示添加額外的條件,使用列表,and連接內部元素。tables參數表示關聯的表。
Userinfo.objects.all().values("id", name) .extra( select={ "total":"select count("id") from table ", "height":"select count("height") from table where gengder=%s", }, select_params=[1,男] where=["user_type.id=userinfo.type_id"], params=[0, 18] tables="app01.user_type" ) 等價的sql "select id, name, select count("id") from table as "total", select count("height") from table as height form (userinfo, app01.user_type) where user_type.id=userinfo.type_id
select_related
會根據指定的字段做連表查詢
Userinfo.objects.all().select_related("group") # 查詢group 表的內容 select * from `userinfo` inner join `group` on userinfo.group_id = group.id
prefetch_related
效果同select_releted,但是執行多次單表查詢,用連表字段的條件進行查詢。
Userinfo.objects.all().perfetch_releted("group") select 其他字段, group_id from userinfo # 查處所有group_id select * from group where group.id in group_ids # 更具id查出所有組
ManytoMany
class Boy: id = AutoField() name = models.Charfield() friend = models.ManyToMany("Girl") # 會自動建立第三章表 # f = models.ManyToMany("Girl", through="Friend", through_field = ["b", "g"]) # 使用我們自己建立的第三章表來關聯,指定表和字段,第三章表的b,g一定是分別做了外鍵的才可以 class Girl: id = AutoField() name = models.Charfield() # boy_set 反向找boy對象的屬性 obj = models.objects.get(name="小明") # 操作這個第三張表,進行查詢操作 obj.friend.add(10) # 對應的一個girl的id值 obj.friend.remove(10) # obj.friend.set([10, 20]) # 設置 obj.friend.clear() obj.friend.all() # 獲得所有關聯的girl對象,是一個查詢集。可以繼續filter查詢
foreigenKey實現多對多
使用第三張表,第三張表兩個字段,分別於其他兩個表做外鍵
class Boy: id = AutoField() name = models.Charfield() class Girl: id = AutoField() name = models.Charfield() class Friend: b = models.ForeigenKey("Boy") g = models.ForeigenKey("Boy")
如果需要查詢時小明的所有女性朋友名字,可以有三種方式,
# 小明的所有女性朋友名字 # 第一種 boy = Boy.objects.filter(name="小明").first() # 小明對象 for f in boy.Friend_set: # Friend中和小明有關的所有Fiend對象 g_name = f.g.name # 每個f對象關聯了一個girl對象獲得 # for 循環中出現了連表,出現了N+1多次查表得問題 # 第二種: 使用join連表,從中間表起手,使用values連表或者select_related都可以 friends = Friend.objects.filter(b__name="小明").values("g__name") # 使用了join連表,得到了 for friend in friends: g_name = friend.g_name # 第三種:perfetch_related多次單表查詢 friends = Friend.objects.filter(b__name="小明").perfetch_related("g__name") # 使用了join連表,得到了 for friend in friends: g_name = friend.g_name
建立聯合唯一索引
class friend: g = field() b = field() class Meta: unique_together=["g", "b"]