1. 程式人生 > >Django ORM之QuerySet

Django ORM之QuerySet

    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為基礎。

1.QuerySet

回到頂部

1.1 簡介

每個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也有,比如切片,遍歷。

>>> from userex.models import UserEx

>>> type(UserEx.objects)

<class ‘django.db.models.manager.Manager’>

>>> a = UserEx.objects.all()

>>> type(a)

<class ‘django.db.models.query.QuerySet’>

QuerySet是延遲獲取的,只有當用到這個QuerySet時,才會查詢資料庫求值。另外,查詢到的QuerySet又是快取的,當再次使用同一個QuerySet時,並不會再查詢資料庫,而是直接從快取獲取(不過,有一些特殊情況)。一般而言,當對一個沒有求值的QuerySet進行的運算,返回的是QuerySet、ValuesQuerySet、ValuesListQuerySet、Model例項時,一般不會立即查詢資料庫;反之,當返回的不是這些型別時,會查詢資料庫。下面介紹幾種(並非全部)對QuerySet求值的場景。

class Blog(models.Model):

    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為例。

I遍歷

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。

II切片

切片不會立即執行,除非顯示指定了步長,如a= Entry.objects.all()[0:10:2],步長為2。

III序列化,即Pickling

序列化QuerySet很少用

IV repr()

和str()功能相似,將物件轉為字串,很少用。

V len()

計算QuerySet元素的數量,並不推薦使用len(),除非QuerySet是求過值的(即evaluated),否則,用QuerySet.count()獲取元素數量,這個效率要高。

VI list()

將QuerySet轉為list

VII bool(),判斷是否為空

if Entry.objects.filter(headline="Test"):

     print("There is at least one Entry with the headline Test")

 同樣不建議這種方法判斷是否為空,而應該使用QuerySet.exists(),查詢效率高

回到頂部

1.2 QuerySet的方法

資料庫的常用操作就四種:增、刪、改、查,QuerySet的方法涉及刪、改、查。後面還會講model物件的方法,model方法主要是增、刪、改、還有呼叫model例項的欄位。

(1) 刪delete()

原型: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

(2) 改 update()

批量修改,返回修改的記錄數。不過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()會出錯。

(3)查詢 filter(**kwargs)、exclude(**kwargs)、get(**kwargs)

相當於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')

(4)SQL其它關鍵字在django中的實現

在SQL中,很多關鍵詞在刪、改、查時都是可以用的,如order by、 like、in、join、union、and、or、not等等,我們以查詢為例,說一下django如何對映SQL的這些關鍵字的(查、刪、改中這些關鍵字的使用方法基本相同)。

No1  F類(無對應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'))

No2  Q類(對應and/or/not)

如果有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類,待測試。

No3  annotate(無對應SQL關鍵字)

函式原型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.*

No4  order_by——對應order by

函式原型 order_by(*fields)

返回QuerySet

正向的反向關聯表跟filter的方式一樣。如果直接用欄位名,那就是升序asc排列;如果欄位名前加-,就是降序desc

No5  distinct——對應distinct

原型 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

No6  values()和values_list()——對應‘select 某幾個欄位’

函式原型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’]

No7  select_related()——對應返回關聯記錄實體

原型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)

No8  prefetch_related(*field) ——對應返回關聯記錄實體的集合

函式原型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):

相關推薦

Django ORMQuerySet詳解

##概述: Django ORM用到三個類:Manager、QuerySet、Model。Manager定義表級方法(表級方法就是影響一條或多條記錄的方法),我們可以以models.Manager為父類,定義自己的manager,增加表級方法;QuerySet:Manager類的一些方法會返

Django ORMQuerySet

    Django ORM用到三個類:Manager、QuerySet、Model。Manager定義表級方法(表級方法就是影響一條或多條記錄的方法),我們可以以models.Manager為父類,定義自己的manager,增加表級方法;QuerySet:Manager類的

Django框架 querySet詳解

int 框架 子句 cell cts syn 重復執行 span pytho 瀏覽目錄 可切片 可叠代 惰性查詢 緩存機制 exists()與iterator()方法 QuerySet 可切片 使用Python 的切片語法來限制查詢集記錄的數目

DjangoORM單表操作

一 單表操作 建立表 1 建立模型   建立名為book的app,在book下的models.py中建立模型: from django.db import models # Create your models here. class Book(models.M

Django基礎數據庫與ORM

page 寫入 src postgres def 由於 ont 方法 cut 一.數據庫配置 1.django默認支持sqlite,mysql, oracle,postgresql數據庫。 django默認使用sqlite的數據庫,默認自帶sqlite的數據庫驅動

