一、QuerySet API介绍
一旦你建立好数据模型,Django会自动为你生成一套数据库抽象的API(QuerySet查询集方法),可以让你创建、检索、更新和删除对象。
Django使用一种直观的方式把数据库表中的数据表示成Python对象:一个模型类代表数据库中的一个表,一个模型类的实例代表这个数据库表中的一条特定的记录。使用关键字参数实例化模型实例来创建一个对象,然后调用save()把它保存到数据库中。
使用python manage.py shell命令可以进入到Python交互式窗口,跟你执行Python直接进入交互式窗口是不同的,前者会加载一些环境变量。
下面主要是通过一个简单的多人博客模型来学一些常见的各类查询,基本都是根据一些实用示例来的。
下面是一个简单的多人博客模型示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
from django.db import models # 标记用户博客简介 class Blog(models.Model): name = models.CharField(max_length=100) tagline = models.TextField() def __str__(self): return self.name # 标记作者 class Author(models.Model): name = models.CharField(max_length=50) email = models.EmailField() def __str__(self): return self.name # 标记用户博文 class Entry(models.Model): blog = models.ForeignKey(Blog, on_delete=models.CASCADE) headline = models.CharField(max_length=255) body_text = models.TextField() pub_date = models.DateField() authors = models.ManyToManyField(Author) n_comments = models.IntegerField(default=0) n_pingbacks = models.IntegerField(default=0) rating = models.IntegerField(default=0) def __str__(self): return self.headline |
插入一些测试数据:
1 2 3 4 5 6 |
>>> from polls.models import * >>> b = Blog(name="Beatles Blog", tagline="All the latest Beatles news.") >>> b.save() >>> a1 = Author.objects.create(name="dkey", email="dkey@163.com") >>> a2 = Author.objects.create(name="jerry", email="jerry@163.com") >>> a3 = Author.objects.create(name="jerry", email="jerry@qq.com") |
二、生成新QuerySet的方法
- filter()
查询过滤
1 2 |
>>> Author.objects.filter(name="dkey") <QuerySet [<Author: dkey>]> |
filter查询是指精确查询,而不是通配匹配查询。
- exclude()
反向查询,指返回查询条件相反的对象。
1 2 |
>>> Author.objects.exclude(name="dkey") <QuerySet [<Author: jerry>, <Author: jerry>]> |
- order_by()
对结果集进行升序或降序,可指定需要排序的字段。
1 2 3 4 5 6 7 |
# 升序 >>> Author.objects.filter().order_by('name') <QuerySet [<Author: dkey>, <Author: jerry>, <Author: jerry>]> # 降序 >>> Author.objects.filter().order_by('-name') <QuerySet [<Author: jerry>, <Author: jerry>, <Author: dkey>]> |
要随机排序可使用“?”
1 2 |
>>> Author.objects.order_by('?') <QuerySet [<Author: jerry>, <Author: dkey>, <Author: jerry>]> |
注意:order_by(‘?’)根据您使用的数据库后端,查询可能非常昂贵并且速度很慢。
- reverse()
使用reverse()方法可以颠倒查询集元素返回的顺序,reverse()再次调用将恢复到正常方向的顺序。
- values()
QuerySet当作为迭代器使用时,返回一个返回字典,而不是模型实例。
这些字典中的每一个都表示一个对象,其中的键与模型对象的属性名称相对应。
1 2 |
>>> Author.objects.filter(name='jerry').values() <QuerySet [{'id': 2, 'name': 'jerry', 'email': 'jerry@163.com'}, {'id': 3, 'name': 'jerry', 'email': 'jerry@qq.com'}]> |
该values()方法采用可选的位置参数,*fields它指定SELECT应限制的字段名称。如果指定了字段,每个字典将仅包含您指定字段的字段键/值。如果不指定字段,则每个字典将包含数据库表中每个字段的键和值。
1 2 |
>>> Author.objects.values('name') <QuerySet [{'name': 'dkey'}, {'name': 'jerry'}, {'name': 'jerry'}]> |
该values()方法还包含可选的关键字参数 **expressions,它们被传递给annotate():
1 2 3 |
>>> from django.db.models.functions import Lower >>> Author.objects.values(lower=Lower('name')) <QuerySet [{'lower': 'dkey'}, {'lower': 'jerry'}, {'lower': 'jerry'}]> |
- values_list()
迭代时返回元组,每个元组都包含传入values_list()调用的相应字段或表达式的值,因此第一个项目是第一个字段等。例如:
1 2 |
>>> Author.objects.values_list('id', 'name') <QuerySet [(1, 'dkey'), (2, 'jerry'), (3, 'jerry')]> |
如果只传入单个字段,则还可以传入 flat 参数,如果为True,这将意味着返回的结果是单值,而不是一元组。如下例子:
1 2 |
>>> Author.objects.values_list('name', flat=True).order_by('-id') <QuerySet ['jerry', 'jerry', 'dkey']> |
当有多个字段时 flat 传入错误。
将结果返回为列表有时候很有用,可能我们需要一个复杂的查询后返回列表,也可以使用filter先过滤出期望数据,然后再返回为列表。
1 |
>>> Author.objects.filter(name='dkey').values_list('id', flat=True) |
你也可以通过named=True以获得结果为 namedtuple():
1 2 |
>>> Author.objects.values_list('id','name', named=True) <QuerySet [Row(id=1, name='dkey'), Row(id=2, name='jerry'), Row(id=3, name='jerry')]> |
使用命名元组可能会使结果更具可读性,将结果转换为命名元组的代价很小。
如果你不传递任何值values_list(),它将按照声明的顺序返回模型中的所有字段。
- distinct()
去重查询,消除查询结果中的重复行,返回一个新的QuerySet。
1 2 3 4 5 |
>>> Author.objects.filter().values('name').distinct() #filter可省略 <QuerySet [{'name': 'dkey'}, {'name': 'jerry'}]> >>> Author.objects.filter().distinct().values_list('name') <QuerySet [('dkey',), ('jerry',)]> |
做完去重之后,如果想把结果封装成一个list,代码如下:
1 2 3 |
>>> list = [i[0] for i in Author.objects.distinct().values_list('name')] >>> list ['dkey', 'jerry'] |
- dates()
dates(field,kind,order =’ASC’)
返回一个表达式,该表达式表示QuerySet一个datetime.date对象列表。
field应该是DateField你模型的名称,kind应该是”year”,”month”或者”day”。datetime.date结果列表中的每个对象都被“截断”给定type。
“year” 返回该字段的所有不同年份值的列表。
“month” 返回该字段的所有不同年/月值的列表。
“day” 返回该字段的所有不同年/月/日值的列表。
order,默认为’ASC’,应该是’ASC’或者 ‘DESC’。这指定了如何排序结果。
1 2 3 4 5 6 7 8 9 10 |
>>> Entry.objects.dates('pub_date', 'year') [datetime.date(2005, 1, 1)] >>> Entry.objects.dates('pub_date', 'month') [datetime.date(2005, 2, 1), datetime.date(2005, 3, 1)] >>> Entry.objects.dates('pub_date', 'day') [datetime.date(2005, 2, 20), datetime.date(2005, 3, 20)] >>> Entry.objects.dates('pub_date', 'day', order='DESC') [datetime.date(2005, 3, 20), datetime.date(2005, 2, 20)] >>> Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day') [datetime.date(2005, 3, 20)] |
- datetimes()
datetimes(field_name,kind,order=’ASC’,tzinfo = None)
返回一个表达式,该表达式表示QuerySet一个datetime.datetime对象列表。
field_name应该是DateTimeField你模型的名称。
kind应该是”year”,”month”,”day”,”hour”,”minute”或”second”。datetime.datetime结果列表中的每个对象都被“截断”给定type。
order,默认为’ASC’,应该是’ASC’或者 ‘DESC’,这指定了如何排序结果。
tzinfo定义在截断之前将日期时间转换到的时区。事实上,根据使用的时区,给定的日期时间具有不同的表示形式。该参数必须是一个datetime.tzinfo对象。如果是None,Django使用当前时区。当它没有效果USE_TZ的 False。
- raw()
在模型查询API不够用的情况下,你可以使用原始的SQL语句。Django提供两种方法使用原始SQL进行查询:一种是使用Manager.raw()方法,进行原始查询并返回模型实例;另一种是完全避开模型层,直接执行自定义的SQL语句。
raw()接受一个原始的SQL查询,执行它并返回一个 django.db.models.query.RawQuerySet实例。这个RawQuerySet实例可以像正常一样迭代QuerySet以提供对象实例。
1 2 3 |
>>> raw = Author.objects.raw('select * from polls_author') >>> type(raw) <class 'django.db.models.query.RawQuerySet'> |
传入变量的方式:
1 2 3 4 |
raw = Author.objects.raw( ''' select * from polls_author where name = '%s' ORDER BY id desc ''' % name) |
通过raw方法查询的结果是一个RawQuerySet对象,如果想取到所有的值可以这么做:
1 2 |
>>> raw[0].__dict__ {'_state': <django.db.models.base.ModelState object at 0x10868fe48>, 'id': 1, 'name': 'dkey', 'email': 'dkey@ywnds.com'} |
借助这个方法我们可以序列化一下这个对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def Serialization(_obj: object) -> list: ''' _obj: objext -> list, Python 3.6新加入的特性, 用来标识这个方法接收一个对象并返回一个list orm.raw序列化 ''' _list = [] _get = [] for i in _obj: _list.append(i.__dict__) for i in _list: del i['_state'] _get.append(i) return _get |
使用方式:
1 2 |
>>> Serialization(raw) [{'id': 1, 'name': 'dkey', 'email': 'dkey@ywnds.com'}] |
- 链式查询
可以在filter后跟exclude,如下:
1 2 |
>>> Author.objects.filter(name="dkey").exclude(name="filter") <QuerySet [<Author: dkey>]> |
也可以使用一些高级过滤,比如过滤以某某开头及某某结尾的名称:
1 2 |
>>> Author.objects.filter(name__startswith='d').filter(name__endswith='y') <QuerySet [<Author: dkey>]> |
- 复杂查询Q filter()
Q filter()方法中的关键字参数查询都是一起进行“AND”的,如果你需要执行更复杂的查询(例如OR语句),你可以使用Q对象。
1 2 3 4 5 6 7 8 9 |
>>> from django.db.models import Q # OR; >>> Author.objects.filter(Q(name="dkey")|Q(name="jerry")) <QuerySet [<Author: dkey>, <Author: jerry>, <Author: jerry>]> # AND; >>> Author.objects.filter(Q(name="dkey")&Q(name="jerry")) <QuerySet []> |
每个接受关键字参数的查询函数(如filter()、exclude()、get())都可以传递一个活多个Q对象作为位置(不带名的)参数。如果一个查询函数有多个Q对象参数,这些参数的逻辑关系为“AND”。查询函数可以混合使用Q对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q对象)都将“AND”在一起。但是,如果出现Q对象,它必须位于所有关键字参数的前面。例如:
1 2 3 4 5 6 7 8 9 |
>>> Author.objects.filter(Q(name="dkey"),Q(email="jerry@163.com")) <QuerySet []> >>> Author.objects.filter(Q(name="dkey"),Q(email="dkey@163.com")) <QuerySet [<Author: dkey>]> >>> Author.objects.filter(email="jerry@163.com",Q(name="dkey")) File "<console>", line 1 SyntaxError: positional argument follows keyword argument |
使用 Q 还可以使用非逻辑,比如不能等于某个条件:
1 |
>>> Author.objects.filter(~Q(name="dkey")) |
有些时候我们需要根据前端选择的不同字段进行构建查询条件,Q 也支持:
1 2 3 4 5 |
q1 = Q() q1.connector = 'OR' q1.children.append(('name', "cox")) q1.children.append(('name', "Tom")) q1.children.append(('name', "Jeck")) |
如果有多个逻辑表达式,也可以支持:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
con = Q() q1 = Q() q1.connector = 'OR' q1.children.append(('name', "cox")) q1.children.append(('name', "Tom")) q1.children.append(('name', "Jeck")) q2 = Q() q2.connector = 'OR' q2.children.append(('age', 12)) con.add(q1, 'AND') con.add(q2, 'AND') |
- 保存ForeignKey和ManyToManyField字段
1 2 |
>>> b = Blog.objects.get(name='Beatles Blog') >>> e = Entry.objects.create(blog=b,headline='hello',body_text='world',pub_date=timezone.now(),n_comments=1,n_pingbacks=1,rating=1) |
1 2 3 4 |
>>> b.entry_set.all() <QuerySet [<Entry: hello>]> >>> b.entry_set.count() 1 |
保存ManyToManyField,在创建Entry时不需要必须指定authors,它是一个ManyRelatedManager管理器,添加authors方式如下:
1 2 3 4 5 |
>>> a1 = Author.objects.get(name='dkey') >>> a2 = Author.objects.get(name='jerry') >>> e.authors = [a1] >>> e.authors = [a1, a2] >>> e.save() |
可以给e.authors对象指定列表,既然是管理器,所以也有很多方法可以使用,比如添加用户和查询用户:
1 2 3 4 5 |
>>> e.authors.add(a1,a2) >>> e.authors.all() <QuerySet [<Author: dkey>, <Author: jerry>]> >>> e.authors.all()[0] <Author: dkey> |
当Entry有了之后,也可以快速创建authors,如下:
1 2 3 4 |
>>> e.authors.create(name='mark',email='mark@163.com') <Author: mark> >>> e.authors.all() <QuerySet [<Author: dkey>, <Author: jerry>, <Author: mark>]> |
并且会自动把authors加入到Author表,如下:
1 2 |
>>> Author.objects.all() <QuerySet [<Author: dkey>, <Author: jerry>, <Author: mark>]> |
- 反向查询
如果模型有一个ForeignKey,那么该ForeignKey所指的模型实例可以通过一个管理器返回前一个模型的所有实例。默认情况下,这个管理器的名字为foo_set,其中foo是源模型的小写名称。该管理器返回的查询集可以用在过滤和操作上。
1 2 3 4 5 |
b = Blog.objects.get(id=1) b.entry_set.all() b.entry_set.filter(headline_contains='L') b.entry_set.count() |
- 跨关联关系查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# 跨关系反向查询; >>> Entry.objects.filter(blog__name='Beatles Blog') <QuerySet [<Entry: hello>]> >>> Blog.objects.filter(entry__headline__contains='hello') <QuerySet [<Blog: Beatles Blog>]> # 跨关系多层查询; >>> Blog.objects.filter(entry__authors__name__isnull=True) <QuerySet []> # 跨关系多层多过滤查询; >>> Blog.objects.filter(entry__authors__isnull=False,entry__authors__name__contains='dkey') <QuerySet [<Blog: Beatles Blog>]> |
- 引用模型字段查询
使用F可以获取模型字段值,常用来比较两个模型字段:
1 2 3 4 5 6 7 |
# 过滤n_comments大于等于n_pingbacks的对象; >>> Entry.objects.filter(n_comments__gte=F('n_pingbacks')) <QuerySet [<Entry: hello>]> # 过滤rating小于n_pingbacks + n_comments的对象; >>> Entry.objects.filter(rating__lt=F('n_pingbacks') + F('n_comments')) <QuerySet [<Entry: hello>]> |
- 限制返回对象
1 2 3 4 5 |
# 返回前5个对象; >>> Entry.objects.all()[:5] # 返回第3个至第5个对象; >>> Entry.objects.all()[3:5] |
三、不返回QuerySet的方法
以下QuerySet方法结果集不返回QuerySet。这些方法不使用缓存。相反,他们每次查询都会调用数据库。
- get()
返回匹配给定查找参数的对象,该参数应采用字段查找中描述的格式。
1 2 |
>>> Person.objects.get(id=1) >>> Person.objects.get(id=1, name='dkey') |
如果没有找到给定参数的对象,则引发异常。
- create()
创建对象并将其全部保存在一个步骤中的便捷方法。从而:
1 |
p = Person.objects.create(first_name="Bruce", last_name="Springsteen") |
和
1 2 |
p = Person(first_name="Bruce", last_name="Springsteen") p.save(force_insert=True) |
是等同的。
- get_or_create()
get_or_create(defaults=None, **kwargs)
一种方便的方法,get_or_create 方法尝试从数据库中获取基于给定 kwargs 的对象。如果没有找到匹配项则创建一个新的对象。
返回一个元组 (object, created),其中 object 是检索或创建的对象,created 是一个布尔值,指定是否创建了新对象。
这是代码编写的一种便捷,例如:
1 2 3 4 5 |
try: obj = Author.objects.get(name='dkey1') except Author.DoesNotExist: obj = Author(name='dkey1', email='dkey@ywnds.com') obj.save() |
随着模型中字段数量的增加,这种模式变得相当不便。上面的例子可以使用get_or_create()像这样重写:
1 2 3 4 |
obj, created = Author.objects.get_or_create( name='dkey', defaults={'email': 'dkey@ywnds.com'}, ) |
传递给 get_or_create () 的任何关键字参数 (除可选的”defaults”) 都将用于获取 get() 调用。如果找到一个对象,get_or_create() 将返回该对象的元组并进行 False。如果找到多个对象,get_or_create 将引发 MultipleObjectsReturned异常。如果找不到对象,get_or_create() 将实例化并创建一个新对象,返回新对象的元组和 True。将根据该算法粗略地创建新对象:
1 2 3 4 |
params = {k: v for k, v in kwargs.items() if '__' not in k} params.update({k: v() if callable(v) else v for k, v in defaults.items()}) obj = self.model(**params) obj.save() |
该 get_or_create() 方法和 create() 方法使用手动指定主键时的错误行为类似,如果需要创建对象并且数据库中已存在唯一key,则会引发一个IntegrityError异常。
- update_or_create()
update_or_create(defaults=None, **kwargs)
一种方便的方法,update_or_create 方法尝试从数据库中获取基于给定 kwargs 的对象。如果找到匹配项,它将更新在默认字典中传递的字段。如果需要更新对象没有则创建一个新的。defaults 是用于更新对象的 (字段、值) 对的字典,默认值可以是 callables。
返回一个元组(object, created),其中 object 是创建或更新的对象,created是一个布尔值,指定是否创建了新对象。
这是代码编写的一种便捷,例如:
1 2 3 4 5 6 7 8 9 10 11 |
defaults = {'email': 'dkey@ywnds.com'} try: obj = Author.objects.get(name='dkey') for key, value in defaults.items(): setattr(obj, key, value) obj.save() except Author.DoesNotExist: new_values = {'name': 'dkey'} new_values.update(defaults) obj = Author(**new_values) obj.save() |
随着模型中字段数量的增加,这种模式变得相当不便。上面的例子可以使用update_or_create()像这样重写:
1 2 3 4 |
obj, created = Author.objects.update_or_create( name='dkey', defaults={'email': 'dkey@ywnds.com'}, ) |
如上 get_or_create() 所述,如果在数据库级别不强制执行唯一性,则该方法容易出现竞争状况,这可能导致同时插入多行。像 get_or_create() 和 create(),如果你使用手动指定主键和需要创建一个对象,但主键在数据库中已存在,引发IntegrityError。
- bulk_create()
bulk_create(objs, batch_size=None)
此方法以高效的方式将提供的对象列表插入数据库(通常只有1个查询,不管有多少个对象):
1 2 3 |
Author.objects.bulk_create([Author(name='dkey2', email='dkey@ywnds.com'), Author(name='dkey3', email='dkey@ywnds.com'), ]) |
尽管如此,还是有一些注意事项:模型的save()方法不会被调用,它不适用于多表继承方案中的子模型,它不适用于多对多的关系。
- count()
返回一个整数,表示与之匹配的数据库中QuerySet的对象数。count()方法从不引发异常。
1 2 3 4 5 |
# Returns the total number of entries in the database. Entry.objects.count() # Returns the number of entries whose headline contains 'Lennon' Entry.objects.filter(headline__contains='Lennon').count() |
- latest()
根据给定的字段返回表中的最新对象。
该示例Entry根据该 pub_date 字段返回表中的最新内容:
1 |
Entry.objects.latest('pub_date') |
- first()
返回查询集匹配的第一个对象,或者为None表示没有匹配的对象。如果QuerySet没有定义的顺序,那么查询集由主键自动排序。这可能会影响聚合结果。
1 |
p = Article.objects.order_by('title', 'pub_date').first() |
请注意,这是first()的一种方便方法,下面的代码示例等同于上面的示例:
1 2 3 4 |
try: p = Article.objects.order_by('title', 'pub_date')[0] except IndexError: p = None |
- last()
跟first()类似,但返回查询集中的最后一个对象。
- in_bulk()
in_bulk(id_list = None,field_name =’pk’)
获取字段值(id_list)的列表以及field_name这些值,并返回一个字典,将每个值映射到具有给定字段值的对象的实例。如果id_list未提供,则返回查询集中的所有对象。field_name必须是唯一的字段,并且它默认为主键。
1 2 3 4 5 6 7 8 9 10 |
>>> Blog.objects.in_bulk([1]) {1: <Blog: Beatles Blog>} >>> Blog.objects.in_bulk([1, 2]) {1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>} >>> Blog.objects.in_bulk([]) {} >>> Blog.objects.in_bulk() {1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>, 3: <Blog: Django Weblog>} >>> Blog.objects.in_bulk(['beatles_blog'], field_name='slug') {'beatles_blog': <Blog: Beatles Blog>} |
如果你传递in_bulk()一个空列表,你会得到一个空字典。
在Django 2.0中更改:该field_name参数已添加。
- update()
对指定字段执行SQL更新查询,并返回匹配的行数(如果某些行已具有新值,则该行数可能不等于更新的行数)。
例如,要关闭2010年发布的所有博客条目的评论,你可以这样做:
1 |
>>> Entry.objects.filter(pub_date__year=2010).update(comments_on=False) |
你可以更新多个字段 – 对多少字段没有限制。例如,在这里我们更新comments_on和headline字段:
1 |
>>> Entry.objects.filter(pub_date__year=2010).update(comments_on=False, headline='This is old') |
该update()方法立即应用。
- delete()
对所有行执行SQL删除操作,QuerySet并返回删除的对象数和每个对象类型具有删除次数的字典。
delete()即时应用。例如,要删除特定博客中的所有条目:
1 2 |
>>> b = Blog.objects.get(pk=1) >>> Entry.objects.filter(blog=b).delete() |
- aggregate()
返回通过计算得到的聚合值(平均值,总和等)的字典。
1 2 3 4 5 6 7 |
>>> from django.db.models import Avg, Min, Max, Count, Sum >>> Entry.objects.aggregate(Sum('n_comments')) {'n_comments__sum': 1} # 别名 >>> Entry.objects.aggregate(comment_avg=Avg('n_comments')) {'comment_avg': 1.0} |
四、字段查找
字段查找是如何指定SQL WHERE子句的内容。它们被指定为QuerySet的filter(), exclude()或get()方法的关键字参数。使用__(双下划线)来查询。
exact:精确匹配,也是默认方式,iexact表示忽略大小写。
为了方便,当没有提供查找类型(如 Author.objects.get(id=1))时,查找类型被假定为exact。
1 2 3 4 5 6 |
>>> Author.objects.get(id__exact=1) <Author: dkey> >>> Author.objects.filter(id__exact=1) <QuerySet [<Author: dkey>]> >>> Author.objects.exclude(id__exact=1) <QuerySet [<Author: jerry>, <Author: jerry>]> |
还有一些常用类型,如下:
gt:大于。
gte:大于等于。
it:小于。
ite:小于等于。
isnull:为null。
contains:包含某某,icontains表示忽略大小写。
startswith:以某某开始,istartswith表示忽略大小写。
endswith:以某某结束,iendswith表示忽略大小写。
regex:正则匹配,区分大小写。
iregex:正则匹配,忽略大小写。
in:在给定的可迭代对象中,通常是一个列表,元组或查询集。
range:在某某范围之内。
date、year、month、week、day、second、hour、minute:时间日期类型。
官网:https://docs.djangoproject.com/en/2.0/ref/models/querysets