Django ORM 知識點總結

  • 2019 年 10 月 3 日
  • 筆記

Query是如何工作的

Django QuerySet是懶執行的,只有訪問到對應數據的時候,才會去訪問數據庫。另外如果你再次讀取查詢到的數據,將不會觸發數據庫的訪問,而是直接從緩存獲取。
比如

# 這裡不會訪問數據庫,origins只是一個查詢query,不是數據實例  origins = queryset.filter(status__in=[0, 2])  # 這裡會訪問數據庫,將origins中的查詢query與此update語句拼在一起組成一個sql語句  origins.update(status=1)  # 這裡的origins,是再次執行查詢之後的結果,因此,結果為空集  # 如果此時認為origins是之前查詢的結果集,就會出錯  for origin in origins:  self.after_confirm(origin, project_id)

在訪問兩個數據庫的時候,需要把對前一個數據庫訪問的結果轉為緩存數據再執行對下一個數據庫的訪問,比如

# object1與object2通過關係表Relations關聯  # object1和Relations表在同一個數據庫中,object2在另一個數據庫中  # 現在需要通過object1的一堆id來找到對應的object2    # 錯誤寫法:  object2_ids = Relations.filter(object1_id__in=(object1_ids)).values_list('object2_id', flat=True).distinct()  object2s = Object2.objects.filter(id__in=object2_ids)  # 如果這時直接這樣寫,則實際上是涉及兩個數據庫的query的拼接,會出錯  # 應該將第一個query轉換為內存數據list    # 正確寫法:  object2_ids = list(Relations.filter(object1_id__in=(object1_ids)).values_list('object2_id', flat=True).distinct())  object2s = Object2.objects.filter(id__in=object2_ids)

多使用query的count()函數代替for循環計數

對1530條數據做for循環計數的速度是0.2~0.3s

而用count只需要0.007s左右

Django目前不提供外鍵或多對多的關係跨越多個數據庫的支持。如果你使用路由器分割模型對不同的數據庫,任何FOREIGNKEY和多對多關係的模型定義必須是單個數據庫的內部。

複製模型數據

  • 獲取model_object值的方式

model.var

model中定義了為IntegerField的屬性取出來是int

  • 將model_object轉成字典

model.__dict__ 或者 model_to_dict(model)

  • 複製模型數據
# 在主數據庫創建一個訂單副本  # id也會相應複製,但created_time和modified_time不會  order = Order.objects.using('qtr').last()  OrderCopy.objects.create(**model_to_dict(order), project_id='qtr')

外鍵的反向引用

  • Tag.objects.filter(project_tag__project_id=project_id)

ProjectTag表的tag字段外鍵到了Tag表的id字段,並且定義了related_name='project_tag'的反向引用,因此可以通過Tag Model的project_tag字段訪問到ProjectTag Model。project_tag__project_id表示ProjectTag Model的project_id字段

  • Tag.objects.filter(user_tag__user_id=user_id)

UserTag表的tag字段外鍵到了Tag表的id字段,並且定義了related_name='user_tag'的反向引用。同時UserTag表的user字段外鍵到了User表,因此user_tag__user_id表示User的id字段

總之,外鍵的反向引用用兩橫

Update

Tag.objects.filter(id__in=ids).all().update(**update_data)

filter(id__in=ids)相當於where id in ids

如果過濾的結果是空集則不會執行更新

update_data是一個字典

利用Q構建複雜的查詢條件

如何取數據表最後兩條數據

Record.objects.order_by('-id')[:2]

[:2]會被翻譯為LIMIT 2

關於這個語句的性能消耗:http://blog.jobbole.com/52852/ 總之就是消耗不大

獲取指定列的數據

  • values:返回一個dict
record = Record.objects.values('id','name').first()  print(type(record))  # <class 'dict'>
  • values_list: 返回一個tuple,設置flat=True可以在只選擇一列的情況下返回不用tuple包裹的數據
# 取最後兩條數據記錄的svn版本  ClientVersion.objects.values_list('svn_version', flat=True).order_by('-id')[:2]
  • 若是過濾出了多行數據,返回的是queryset類型,可以用list()將其轉為列表

獲取上一條數據和下一條數據

# 本條  obj = Record.objects.get(name='test')  # 上一條  pre_obj = Record.objects.filter(id__lt=obj.id).last()  # 下一條  next_obj = Record.objects.filter(id__gt=obj.id).first()

不等於

User.objects.exclude(age=10) // 查詢年齡不為10的用戶  User.objects.exclude(age__in=[10, 20]) // 查詢年齡不為在 [10, 20] 的用戶

exact

