1.查看 Django queryset 執行的 SQL
1. print str(Author.objects.all().query) SELECT "blog_author"."id", "blog_author"."name", "blog_author"."qq", "blog_author"."addr", "blog_author"."email" FROM "blog_author" 簡化一下,就是:SELECT id, name, qq, addr, email FROM blog_author;
2.values_list 獲取元組形式結果
獲取作者的 name 和 qq In [6]: authors = Author.objects.values_list('name', 'qq') In [7]: authors Out[7]: <QuerySet [(u'WeizhongTu', u'336643078'), (u'twz915', u'915792575'), (u'wangdachui', u'353506297'), (u'xiaoming', u'004466315')]> In [8]: list(authors) Out[8]: [(u'WeizhongTu', u'336643078'), (u'twz915', u'915792575'), (u'wangdachui', u'353506297'), (u'xiaoming', u'004466315')] 如果只需要 1 個字段,可以指定 flat=True In [9]: Author.objects.values_list('name', flat=True) Out[9]: <QuerySet [u'WeizhongTu', u'twz915', u'wangdachui', u'xiaoming']> In [10]: list(Author.objects.values_list('name', flat=True)) Out[10]: [u'WeizhongTu', u'twz915', u'wangdachui', u'xiaoming']
3. values 獲取字典形式的結果
獲取作者的 name 和 qq In [13]: Author.objects.values('name', 'qq') Out[13]: <QuerySet [{'qq': u'336643078', 'name': u'WeizhongTu'}, {'qq': u'915792575', 'name': u'twz915'}, {'qq': u'353506297', 'name': u'wangdachui'}, {'qq': u'004466315', 'name': u'xiaoming'}]> In [14]: list(Author.objects.values('name', 'qq')) Out[14]: [{'name': u'WeizhongTu', 'qq': u'336643078'}, {'name': u'twz915', 'qq': u'915792575'}, {'name': u'wangdachui', 'qq': u'353506297'}, {'name': u'xiaoming', 'qq': u'004466315'}] 注意: 1. values_list 和 values 返回的並不是真正的 列表 或 字典,也是 queryset,他們也是 lazy evaluation 的(惰性評估,通俗地說,就是用的時候才真正的去數據庫查) 2. 如果查詢后沒有使用,在數據庫更新后再使用,你發現得到在是新內容!!!如果想要舊內容保持着,數據庫更新后不要變,可以 list 一下 3. 如果只是遍歷這些結果,沒有必要 list 它們轉成列表(浪費內存,數據量大的時候要更謹慎!!!)
4. extra 實現 別名,條件,排序等
extra 中可實現別名,條件,排序等,后面兩個用 filter, exclude 一般都能實現,排序用 order_by 也能實現。我們主要看一下別名這個 比如 Author 中有 name, Tag 中有 name 我們想執行 SELECT name AS tag_name FROM blog_tag; 這樣的語句,就可以用 select 來實現,如下: In [44]: tags = Tag.objects.all().extra(select={'tag_name': 'name'}) In [45]: tags[0].name Out[45]: u'Django' In [46]: tags[0].tag_name Out[46]: u'Django' 我們發現 name 和 tag_name 都可以使用,確認一下執行的 SQL In [47]: Tag.objects.all().extra(select={'tag_name': 'name'}).query.__str__() Out[47]: u'SELECT (name) AS "tag_name", "blog_tag"."id", "blog_tag"."name" FROM "blog_tag"' 我們發現查詢的時候弄了兩次 (name) AS "tag_name" 和 "blog_tag"."name" 如果我們只想其中一個能用,可以用 defer 排除掉原來的 name (后面有講) In [49]: Tag.objects.all().extra(select={'tag_name': 'name'}).defer('name').query.__str__() Out[49]: u'SELECT (name) AS "tag_name", "blog_tag"."id" FROM "blog_tag"'
5.annotate:聚合 計數,求和,平均數等
1.計算一下每個作者的文章數(我們每個作者都導入的Article的篇數一樣,所以下面的每個都一樣) Article.objects.all().values('author_id').annotate(count=Count('author')).values('author_id', 'count').query.__str__() Out[67]: u'SELECT "blog_article"."author_id", COUNT("blog_article"."author_id") AS "count" FROM "blog_article" GROUP BY "blog_article"."author_id"' 簡化一下SQL: SELECT author_id, COUNT(author_id) AS count FROM blog_article GROUP BY author_id 2.求一個作者的所有文章的得分(score)平均值 In [8]: Article.objects.values('author_id').annotate(avg_score=Avg('score')).values('author_id', 'avg_score').qu ...: ery.__str__() Out[8]: u'SELECT "blog_article"."author_id", AVG("blog_article"."score") AS "avg_score" FROM "blog_article" GROUP BY "blog_article"."author_id"' 3.求一個作者所有文章的總分 執行的SQL In [14]: Article.objects.values('author__name').annotate(sum_score=Sum('score')).values('author__name', 'sum_sco ...: re').query.__str__() Out[14]: u'SELECT "blog_author"."name", SUM("blog_article"."score") AS "sum_score" FROM "blog_article" INNER JOIN "blog_author" ON ("blog_article"."author_id" = "blog_author"."id") GROUP BY "blog_author"."name"' Out[13]: <QuerySet [{'author__name': u'WeizhongTu', 'sum_score': 1721}, {'author__name': u'twz915', 'sum_score': 1675}, {'author__name': u'zhen', 'sum_score': 1713}]> 6. select_related 優化一對一,多對一查詢 開始之前我們修改一個 settings.py 讓Django打印出在數據庫中執行的語句。 settings.py 尾部加上 LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console': { 'class': 'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'level': 'DEBUG' if DEBUG else 'INFO', }, }, } 這樣當 DEBUG 為 True 的時候,我們可以看出 django 執行了什么 SQL 語句 In [2]: Author.objects.all() Out[2]: (0.001) SELECT "blog_author"."id", "blog_author"."name", "blog_author"."qq", "blog_author"."addr", "blog_author"."email" FROM "blog_author" LIMIT 21; args=() 'out[2]'就是打出的 log。 In [13]: articles = Article.objects.all()[:10] In [14]: a1 = articles[0] # 取第一篇 (0.000) SELECT "blog_article"."id", "blog_article"."title", "blog_article"."author_id", "blog_article"."content", "blog_article"."score" FROM "blog_article" LIMIT 1; args=() In [15]: a1.title Out[15]: u'Django \u6559\u7a0b_1' In [16]: a1.author_id Out[16]: 5 In [17]: a1.author.name # 再次查詢了數據庫,注意!!! (0.000) SELECT "blog_author"."id", "blog_author"."name", "blog_author"."qq", "blog_author"."addr", "blog_author"."email" FROM "blog_author" WHERE "blog_author"."id" = 5; args=(5,) Out[17]: u'zhen' 這樣的話我們遍歷查詢結果的時候就會查詢很多次數據庫,能不能只查詢一次,把作者的信息也查出來呢? 當然可以,這時就用到 select_related,我們的數據庫設計的是一篇文章只能有一個作者,一個作者可以有多篇文章。 現在要查詢文章的時候連同作者一起查詢出來,“文章”和“作者”的關系就是多對一,換句說說,就是一篇文章只可能有一個作者。 In [18]: articles = Article.objects.all().select_related('author')[:10] In [19]: a1 = articles[0] # 取第一篇 (0.000) SELECT "blog_article"."id", "blog_article"."title", "blog_article"."author_id", "blog_article"."content", "blog_article"."score", "blog_author"."id", "blog_author"."name", "blog_author"."qq", "blog_author"."addr", "blog_author"."email" FROM "blog_article" INNER JOIN "blog_author" ON ("blog_article"."author_id" = "blog_author"."id") LIMIT 1; args=() In [20]: a1.title Out[20]: u'Django \u6559\u7a0b_1' In [21]: a1.author.name # 嘻嘻,沒有再次查詢數據庫!! Out[21]: u'zhen'
7. prefetch_related 優化一對多,多對多查詢
prefetch_related和 select_related 功能類似,但是實現不同。 select_related 是使用 SQL JOIN 一次性取出相關的內容。 prefetch_related 用於 一對多,多對多 的情況,這時 select_related 用不了,因為當前一條有好幾條與之相關的內容。 prefetch_related是通過再執行一條額外的SQL語句,然后用 Python 把兩次SQL查詢的內容關聯(joining)到一起 我們來看個例子,查詢文章的同時,查詢文章對應的標簽。“文章”與“標簽”是多對多的關系。 In [11]: articles = Article.objects.all().prefetch_related('tags')[:3] In [12]: for a in articles: ...: print a.title, a.tags.all() ...: (0.000) SELECT "blog_article"."id", "blog_article"."title", "blog_article"."author_id", "blog_article"."content", "blog_article"."score" FROM "blog_article" LIMIT 3; args=() (0.000) SELECT ("blog_article_tags"."article_id") AS "_prefetch_related_val_article_id", "blog_tag"."id", "blog_tag"."name" FROM "blog_tag" INNER JOIN "blog_article_tags" ON ("blog_tag"."id" = "blog_article_tags"."tag_id") WHERE "blog_article_tags"."article_id" IN (1, 2, 3); args=(1, 2, 3) Django 教程_1 <QuerySet [<Tag: Django>]> Django 教程_2 <QuerySet [<Tag: Django>]> Django 教程_3 <QuerySet [<Tag: Django>]> 遍歷查詢的結果: 不用 prefetch_related 時 In [9]: articles = Article.objects.all()[:3] In [10]: for a in articles: ...: print a.title, a.tags.all() ...: (0.000) SELECT "blog_article"."id", "blog_article"."title", "blog_article"."author_id", "blog_article"."content", "blog_article"."score" FROM "blog_article" LIMIT 3; args=() (0.000) SELECT "blog_tag"."id", "blog_tag"."name" FROM "blog_tag" INNER JOIN "blog_article_tags" ON ("blog_tag"."id" = "blog_article_tags"."tag_id") WHERE "blog_article_tags"."article_id" = 1 LIMIT 21; args=(1,) Django 教程_1 <QuerySet [<Tag: Django>]> (0.000) SELECT "blog_tag"."id", "blog_tag"."name" FROM "blog_tag" INNER JOIN "blog_article_tags" ON ("blog_tag"."id" = "blog_article_tags"."tag_id") WHERE "blog_article_tags"."article_id" = 2 LIMIT 21; args=(2,) Django 教程_2 <QuerySet [<Tag: Django>]> (0.000) SELECT "blog_tag"."id", "blog_tag"."name" FROM "blog_tag" INNER JOIN "blog_article_tags" ON ("blog_tag"."id" = "blog_article_tags"."tag_id") WHERE "blog_article_tags"."article_id" = 3 LIMIT 21; args=(3,) Django 教程_3 <QuerySet [<Tag: Django>]>
8. defer 排除不需要的字段
在復雜的情況下,表中可能有些字段內容非常多,取出來轉化成 Python 對象會占用大量的資源。 這時候可以用 defer 來排除這些字段,比如我們在文章列表頁,只需要文章的標題和作者,沒有必要把文章的內容也獲取出來(因為會轉換成python對象,浪費內存) In [13]: Article.objects.all() Out[13]: (0.000) SELECT "blog_article"."id", "blog_article"."title", "blog_article"."author_id", "blog_article"."content", "blog_article"."score" FROM "blog_article" LIMIT 21; args=() # 注意這里沒有查 content 字段了 In [14]: Article.objects.all().defer('content') Out[14]: (0.000) SELECT "blog_article"."id", "blog_article"."title", "blog_article"."author_id", "blog_article"."score" FROM "blog_article" LIMIT 21; args=()
9. only 僅選擇需要的字段
和 defer 相反,only 用於取出需要的字段,假如我們只需要查出 作者的名稱 In [15]: Author.objects.all().only('name') Out[15]: (0.000) SELECT "blog_author"."id", "blog_author"."name" FROM "blog_author" LIMIT 21; args=() <QuerySet [<Author: WeizhongTu>, <Author: twz915>, <Author: dachui>, <Author: zhe>, <Author: zhen>]> 細心的同學會發現,我們讓查 name , id 也查了,這個 id 是 主鍵,能不能沒有這個 id 呢? 試一下原生的 SQL 查詢 In [26]: authors = Author.objects.raw('select name from blog_author limit 1') In [27]: author = authors[0] (0.000) select name from blog_author limit 1; args=() --------------------------------------------------------------------------- InvalidQuery Traceback (most recent call last) <ipython-input-27-51c5f914fff2> in <module>() ----> 1author = authors[0] /usr/local/lib/python2.7/site-packages/django/db/models/query.pyc in __getitem__(self, k) 1275 1276 def __getitem__(self, k): -> 1277 return list(self)[k] 1278 1279 @property /usr/local/lib/python2.7/site-packages/django/db/models/query.pyc in __iter__(self) 1250 if skip: 1251 if self.model._meta.pk.attname in skip: -> 1252 raise InvalidQuery('Raw query must include the primary key') 1253 model_cls = self.model 1254 fields =[self.model_fields.get(c)for c in self.columns] InvalidQuery: Raw query must include the primary key 報錯信息說 非法查詢,原生SQL查詢必須包含 主鍵! 再試試直接執行 SQL tu@pro ~/zqxt $ python manage.py dbshell SQLite version 3.14.0 2016-07-26 15:17:14 Enter ".help" for usage hints. sqlite> select name from blog_author limit 1; WeizhongTu <--- 成功!!! 雖然直接執行SQL語句可以這樣,但是 django queryset 不允許這樣做,一般也不需要關心,反正 only 一定會取出你指定了的字段。
10. 自定義聚合功能
我們前面看到了 django.db.models 中有 Count, Avg, Sum 等,但是有一些沒有的,比如 GROUP_CONCAT,它用來聚合時將符合某分組條件(group by)的不同的值,連到一起,作為整體返回。 新建一個文件 比如 my_aggregate.py from django.db.models import Aggregate, CharField class GroupConcat(Aggregate): function = 'GROUP_CONCAT' template = '%(function)s(%(distinct)s%(expressions)s%(ordering)s%(separator)s)' def __init__(self, expression, distinct=False, ordering=None, separator=',', **extra): super(GroupConcat, self).__init__( expression, distinct='DISTINCT ' if distinct else '', ordering=' ORDER BY %s' % ordering if ordering is not None else '', separator=' SEPARATOR "%s"' % separator, output_field=CharField(), **extra ) 使用時先引入 GroupConcat 這個類,比如聚合后的錯誤日志記錄有這些字段 time, level, info 我們想把 level, info 一樣的 聚到到一起,按時間和發生次數倒序排列,並含有每次日志發生的時間。 ErrorLogModel.objects.values('level', 'info').annotate( count=Count(1), time=GroupConcat('time', ordering='time DESC', separator=' | ') ).order_by('-time', '-count')
11.Model.objects.bulk_create() 更快更方便
#使用Model.objects.create() for line in f: title,content = line.split('****') Blog.objects.create(title=title,content=content) # 使用Model.objects.bulk_create() BlogList = [] for line in f: title,content = line.split('****') blog = Blog(title=title,content=content) BlogList.append(blog) Blog.objects.bulk_create(BlogList) 由於Blog.objects.create()每保存一條就執行一次SQL,而bulk_create()是執行一條SQL存入多條數據,做會快很多!當然用列表解析代替 for 循環會更快!! # 使用列表 BlogList = [Blog(title=line.split('****')[0], content=line.split('****')[1]) for line in f] Blog.objects.bulk_create(BlogList)
12.合並QuerySet
當你想要讓兩個或者多個 queryset 合並為一個 queryset 的時候, 並且希望使用 list, 而且想要保留對象的filter, count, distinct等 queryset 方法。 查看以下模型: Python class Story(models.Model): title = models.CharField(max_length=255) content = models.TextField(blank=True) category = models.ForeignKey(Category, related_name='stories') author = models.ForeignKey(User, related_name='stories') class Medium(models.Model): name = models.CharField(max_length=30, unique=True) stories = models.ManyToManyField(Story) 假設你想顯示特定的 Medium 中發布的故事以及屬於特定作者的所有故事。那么一般情況下, 你用以下方法獲取了兩個 queryset。 Python medium = Medium.objects.get(name='Django Blog') user = User.objects.get(username='vitor') django_stories = medium.stories.all() vitor_stories = user.stories.filter(category__name='django') 此時我們有兩個查詢集(QuerySets) ,我們可以通過| 運算符來合並這兩個查詢集。 Python stories = django_stories | vitor_stories # merge querysets 此時你依然可以使用 queryset 的一些操作方法. Python recent_stories = stories.distinct().order_by('-date')[:10] 注意: 合並運算符僅適用於同一類型的 queryset, 並且數據為切片前數據集.