Django ORM 知識點總結

Query是如何工做的

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

# 這裏不會訪問數據庫,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)

在訪問兩個數據庫的時候,須要把對前一個數據庫訪問的結果轉爲緩存數據再執行對下一個數據庫的訪問,好比python

# 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.3ssql

而用count只須要0.007s左右數據庫

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

複製模型數據

  • 獲取model_object值的方式

model.vardjango

model中定義了爲IntegerField的屬性取出來是int緩存

  • 將model_object轉成字典

model.__dict__ 或者 model_to_dict(model)app

  • 複製模型數據
# 在主數據庫建立一個訂單副本
# 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字段svn

  • 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`

原文出處:https://www.cnblogs.com/luozx207/p/11545057.html

相關文章
相關標籤/搜索