Django(MTV) M:ORM

double field userinfo 系統結構 上傳文件 protocol 如何實現 ger 一定的 ORM前序 到目前為止,當我們的程序涉及到數據庫相關操作時,我們一般都會這麽搞: 創建數據庫,設計表結構和字段 使用 MySQLdb 來連接數據庫,並編寫數

django基礎ORM

配置文件 nbsp rom 查看 如果 並不是 rem decimal 屬性和方法 一、定義 1.什麽是ORM? ORM,即Object-Relational Mapping(對象關系映射),它的作用是在關系型數據庫和業務實體對象之間作一個映射,這樣,我們在具體的操作業務對

DjangoORM查詢表記錄

temp and 返回 等於 play bject aggregate efi alt 查詢相關API from django.db import models # Create your models here. class Book(models.Model):

django知識ORM查詢

知識 mod rim desc not tinc index 日期 統計 1. ORM字段 1. AutoField(primary_key=True) -------->自增且主鍵 2. CharField(max_length=16)

Django框架ORM

1,欄位和欄位的引數 1.1>ORM的概念:物件對映模型(Objects Relational Model)是一種為了解決面向物件和關係型資料庫存在的互不匹配的現象和技術,簡單的說,ORM是通過使用描述物件和資料庫之間對映的元資料,將程式中的物件自動持久化到關係資料庫中,ORM是連線業務邏輯和關係型資

毛毛Django修煉路2——當ORM愛上Django

毛毛Django修煉之路2——當ORM愛上Django 一、遇上ORM 1、ORM優缺點 優點: 1、簡單、不通自己寫SQL語句 2、開發效率高 缺點: 1、需要記憶特殊語法 2、相對於正統的SQL語句,執行效率低一些。 2、ORM與Python函式對應關係

python學習筆記(92) Django基礎ORM

models back efi engine all 信息 記錄 super bin 1. ORM已經的學過的內容:   1. Django項目如何使用ORM連接MySQL     1. 手動創建數據庫     2. 在settings.py裏面配置一下數據庫的連

Django ORM queryset object 解釋(子查詢和join連表查詢的結果)

解釋 pri mod span books round 取數據 通過 color #下面兩種是基於QuerySet查詢 也就是說SQL中用的jion連表的方式查詢books = models.UserInfo.objects.all() print(type(books)

Django框架Model系統:ORM基本增刪改查操作(二)

我們 raw 評論 div tst 操作符 部門 pad files 1、創建模型 例:我們來假定下面這些概念,字段和關系   作者模型:一個作者有姓名和年齡。   作者詳細模型:把作者的詳情放到詳情表,包含生日,手機號,家庭住址等信息。作者詳情模型和作者模型之間是一

Django ORM效能優化count和len方法的選擇(非常詳細推薦乾貨)

  接下來我將從原始碼層面分情況和應用分析我們在計算queryset資料集時是用orm的count函式計算長度還是用len函式計算資料集長度。     首先,我們知道ORM查詢queryset資料集是惰性查詢的,只有使用到資料集時,ORM才會真正去執行查詢語句,然後ORM會把查詢到的資料集快取到記憶

django基礎數據庫操作

tor tap request set pycha 字段 shortcuts 啟動 tex Django 自稱是“最適合開發有限期的完美WEB框架”。本文參考《Django web開發指南》,快速搭建一個blog 出來,在中間涉及諸多知識點,這裏不會詳細說明,如果你是第一次

Django入門數據庫相關

script web 數據庫文件 字段 python代碼 pan wid django 數據庫 1. 數據庫設置 在settings.py中配置數據庫 我首先使用的是sqlite3,所以配置如下 2. 數據庫的數據結構定義   #blog/models.py #定義了

Django ORM常用的函數以及修飾詞

字段 函數 gre 字符 () width date() ble wid 函數名稱或修飾詞 說明 filter() 返回符合指定條件的QuerySet exclude() 返回不符合指定條件的QuerySet ordey_by() 串接到QuerySet之

Django ORM 一對多表的創建

django 外鍵 一對多 前面已經學習了在Django裏面,如何對單表進行增刪改查詢。下面學習一下如果存在外鍵約束的情況下,如何創建。例1models.pyfrom django.db import models class UserGroup(models.Model): uid =

Django ORM------Mysql

存儲 hang body ont 軟件 使用 傳遞 用戶權限 cell ORM操作 select * from tb where id > 1 #對應關系 models.tb.objects.filter(id__gt=1) models.tb.objects.fil