1、外鍵自關聯(一對多) 1、建表 # 評論表 class Comment(models.Model): id = models.AutoField(primary_key=True) content = models.CharField(max_length=255) push_time = models.DateTimeField(auto_now_add=True) # 父評論:自關聯,一個評論能夠沒有父評論因此null=True pcomment = models.ForeignKey(to='self', null=True) def __str__(self): return self.content 2、表數據
2、操做 1.找到id等於2的那個評論的全部子評論(跨表查詢) ret = Comment.objects.filter(pcomment__id=2) print(ret) 2.方法2(直接查詢) ret = Comment.objects.filter(pcomment_id=2) print(ret) 3.方法3 ret = Comment.objects.filter(id=2).values('comment__id') print(ret) 2、多對多自關聯 1、建表 class Person(models.Model): name = models.CharField(max_length=12) friends = models.ManyToManyField(to='self', symmetrical=False) def __str__(self): return self.name 2、注意 在多對多的自關聯時,若是須要反向查找,則須要添加symmetrical這個字段, 指定內部是否建立反向操做的字段,默認爲True,須要反向查找則改爲False 3、操做 1.找到小明的朋友(正向找) ret = Person.objects.filter(name='小明').values('friends__name') print(ret) 2.朋友是小明的那我的(反向找) ret = Person.objects.get(name='小明').person_set.all() print(ret) 或者 ret = Person.objects.filter(name='小明').values('person__name') print(ret)
補充:
create(關鍵字參數)
建立一個新的對象,保存對象,並將它添加到關聯對象集之中,返回新建立的對象。
publisher.objects.create(id=1, name="...")
3、補充的幾個方法 1、這幾個方法適用於一對多和多對多的狀況,一對多的時候,在"一"這張表纔有下面這些方法,多對多的時候,兩張表跨到第三張表時都有下面這些方法, 且只是對象才能使用(queryset列表不能使用),就是obj.get()、obj.first()、obj.last()才能使用,
須要注意的是:'一'這張表使用下面這些方法時,參數是對象,而多對多的第三張表使用下面這些方法時,參數是id 它存在於下面兩種狀況: 外鍵關係的反向查詢 多對多關聯關係 簡單來講就是當 點後面的對象 可能存在多個的時候就可使用如下的方法 2、方法
1.add(位置參數) 把指定的model對象添加到關聯對象集中。 可是一對多中add裏面的實參(位置參數)是對象 而多對多中add裏面的實參(位置參數)能夠是id 2.set([列表]) 更新model對象的關聯對象。 可是一對多中set裏面的實參(列表)是對象 而多對多中add裏面的實參(列表)能夠是id 3.remove() 從關聯對象集中移除執行的model對象 4.clear() 從關聯對象集中移除一切對象。 注意: 對於ForeignKey對象,clear()和remove()方法僅在null=True時存在。 舉個例子: ForeignKey字段沒設置null=True時, class Book(models.Model): title = models.CharField(max_length=32) publisher = models.ForeignKey(to=Publisher) 沒有clear()和remove()方法 >>> models.Publisher.objects.first().book_set.clear() Traceback (most recent call last): File "<input>", line 1, in <module> AttributeError: 'RelatedManager' object has no attribute 'clear' 當ForeignKey字段設置null=True時, class Book(models.Model): name = models.CharField(max_length=32) publisher = models.ForeignKey(to=Class, null=True) 此時就有clear()和remove()方法 >>> models.Publisher.objects.first().book_set.clear() 5.注意 對於全部類型的關聯字段,add()、create()、remove()和clear(),set()都會立刻更新數據庫。換句話說,在關聯的任何一端,都不須要再調用save()方法。 6.建表 class MyClass(models.Model): cname = models.CharField(max_length=12) class Student(models.Model): sname = models.CharField(max_length=12) myclass = models.ForeignKey(to='MyClass') def __str__(self): return self.sname class Teacher(models.Model): tname = models.CharField(max_length=12) myclass = models.ManyToManyField(to='MyClass') 7.例子 1.一對多 # 查詢id=2的班級的全部學生 ret = MyClass.objects.get(id=2).student_set.all() print(ret) # id=2的班級的全部老師 ret = MyClass.objects.get(id=2).teacher_set.all() print(ret) # 給id=1的班級添加一個id=4的學生 student_obj = Student.objects.get(id=4) MyClass.objects.get(id=1).student_set.add(student_obj) ret = MyClass.objects.get(id=1).student_set.all() print(ret) # 把全部學生都綁定到id=1的班級中 MyClass.objects.get(id=1).student_set.set(Student.objects.all()) ret = MyClass.objects.get(id=1).student_set.all() print(ret) 2.多對多 # 給id=2的老師添加一個id=4的班級 Teacher.objects.get(id=2).myclass.add(4)
# 給id=3的班級添加一個id=1的老師
Myclass.objects.get(id=3).teacher_set.add(1)
# 把id=3的班級的老師修改成id=2和id=3的老師
Myclass.objects.get(id=3).teacher_set.set([2, 3])
# 從id=2的老師的中移除2班 Teacher.objects.get(id=2).myclass.remove(2) # 清空id=2的老師的班級 Teacher.objects.get(id=2).myclass.clear() # 給id=2的老師新增一門課:生理課 Teacher.objects.get(id=2).myclass.create(cname='生理課') 4、如何在Django終端打印SQL語句 在Django項目的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項目配置上一個名爲django.db.backends的logger實例便可查看翻譯後的SQL語句。 5、基於對象的跟新(save)和基於QuerySet的update跟新的區別 # 把南山出版社的ceo改爲小紅 # 1. 基於對象的修改(會跟新全部字段,效率慢) publisher_obj = Publisher.objects.get(name='南山出版社') publisher_obj.ceo = '小紅' publisher_obj.save() # 2. 基於QuerySet的update跟新(只跟新指定的字段,效率快於基於對象的修改) Publisher.objects.filter(name='南山出版社').update(ceo='小勾') 6、聚合查詢
注意:ORM的聚合函數必定要搭配aggregate或者annotate使用
6-一、aggregate
1、介紹 aggregate()是QuerySet的一個終止子句,意思是說,它返回一個包含一些鍵值對的字典。 鍵的名稱是聚合值的標識符,值是計算出來的聚合值。鍵的名稱是按照字段和聚合函數的名稱自動生成出來的。 用到的內置函數: from django.db.models import Avg, Sum, Max, Min, Count 2、例子 from django.db.models import Avg, Sum, Max, Min, Count # 求全部書中價格最高的書 ret = Book.objects.all().aggregate(Max('price')) print(ret) # 爲聚合值指定一個名稱,返回指定的key值 ret = Book.objects.all().aggregate(max_price=Max('price')) print(ret) # 生成不止一個聚合,求全部書的平均價格和最小的價格 ret = Book.objects.all().aggregate(avg_price=Avg('price'), min_price=Min('price')) print(ret) 6-二、annotate分組查詢 1、介紹 values/values_list 對應 SQL語句 select 部分 filter 對應 SQL語句 where 部分 2、ORM中分組使用annotate 1. annotate前面查詢(values)的是什麼,就按什麼分組,沒有values就默認按id分組,而id是惟一的,所以不寫values等於沒有分組 2. annotate中要寫上分組以後要作的事情 3、建表 class Employee(models.Model): name = models.CharField(max_length=12) age = models.IntegerField() salary = models.IntegerField() province = models.CharField(max_length=12) dept = models.CharField(max_length=12) 4、例子 from django.db.models import Avg, Sum, Max, Min, Count # 使用ORM查詢每一個部門的平均工資 ret = Employee.objects.values('dept').annotate(avg=Avg('salary')) print(ret) ret = Employee.objects.annotate(avg=Avg('salary')).values('dept', 'avg') print(ret) # 等於沒有分組 # 每一個部門的平均年齡 ret = Employee.objects.values('dept').annotate(Avg('age')).values_list('dept', 'avg_age') print(ret) 4、多表使用分組 class Publisher(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=16) addr = models.TextField() # 成立日期:對應Python中的datetime.date類型 date = models.DateField() def __str__(self): return self.name class Book(models.Model): # 書名 title = models.CharField(max_length=16) # 價格:最多顯示6個數字,小數位有2個 price = models.DecimalField(max_digits=6, decimal_places=2) # ISBN:書籍的惟一編號 isbn = models.CharField(max_length=20, unique=True) # 外鍵關聯出版社,db_constraint=False不在數據庫中創建約束 publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE, related_name='books') def __str__(self): return self.title class Author(models.Model): name = models.CharField(max_length=12) # 性別,choice選項用的是本模塊定義的常量,默認選保密 gender = models.SmallIntegerField(choices=((1, '男'), (2, '女'), (3, '保密')), default=3) # 手機號,惟一約束 phone = models.CharField(max_length=11, unique=True) # 郵箱 email = models.EmailField() # 多對多關聯書籍 books = models.ManyToManyField(to='Book', related_name='authors') 例子: from django.db.models import Avg, Sum, Max, Min, Count # 求每一個出版社出版的書的平均價格
# 基於Book ret = Book.objects.values('publisher_id').annotate(avg_price=Avg('price')).values_list('publisher__name', 'avg_price') print(ret)
# 基於Publisher
ret = Publisher.objects.annotate(avg_price=Avg(books__price)).values('name', 'avg_price')
print(ret)
# 求每一個出版社出版的書的數量 ret = Book.objects.values('publisher_id').annotate(count=Count('id')).values_list('publisher__name', 'count') print(ret) 7、Q查詢 filter()等方法中的關鍵字參數查詢都是一塊兒進行「AND」 的。 若是你須要執行更復雜的查詢(例如OR語句),你可使用Q對象。 一樣是上面書籍出版社那個數據庫 例如: 一、或 | from django.db.models import Q # 查詢書的價格大於100或者做者是小明的書 ret = Book.objects.filter(Q(price__gt=100) | Q(authors__name='小明')) print(ret) 二、且 & # 原生的 且 # 查詢書的價格大於100且做者是小明的書 ret = Book.objects.filter(price__gt=100, authors__name='小明') print(ret) # Q對象的 且 from django.db.models import Q ret = Book.objects.filter(Q(price__gt=100) & Q(authors__name='小明')) print(ret) 三、非 ~ from django.db.models import Q ret = Book.objects.filter(Q(price__gt=100) & ~Q(authors__name='小明')) print(ret) 4、混合使用: 查詢函數能夠混合使用Q對象和關鍵字參數。全部提供給查詢函數的參數(關鍵字參數或Q對象)都將進行'END'運算, 可是,若是出現Q對象,它必須位於全部關鍵字參數的前面。 # 查詢出版社id是1或者2,且做者名字中有小的書 ret = Book.objects.filter(Q(publisher_id=1) | Q(publisher_id=2), authors__name__contains='小') print(ret)
五、Q的拓展使用方法
Q查詢的兩種寫法 # Q原始寫法 data = data.filter( Q(name__icontains=query_value)|Q(qq__icontains=query_value)|Q(qq_name__icontains=query_value) ) 或者 data = data.filter( Q(('name__icontains', query_value))|Q(('qq__icontains', query_value))|Q(('qq_name__icontains', query_value)) ) # Q使用對象的寫法 q = Q() # 實例一個Q對象 q.connector = 'OR' # 鏈接符,OR AND q.children.append(Q(('name__icontains', query_value))) # Q查詢裏面的實參是一個元組,元組第一個元素是查詢的字段(字符串形式) q.children.append(Q(('qq__icontains', query_value))) # 元組第二個元素是須要匹配的值 q.children.append(Q(('qq_name__icontains', query_value))) data = data.filter(q)
8、F查詢 1、字段在原基礎上跟新
1.1跟新字段的值(數字) 例如: from django.db.models import F # F用來在字段原來的基礎上進行操做 Book.objects.all().update(price=F('price')+20) 進行了上面的操做後,數據庫中book表全部的price字段的值所有加了20, 好比原本90的,如今就110
1.2拓展:跟新字段的值(字符串) from django.db.models import F # 給每本書的名字加'新款'(注意:通常在實際中不該該這麼作,這裏只是用於瞭解F對象) from django.db.models.functions import Concat # 字符串拼接 from django.db.models import Value # 把字符串轉變成F對象能夠操做的變量 Book.objects.all().update(title=Concat(F('title'), Value('新款')))
二、同一條記錄不一樣字段之間的比較
# 找出賣出數大於收藏數的書籍
Book.objects.filter(sale_num__gt=F('fav_num')) # sale_num是這本書的賣出的數量,fav_num是這本書收藏的次數
9、事務 在事務代碼塊內的代碼,所有都執行成功後,數據庫中才會進行修改 只要存在一句錯誤的語句,所有代碼都不會生效 通常使用在安全性須要很高的場景,好比,銀行的轉帳, 爲了不一方轉帳後,中途出現斷電,服務器異常等狀況,致使另外一方沒收到轉帳, 而轉帳方的錢卻減小了 from django.db import transaction with transaction.atomic(): # 建立一本書 Book.objects.create(title='嘿嘿書', price=10.99, isbn='213dsxc', publisher_id=1) # 把id=100的書名修改成哈哈書 book_obj = Book.objects.get(id=100) # 沒有id=100的書,這裏會報錯,結果是總體都不生效,數據庫不會有任何修改 book_obj.title = '哈哈書' book_obj.save() 10、去重distinct ret = Book.objects.all().values('publisher__name').distinct() print(ret) 11、Django ORM執行原生SQL(拓展瞭解) 1、raw 1. 查詢本身這個表 ret = Book.objects.raw('select * from app04_book') # raw裏面寫原生的SQL語句 print(ret) for i in ret: print(i.title, i.price) 2. 查詢別的表 在Book表查詢publisher表,select必需要寫上publisher的主鍵,能使用的也只有主鍵,若是要使用其餘字段,就在主鍵後寫上其餘字段 ret = Book.objects.raw('select id, name from app04_publisher') for i in ret: print(i.id, i.name) 拓展知識:在ORM中,使用的是懶查詢,就是當你沒有使用這個結果的時候,它是不會去數據庫幫你查詢的, 好比上面的 ret = Book.objects.raw('select * from app04_book') 當你print這個結果的時候,並不會有內容的 由於ORM沒有看到你使用這個ret,它不會去數據庫幫你查詢的,而當你把ret進行循環的時候,ORM知道你要使用了,它纔會 真正地去數據庫幫你進行操做並返回結果給你。 2、直接執行自定義SQL 有時候raw()方法並不十分好用,不少狀況下咱們不須要將查詢結果映射成模型,或者咱們須要執行DELETE、 INSERT以及UPDATE操做。在這些狀況下,咱們能夠直接訪問數據庫,徹底避開模型層。 咱們能夠直接從django提供的接口中獲取數據庫鏈接,而後像使用pymysql模塊同樣操做數據庫。 from django.db import connection, connections connection獲取默認的數據庫 connections當有多個數據庫的時候,使用關鍵字獲取指定的數據庫 connections['default'] 例如: from django.db import connection, connections cursor = connection.cursor() cursor.execute('select * from app04_book') ret = cursor.fetchone() print(ret) 12、QuerySet方法大全
def all(self) # 獲取全部的數據對象 def filter(self, *args, **kwargs) # 條件查詢 # 條件能夠是:參數,字典,Q def exclude(self, *args, **kwargs) # 條件查詢 # 條件能夠是:參數,字典,Q def select_related(self, *fields) 性能相關:表之間進行join連表操做,一次性獲取關聯的數據。 總結: 1. select_related主要針一對一和多對一關係進行優化。 2. select_related使用SQL的JOIN語句進行優化,經過減小SQL查詢的次數來進行優化、提升性能。 def prefetch_related(self, *lookups) 性能相關:多表連表操做時速度會慢,使用其執行屢次SQL查詢在Python代碼中實現連表操做。 總結: 1. 對於多對多字段(ManyToManyField)和一對多字段,可使用prefetch_related()來進行優化。 2. prefetch_related()的優化方式是分別查詢每一個表,而後用Python處理他們之間的關係。 def annotate(self, *args, **kwargs) # 用於實現聚合group by查詢 from django.db.models import Count, Avg, Max, Min, Sum v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')) # SELECT u_id, COUNT(ui) AS `uid` FROM UserInfo GROUP BY u_id v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')).filter(uid__gt=1) # SELECT u_id, COUNT(ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1 v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1) # SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1 def distinct(self, *field_names) # 用於distinct去重 models.UserInfo.objects.values('nid').distinct() # select distinct nid from userinfo 注:只有在PostgreSQL中才能使用distinct進行去重 def order_by(self, *field_names) # 用於排序 models.UserInfo.objects.all().order_by('-id','age') 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']) def reverse(self): # 倒序 models.UserInfo.objects.all().order_by('-nid').reverse() # 注:若是存在order_by,reverse則是倒序,若是多個排序則一一倒序 def defer(self, *fields): models.UserInfo.objects.defer('username','id') 或 models.UserInfo.objects.filter(...).defer('username','id') #映射中排除某列數據 def only(self, *fields): #僅取某個表中的數據 models.UserInfo.objects.only('username','id') 或 models.UserInfo.objects.filter(...).only('username','id') def using(self, alias): 指定使用的數據庫,參數爲別名(setting中的設置) ################################################## # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS # ################################################## def raw(self, raw_query, params=None, translations=None, using=None): # 執行原生SQL models.UserInfo.objects.raw('select * from userinfo') # 若是SQL是其餘表時,必須將名字設置爲當前UserInfo對象的主鍵列名 models.UserInfo.objects.raw('select id as nid from 其餘表') # 爲原生SQL設置參數 models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,]) # 將獲取的到列名轉換爲指定列名 name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'} Person.objects.raw('SELECT * FROM some_other_table', translations=name_map) # 指定數據庫 models.UserInfo.objects.raw('select * from userinfo', using="default") ################### 原生SQL ################### from django.db import connection, connections cursor = connection.cursor() # cursor = connections['default'].cursor() cursor.execute("""SELECT * from auth_user where id = %s""", [1]) row = cursor.fetchone() # fetchall()/fetchmany(..) def values(self, *fields): # 獲取每行數據爲字典格式 def values_list(self, *fields, **kwargs): # 獲取每行數據爲元祖 def dates(self, field_name, kind, order='ASC'): # 根據時間進行某一部分進行去重查找並截取指定內容 # kind只能是:"year"(年), "month"(年-月), "day"(年-月-日) # order只能是:"ASC" "DESC" # 並獲取轉換後的時間 - year : 年-01-01 - month: 年-月-01 - day : 年-月-日 models.DatePlus.objects.dates('ctime','day','DESC') def datetimes(self, field_name, kind, order='ASC', tzinfo=None): # 根據時間進行某一部分進行去重查找並截取指定內容,將時間轉換爲指定時區時間 # kind只能是 "year", "month", "day", "hour", "minute", "second" # order只能是:"ASC" "DESC" # tzinfo時區對象 models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.UTC) models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.timezone('Asia/Shanghai')) """ pip3 install pytz import pytz pytz.all_timezones pytz.timezone(‘Asia/Shanghai’) """ def none(self): # 空QuerySet對象 #################################### # METHODS THAT DO DATABASE QUERIES # #################################### def aggregate(self, *args, **kwargs): # 聚合函數,獲取字典類型聚合結果 from django.db.models import Count, Avg, Max, Min, Sum result = models.UserInfo.objects.aggregate(k=Count('u_id', distinct=True), n=Count('nid')) ===> {'k': 3, 'n': 4} def count(self): # 獲取個數 def get(self, *args, **kwargs): # 獲取單個對象 def create(self, **kwargs): # 建立對象 def bulk_create(self, objs, batch_size=None): # 批量插入 # batch_size表示一次插入的個數 objs = [ models.DDD(name='r11'), models.DDD(name='r22') ] models.DDD.objects.bulk_create(objs, 10) def get_or_create(self, defaults=None, **kwargs): # 若是存在,則獲取,不然,建立 # defaults 指定建立時,其餘字段的值 obj, created = models.UserInfo.objects.get_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 2}) def update_or_create(self, defaults=None, **kwargs): # 若是存在,則更新,不然,建立 # defaults 指定建立時或更新時的其餘字段 obj, created = models.UserInfo.objects.update_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 1}) def first(self): # 獲取第一個 def last(self): # 獲取最後一個 def in_bulk(self, id_list=None): # 根據主鍵ID進行查找 id_list = [11,21,31] models.DDD.objects.in_bulk(id_list) def delete(self): # 刪除 def update(self, **kwargs): # 更新 def exists(self): # 是否有結果
舉幾個例子: # only:將指定的字段查詢加載出來,後續再訪問指定的字段就不須要再查詢數據庫 ret = Book.objects.all().only('title') # 拿到全部書的對象列表 for i in ret: print(i.title) # 訪問指定的字段title不須要再去查詢數據庫 for i in ret: print(i.price) # 訪問不是指定的字段,每一次都去查一次數據庫 # defer:將除了指定的字段查詢加載出來,後續再訪問指定的字段就不須要再查詢數據庫(only的反義詞) ret = Book.objects.all().defer('title') for i in ret: print(i.title) # 訪問指定的字段title,每一次都須要去查詢數據庫 for i in ret: print(i.price) # 訪問不是指定的字段,不須要再查詢數據庫 # bulk_create:一次SQL語句批量建立 from datetime import date obj = (Publisher(name='第%s出版社' %(i), addr='中國', date=date.today()) for i in range(100)) Publisher.objects.bulk_create(obj) # select_related:把id=2的書的信息和它關聯的出版社的信息一塊兒查詢出來。 ret = Book.objects.filter(id=2).select_related('publisher') print(ret[0].publisher.name)
aggregate