def test_exact():  query1 = Origin.objects.filter(origin_str='test')  print(query1.query)  query2 = Origin.objects.filter(origin_str__exact='test')  print(query2.query)  # 二者翻譯成sql語句是一樣的  # WHERE `translate_app_origin`.`origin_str` = test

篩選空

django model從數據庫中取字符串的時候會自動去掉字符實際內容兩旁的空格
比如 queryset.filter(result=”) 可以過濾出result=" "和result=""的條目

# 排除result=null、result=""、result=" "  # 注意不要寫成queryset.exclude(result__isnull=True, result=''),這表示同時滿足兩個條件才會被過濾  items = queryset.exclude(result__isnull=True).exclude(result='')

queryset的拼接

a1 = User.objects.filter(id__gt=8)  a2 = User.objects.filter(id__lt=4)    a3 = a1 | a2  # 這種方式合併的結構還是一個queryset,相當於a3把a1和a2的條件合併了  # 只能合併同一個數據庫同種model對象的數據,並不能拼接兩個不同數據庫相同model的queryset
from itertools import chain    a1 = User.objects.filter(id__gt=8)  a2 = User.objects.filter(id__lt=4)    a3 = chain(a1, a2)  # 這時候a3是個可迭代對象,把a1和a2分別求出來之後合併成了一個可迭代對象,  # 可以把不同model的對象合併,類似於與list相加。  # 但是這樣合併之後a3並不是一個queryset,不能用任何篩選,沒什麼意義,還不如全部轉成data_dict再拼接

總之就是,沒有把多個不同數據庫中相同model過濾出來的queryset合併的辦法

distinct

如果出現錯誤:DISTINCT ON fields is not supported by this database backend

如果你用的Mysql數據庫,那麼distinct() 裏面不要任何參數,參數應該寫在 value 中去,如

language_list = items.values_list('language', flat=True).distinct()

order by

一個query只能有一個order_by,如果有多個,後面的order_by會覆蓋前面的,如

Order.objects.order_by('project_id').order_by('name')  # sql:  # select * from order order by order.name ASC

對bool值按默認順序排序的時候,False會排在True前面,因為False相當於0,True相當於1

# 需要將True排在前面  def test_order_by():  result = OrderLanguagePair.objects.order_by('-activate').first()  print(result.activate)

group by

比如現在想知道每個項目有多少個訂單,在sql語句中應對訂單按項目id分組,然後求出每組訂單的數量

SELECT project_id, count(*) FROM order group by project_id;

django ORM中沒有顯式的group by函數,通過annotate來實現分組

# annotate的作用是為一個query增加一個自定義的新字段  # annotate接收表達式作為參數  def annotate(self, *args, **kwargs):  """  Return a query set in which the returned objects have been annotated  with extra data or aggregations.  """

如果沒有指定任何字段,annotate會根據前面queryset的第一個字段(一般是id)分組計算,如

Order.objects.annotate(Count('name'))  # sql:  # select *, count(order.name) from order group by order.id

在annotate前用values或values_list指定根據什麼字段分組,如

# 注意values要放在annotate之前  Order.objects.values('project_id').annotate(count=Count('*'))  # sql:  # select order.project_id, count(*) as count from order group by order.project_id

annotate定義的字段會加到前面的values或values_list中

values中有多個值時,會按照順序group by

Order.objects.values('project_id', 'name').annotate(count=Count('*'))  # sql:  # select order.project_id, order.name, count(*) as count from order group by order.project_id, order.name

如果annotate所屬的query含有order_by的話,除了按values的字段分組外,還會額外按照order_by的字段分組(如果order_by中的字段不在values中)

# 下面兩個query對應的sql是一樣的  Order.objects.values('project_id').annotate(count=Count('*')).order_by('name')  Order.objects.order_by('name').values('project_id').annotate(count=Count('*'))  # sql:  # select order.project_id, count(*) as count from order  # group by order.project_id, order.name  # order by order.name

解決的方法是用對分組字段的排序覆蓋query之前的排序,比如

query = Order.objects.order_by('name')  query.order_by('project_id').values('project_id').annotate(count=Count('*'))

別名

希望使用ORM實現給字段加別名,如

select name as user_name, id as user_id  from users

Django有兩種實現方式

  1. extra
User.objects.extra(select={'user_id':user, 'user_name':id}).   values('user_id', 'user_name')

但是這種方法只能適用於沒有外鍵引用的情況,即只能選擇給此Model的字段取別名,如果要給外鍵引用的字段取別名,需要用到下面這種方式

  1. annotate
ProjectLanguagePair.objects.  annotate(supplier_name=F('supplier__supplier_name')).   values('supplier_name')

ProjectLanguagePair用supplier字段外鍵到了Supplier表,相當於

SELECT `supplier_app_supplier`.`supplier_name` AS `supplier_name`