Django ORM用到三個類:Manager、QuerySet、Model。Manager定義表級方法(表級方法就是影響一條或多條記錄的方法),咱們能夠以models.Manager爲父類,定義本身的manager,增長表級方法;QuerySet:Manager類的一些方法會返回QuerySet實例,QuerySet是一個可遍歷結構,包含一個或多個元素,每一個元素都是一個Model 實例,它裏面的方法也是表級方法,前面說了,Django給咱們提供了增長表級方法的途徑,那就是自定義manager類,而不是自定義QuerySet類,通常的咱們沒有自定義QuerySet類的必要;django.db.models模塊中的Model類,咱們定義表的model時,就是繼承它,它的功能很強大,經過自定義model的instance能夠獲取外鍵實體等,它的方法都是記錄級方法(都是實例方法,貌似無類方法),不要在裏面定義類方法,好比計算記錄的總數,查看全部記錄,這些應該放在自定義的manager類中。以Django1.6爲基礎。
python
每一個Model都有一個默認的manager實例,名爲objects,QuerySet有兩種來源:經過manager的方法獲得、經過QuerySet的方法獲得。mananger的方法和QuerySet的方法大部分同名,贊成思,如filter(),update()等,但也有些不一樣,如manager有create()、get_or_create(),而QuerySet有delete()等,看源碼就能夠很容易的清楚Manager類與Queryset類的關係,Manager類的絕大部分方法是基於Queryset的。一個QuerySet包含一個或多個model instance。QuerySet相似於Python中的list,list的一些方法QuerySet也有,好比切片,遍歷。shell
>>> from userex.models import UserEx數據庫
>>> type(UserEx.objects)django
<class ‘django.db.models.manager.Manager’>緩存
>>> a = UserEx.objects.all()函數
>>> type(a)測試
<class ‘django.db.models.query.QuerySet’>fetch
QuerySet是延遲獲取的,只有當用到這個QuerySet時,纔會查詢數據庫求值。另外,查詢到的QuerySet又是緩存的,當再次使用同一個QuerySet時,並不會再查詢數據庫,而是直接從緩存獲取(不過,有一些特殊狀況)。通常而言,當對一個沒有求值的QuerySet進行的運算,返回的是QuerySet、ValuesQuerySet、ValuesListQuerySet、Model實例時,通常不會當即查詢數據庫;反之,當返回的不是這些類型時,會查詢數據庫。下面介紹幾種(並不是所有)對QuerySet求值的場景。網站
class Blog(models.Model):lua
name = models.CharField(max_length=100)
tagline = models.TextField()
def __unicode__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
def __unicode__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author)
n_comments = models.IntegerField()
n_pingbacks = models.IntegerField()
rating = models.IntegerField()
def __unicode__(self):
return self.headline
咱們以上面的models爲例。
a = Entry.objects.all()
for e in a:
print (e.headline)
當遍歷一開始時,先從數據庫執行查詢select * from Entry獲得a,而後再遍歷a。注意:這裏只是查詢Entry表,返回的a的每條記錄只包含Entry表的字段值,無論Entry的model中是否有onetoone、onetomany、manytomany字段,都不會關聯查詢。這遵循的是數據庫最少讀寫原則。咱們修改一下代碼,以下,遍歷一開始也是先執行查詢獲得a,但當執行print (e.blog.name)時,還須要再次查詢數據庫獲取blog實體。
from django.db import connection
l = connection.queries #l是一個列表,記錄SQL語句
a = Entry.objects.all()
for e in a:
print (e.blog.name)
len(l)
遍歷時,每次都要查詢數據庫,l長度每次增1,Django提供了方法能夠在查詢時返回關聯表實體,若是是onetoone或onetomany,那用select_related,不過對於onetomany,只能在主表(定義onetomany關係的那個表)的manager中使用select_related方法,即經過select_related獲取的關聯對象是model instance,而不能是QuerySet,以下,e.blog就是model instance。對於onetomany的反向和manytomany,要用prefetch_related,它返回的是多條關聯記錄,是QuerySet。
a = Entry.objects.select_related('blog')
for e in a:
print (e.blog.name)
len(l)
能夠看到從開始到結束,l的長度只增長1。另外,經過查詢connection.queries[-1]能夠看到Sql語句用了join。
切片不會當即執行,除非顯示指定了步長,如a= Entry.objects.all()[0:10:2],步長爲2。
序列化QuerySet不多用
和str()功能類似,將對象轉爲字符串,不多用。
計算QuerySet元素的數量,並不推薦使用len(),除非QuerySet是求過值的(即evaluated),不然,用QuerySet.count()獲取元素數量,這個效率要高。
將QuerySet轉爲list
if Entry.objects.filter(headline="Test"):
print("There is at least one Entry with the headline Test")
一樣不建議這種方法判斷是否爲空,而應該使用QuerySet.exists(),查詢效率高
數據庫的經常使用操做就四種:增、刪、改、查,QuerySet的方法涉及刪、改、查。後面還會講model對象的方法,model方法主要是增、刪、改、還有調用model實例的字段。
原型:delete()
返回:None
至關於delete-from-where, delete-from-join-where。先filter,而後對獲得的QuerySet執行delete()方法就好了,它會同時刪除關聯它的那些記錄,好比我刪除記錄表1中的A記錄,表2中的B記錄中有A的外鍵,那同時也會刪除B記錄,那ManyToMany關係呢?對於ManyToMany,刪除其中一方的記錄時,會同時刪除中間表的記錄,即刪除雙方的關聯關係。因爲有些數據庫,如Sqlite不支持delete與limit連用,因此在這些數據庫對QuerySet的切片執行delete()會出錯。如
>>> a = UserEx.objects.filter(is_active=False)
>>> b = a[:3]
>>> b.delete() #執行時會報錯
解決:UserEx.objects.filter(pk__in=b).delete()
in後面能夠是一個QuerySet,見 https://docs.djangoproject.com/en/1.6/ref/models/querysets/#in
批量修改,返回修改的記錄數。不過update()中的鍵值對的鍵只能是主表中的字段,不能是關聯表字段,以下
Entry.objects.update(blog__name='foo') #錯誤,沒法修改關聯表字段,只能修改Entry表的字段
Entry.objects.filter(blog__name='foo').update(comments_on=False) #正確
最好的方法是先filter,查詢出QuerySet,而後再執行QuerySet.update()。
因爲有些數據庫,不支持update與limit連用,因此在這些數據庫對QuerySet的切片執行update()會出錯。
至關於select-from-where,select-from-join-where,不少網站讀數據庫操做最多。能夠看到,filter()的參數是變個數的鍵值對,而不會出現>,<,!=等符號,這些符號分別用__gt,__lt,~Q或exclude(),不過對於!=,建議使用Q查詢,更不容易出錯。可使用雙下劃線對OneToOne、OneToMany、ManyToMany進行關聯查詢和反向關聯查詢,並且方法都是同樣的,如:
>>> Entry.objects.filter(blog__name='Beatles Blog') #限定外鍵表的字段
#下面是反向鏈接,不過要注意,這裏不是entry_set,entry_set是Blog instance的一個屬性,表明某個Blog object
#的關聯的全部entry,而QuerySet的方法中反向鏈接是直接用model的小寫,不要把二者搞混。It works backwards,
#too. To refer to a 「reverse」 relationship, just use the lowercase name of the model.
>>> Blog.objects.filter(entry__headline__contains='Lennon')
>>> Blog.objects.filter(entry__authors__name='Lennon') #ManyToMany關係,反向鏈接
>>> myblog = Blog.objects.get(id=1)
>>> Entry.objects.filter(blog=myblog) #正向鏈接。與下面一句等價,既能夠用實體,也能夠用
#實體的主鍵,其實即便用實體,也是隻用實體的主鍵而已。這兩種方式對OneToOne、
#OneToMany、ManyToMany的正向、反向鏈接都適用。
>>> Entry.objects.filter(blog=1) #我我的不建議這樣用,對於create(),不支持這種用法
>>> myentry = Entry.objects.get(id=1)
>>> Blog.objects.filter(entry=myentry) #ManyToMany反向鏈接。與下面兩種方法等價
>>> Blog.objects.filter(entry=1)
>>> Blog.objects.filter(entry_id=1) #適用於OneToOne和OneToMany的正向鏈接
OneToOne的關係也是這樣關聯查詢,能夠看到,Django對OneToOne、OneToMany、ManyToMany關聯查詢及其反向關聯查詢提供了相同的方式,真是牛逼啊。對於OneToOne、OneToMany的主表,也可使用下面的方式
Entry.objects.filter(blog_id=1),由於blog_id是數據庫表Entry的一個字段, 這條語句與Entry.objects.filter(blog=blog1)生成的SQL是徹底相同的。
與filter相似的還有exclude(**kwargs)方法,這個方法是剔除,至關於select-from-where not。可使用雙下劃線對OneToOne、OneToMany、ManyToMany進行關聯查詢和反向關聯查詢,方法與filter()中的使用方法相同。
>>> Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline='Hello')
轉爲SQL爲
SELECT *
FROM Entry
WHERE NOT (pub_date > '2005-1-3' AND headline = 'Hello')
在SQL中,不少關鍵詞在刪、改、查時都是能夠用的,如order by、 like、in、join、union、and、or、not等等,咱們以查詢爲例,說一下django如何映射SQL的這些關鍵字的(查、刪、改中這些關鍵字的使用方法基本相同)。
前面提到的filter/exclude中的查詢參數值都是常量,若是咱們想比較model的兩個字段怎麼辦呢?Django也提供了方法,F類,F類實例化時,參數也能夠用雙下劃線,也能夠邏輯運算,以下
>>> from django.db.models import F
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
>>> Entry.objects.filter(authors__name=F('blog__name'))
若是有or等邏輯關係呢,那就用Q類,filter中的條件能夠是Q對象與非Q查詢混和使用,但不建議這樣作,由於混和查詢時Q對象要放前面,這樣就有不免忘記順序而出錯,因此若是使用Q對象,那就所有用Q對象。Q對象也很簡單,就是把原來filter中的各個條件分別放在一個Q()便可,不過咱們還可使用或與非,分別對應符號爲」|」和」&」和」~」,並且這些邏輯操做返回的仍是一個Q對象,另外,逗號是各組條件的基本鏈接符,也是與的關係,其實能夠用&代替(在python manage.py shell測試過,&代替逗號,執行的SQL是同樣的),不過那樣的話可讀性會不好,這與咱們直接寫SQL時,各組條件and時用換行同樣,邏輯清晰。
from django.db.models import Q
>>> Poll.objects.get( Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
question__startswith='Who') #正確,但不要這樣混用
>>> Poll.objects.get( Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
Q(question__startswith='Who')) #推薦,所有是Q對象
>>> Poll.objects.get( (Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))&
Q(question__startswith='Who')) #與上面語句贊成,&代替」,」,可讀性差
Q類中時應該能夠用F類,待測試。
函數原型annotate(*args, **kwargs)
返回QuerySet
往每一個QuerySet的model instance中加入一個或多個字段,字段值只能是聚合函數,由於使用annotate時,會用group by,因此只能用聚合函數。聚合函數能夠像filter那樣關聯表,即在聚合函數中,Django對OneToOne、OneToMany、ManyToMany關聯查詢及其反向關聯提供了相同的方式,見下面例子。
>>> from django.contrib.auth.models import User
>>> from django.db.models import Count
#計算每一個用戶的userjob數量,字段命名爲ut_num,返回的QuerySet中的每一個object都有
#這個字段。在UserJob中定義User爲外鍵,在Job中定義與User是ManyToMany
>>> a = User.objects.filter(is_active=True, userjob__is_active=True). annotate(n=Count(‘userjob’)) #一對多反向鏈接
>>> b = User.objects.filter(is_active=True, job__is_active=True).annotate(n=Count(‘job__name’)) #多對多反向鏈接,User與Job是多對多
>>> len(a) #這裏纔會對a求值
>>> len(b) #這裏纔會對b求值
a對應的SQL語句爲(SQL中沒有爲表起別名,u、ut是我加的):
select auth.user.*,Count(ut.id) as ut_num
from auth_user as u
left outer join job_userjob as ut on u.id = ut.user_id
where u.is_active=True and ut.is_active=True
group by u.*
b對應的SQL語句爲(SQL中沒有爲表起別名,u、t、r是我加的):
select u.*,Count(t.name) as n
from auth_user as u
left outer join job_job_users as r on u.id=r.user_id
left outer join job_job as t on r.job_id=t.id
where t.is_active=True and u.is_active=True
group by u.*
函數原型 order_by(*fields)
返回QuerySet
正向的反向關聯表跟filter的方式同樣。若是直接用字段名,那就是升序asc排列;若是字段名前加-,就是降序desc
原型 distinct()
通常與values()、values_list()連用,這時它返回ValuesQuerySet、ValuesListQuerySet
這個類跟列表很類似,它的每一個元素是一個字典。它沒有參數(實際上是有參數的,不過,參數只在PostgreSQL上起做用)。使用方法爲
>>> a=Author.objects.values_list(name).distinct()
>>> b=Author.objects.values_list(name,email).distinct()
對應的SQL分別爲
select distinct name
from Author
和
select distinct name,email
from Author
函數原型values(*field), values_list(*field)
返回ValuesQuerySet, ValuesListQuerySet
Author.objects.filter(**kwargs)對應的SQL只返回主表(即Author表)的全部字段值,即便在查詢時關聯了其它表,關聯表的字段也不會返回,只有當咱們經過Author instance用關聯表時,Django纔會再次查詢數據庫獲取值。當咱們不用Author instance的方法,且只想返回幾個字段時,就要用values(),它返回的是一個ValuesQuerySet對象,它相似於一個列表,不過,它的每一個元素是字典。而values_list()跟values()類似,它返回的是一個ValuesListQuerySet,也類型於一個列表,不過它的元素不是字典,而是元組。通常的,當咱們不須要model instance的方法且返回多個字段時,用values(*field),而返回單個字段時用values_list(‘field’,flat=True),這裏flat=True是要求每一個元素不是元組,而是單個值,見下面例子。並且咱們能夠返回關聯表的字段,用法跟filter中關聯表的方式徹底相同。
>>> a = User.objects.values(‘id’,’username’,’userex__age’)
>>> type(a)
<class ‘django.db.models.query.ValuesQuerySet’>
>>> a
[{‘id’:0,’username’:u’test0’,’ userex__age’: 20},{‘id’:1,’username’:u’test1’,’userex__age’: 25},
{‘id’:2,’username’:u’test2’, ’ userex__age’: 28}]
>>> b= User.objects.values_list(’username’,flat=True)
>>> b
[u’test0’, u’test1’ ,u’test2’]
原型select_related(*filed)
返回QuerySet
它能夠指定返回哪些關聯表model instance,這裏的field跟filter()中的鍵同樣,能夠用雙下劃線,但也有不一樣,You can refer to any ForeignKey or OneToOneField relation in the list of fields passed to select_related(),QuerySet中的元素中的OneToOne關聯及外鍵對應的是都是關聯表的一條記錄,如my_entry=Entry.objects.get(id=1),my_entry.blog就是關聯表的一條記錄的對象。select_related()不能用於OneToMany的反向鏈接,和ManyToMany,這些都是model的一條記錄對應關聯表中的多條記錄。前面提到了對於a = Author.objects.filter(**kwargs)這類語句,對應的SQL只返回主表,即Author的全部字段,並不會返回關聯表字段值,只有當咱們使用關聯表時纔會再查數據庫返回,但有些時候這樣作並很差。看下面兩段代碼,這兩段代碼在1.1中提到過。在代碼1中,在遍歷a前,先執行a對應的SQL,拿到數據後,而後再遍歷a,而遍歷過程當中,每次都還要查詢數據庫獲取關聯表。代碼2中,當遍歷開始前,先拿到Entry的QuerySet,而且也拿到這個QuerySet的每一個object中的blog對象,這樣遍歷過程當中,就不用再查詢數據庫了,這樣就減小了數據庫讀次數。
代碼1
a = Entry.objects.all()
for e in a:
print (e.blog.name)
代碼2
a = Entry.objects.select_related('blog')
for e in a:
print (e.blog.name)
函數原型prefetch_related(*field)
返回的是QuerySet
這裏的field跟filter()中的鍵同樣,能夠用雙下劃線。用於OneToMany的反向鏈接,及ManyToMany。其實,prefetch_related()也能作select_related()的事情,但因爲策略不一樣,可能相比select_related()要低效一些,因此建議仍是各管各擅長的。select_related是用select ……join來返回關聯的表字段,而prefetch_related是用多條SQL語句的形式查詢,通常,後一條語句用IN來調用上一句話返回的結果。
class Restaurant(models.Model):
pizzas = models.ManyToMany(Pizza, related_name='restaurants')
best_pizza = models.ForeignKey(Pizza, related_name='championed_by')
>>> Restaurant.objects.prefetch_related('pizzas__toppings')
>>> Restaurant.objects.select_related('best_pizza').prefetch_related('best_pizza__toppings')
先用select_related查到best_pizza對象,再用prefetch_related 從best_pizza查出toppings
函數原型:extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
基本上,查詢時用django提供的方法就夠用了,不過有時where子句中包含複雜的邏輯,這種狀況下django提供的方法可能不容易作到,還好,django有extra(), extra()中直接寫一些SQL語句。不過,不一樣的數據庫用的SQL有些差別,因此儘量不要用extra()。須要時再看使用方法吧。
參數爲聚合函數,最好用**kwargs的形式,每一個參數起一個名字。
該函數與annotate()有何區別呢?annotate至關於aggregate()和group by的結合,對每一個group執行aggregate()函數。而單獨的aggregate()並無group by。
>>> from django.db.models import Count
>>> q = Blog.objects.aggregate(Count('entry')) #這是用*args的形式,最好不要這樣用
>>> q = Blog.objects.aggregate(number_of_entries=Count('entry')) #這是用**kwargs的形式
{'number_of_entries': 16}
至此,咱們總結了QuerySet方法返回的數據形式,主要有五種。第一種:返回QuerySet,每一個object只包含主表字段;第二種:返回QuerySet,每一個object除了包含主表全部字段,還包含某些關聯表的object,這種狀況要用select_related()和prefetch_related(),能夠是任意深度(即任意多個雙下劃線)的關聯,一般一層關聯和二層關聯用的比較多;第三種:返回ValuesQuerySet, ValuesListQuerySet,它們的每一個元素包含若干主表和關聯表的字段,不包含任何實體和關聯實例,這種狀況要用values()和values_list();第四種:返回model instance;第五種:單個值,如aggregate()方法。
若是隻是想知道一個QuerySet是否爲空,而不想獲取QuerySet中的每一個元素,那就用exists(),它要比len()、count()、和直接進行if判斷效率高。若是隻想知道一個QuerySet有多大,而不想獲取QuerySet中的每一個元素,那就用count();若是已經從數據庫獲取到了QuerySet,那就用len()
字段名加雙下劃線,除了它,還有icontains,即Case-insensitive contains,這個是大小寫不敏感的,這須要相應數據庫的支持。有些數據庫須要設置
才能支持大小寫敏感。
字段名加雙下劃線
iterable是可迭代對象
字段名加雙下劃線
字段名加雙下劃線,range後面值是列表
Entry.objects.filter(pub_date__isnull=True)對應的SQL爲SELECT ... WHERE pub_date IS NULL;
QuerySet的索引只能是非負整數,不支持負整數,因此QuerySet[-1]錯誤
a=Entry.objects.all()[5:10]
b=len(a)
執行Entry.objects.all()[5:8],對於不一樣的數據庫,SQL語句不一樣,Sqlite 的SQL語句爲select * from tablename limit 3 offset 5; MySQL的SQL語句爲select * from tablename limit 5,3
參考資料:
一、https://docs.djangoproject.com/en/1.6/ref/models/querysets/
二、https://docs.djangoproject.com/en/1.6/topics/db/queries/