django相关知识
Djangoselect_related 和 prefetch_related 函数
对 QuerySet 查询的优化
在数据库有外键的时候,使用 select_related() 和 prefetch_related() 能够很好的减小数据库请求的次数,从而提升性能。本文经过一个简单的例子详解这两个函数的做用。虽然QuerySet的文档中已经详细说明了,但本文试图从QuerySet触发的SQL语句来分析工做方式,从而进一步了解Django具体的运做方式。
1. 实例的背景说明
假定一个我的信息系统,须要记录系统中各我的的故乡、居住地、以及到过的城市。数据库设计以下:python
Models.py 内容以下:mysql
from django.db import models
class Province(models.Model):
name = models.CharField(max_length=10)
def __unicode__(self):
return self.name
class City(models.Model):
name = models.CharField(max_length=5)
province = models.ForeignKey(Province)
def __unicode__(self):
return self.name
class Person(models.Model):
firstname= models.CharField(max_length=10)
lastname = models.CharField(max_length=10)
visitation = models.ManyToManyField(City, related_name = "visitor")
hometown = models.ForeignKey(City, related_name = "birth")
living = models.ForeignKey(City, related_name = "citizen")
def __unicode__(self):
return self.firstname + self.lastname注1:建立的app名为“QSOptimize”sql
注2:为了简化起见,qsoptimize_province 表中只有2条数据:湖北省和广东省,qsoptimize_city表中只有三条数据:武汉市、十堰市和广州市数据库
2. select_related()
对于一对一字段(OneToOneField)和外键字段(ForeignKey),可使用select_related 来对QuerySet进行优化django
做用和方法
在对QuerySet使用select_related()函数后,Django会获取相应外键对应的对象,从而在以后须要的时候没必要再查询数据库了。以上例说明,若是咱们须要打印数据库中的全部市及其所属省份,最直接的作法是:缓存
citys = City.objects.all()
for c in citys:
print c.province这样会致使线性的SQL查询,若是对象数量n太多,每一个对象中有k个外键字段的话,就会致使n*k+1次SQL查询。在本例中,由于有3个city对象就致使了4次SQL查询:app
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name`
FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`id` = 1 ;
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name`
FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`id` = 2 ;
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name`
FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`id` = 1 ;注:这里的SQL语句是直接从Django的logger:‘django.db.backends’输出出来的数据库设计
若是咱们使用select_related()函数:函数
citys = City.objects.select_related().all()
for c in citys:
print c.province就只有一次SQL查询,显然大大减小了SQL查询的次数:性能
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`,
`QSOptimize_city`.`province_id`, `QSOptimize_province`.`id`, `QSOptimize_province`.`name`
FROM`QSOptimize_city`
INNER JOIN `QSOptimize_province` ON (`QSOptimize_city`.`province_id` = `QSOptimize_province`.`id`) ;这里咱们能够看到,Django使用了INNER JOIN来得到省份的信息。顺便一提这条SQL查询获得的结果以下:
+----+-----------+-------------+----+-----------+
| id | name | province_id | id | name |
+----+-----------+-------------+----+-----------+
|1 | 武汉市 | 1 |1 | 湖北省 |
|2 | 广州市 | 2 |2 | 广东省 |
|3 | 十堰市 | 1 |1 | 湖北省 |
+----+-----------+-------------+----+-----------+
3 rows in set (0.00 sec)
使用方法
函数支持以下三种用法:
*fields 参数
select_related() 接受可变长参数,每一个参数是须要获取的外键(父表的内容)的字段名,以及外键的外键的字段名、外键的外键的外键…。若要选择外键的外键须要使用两个下划线“__”来链接。
例如咱们要得到张三的现居省份,能够用以下方式:
zhangs = Person.objects.select_related('living__province').get(firstname=u"张",lastname=u"三")
zhangs.living.province
触发的SQL查询以下:
SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`,
`QSOptimize_person`.`lastname`, `QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`,
`QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`, `QSOptimize_province`.`id`,
`QSOptimize_province`.`name`
FROM `QSOptimize_person`
INNER JOIN `QSOptimize_city` ON (`QSOptimize_person`.`living_id` = `QSOptimize_city`.`id`)
INNER JOIN `QSOptimize_province` ON (`QSOptimize_city`.`province_id` = `QSOptimize_province`.`id`)
WHERE (`QSOptimize_person`.`lastname` = '三'AND `QSOptimize_person`.`firstname` = '张' );能够看到,Django使用了2次 INNER JOIN 来完成请求,得到了city表和province表的内容并添加到结果表的相应列,这样在调用 zhangs.living的时候也没必要再次进行SQL查询。
+----+-----------+----------+-------------+-----------+----+-----------+-------------+----+-----------+
| id | firstname | lastname | hometown_id | living_id | id | name | province_id | id | name |
+----+-----------+----------+-------------+-----------+----+-----------+-------------+----+-----------+
|1 | 张 | 三 | 3 | 1 |1 | 武汉市 | 1 |1 | 湖北省 |
+----+-----------+----------+-------------+-----------+----+-----------+-------------+----+-----------+
1 row in set (0.00 sec)
然而,未指定的外键则不会被添加到结果中。这时候若是须要获取张三的故乡就会进行SQL查询了:
zhangs.hometown.province
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
WHERE `QSOptimize_city`.`id` = 3 ;
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name`
FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`id` = 1同时,若是不指定外键,就会进行两次查询。若是深度更深,查询的次数更多。
值得一提的是,从Django 1.7开始,select_related()函数的做用方式改变了。在本例中,若是要同时得到张三的故乡和现居地的省份,在1.7之前你只能这样作:
zhangs = Person.objects.select_related('hometown__province','living__province').get(firstname=u"张",lastname=u"三")
zhangs.hometown.province
zhangs.living.province
可是1.7及以上版本,你能够像和queryset的其余函数同样进行链式操做:
zhangs = Person.objects.select_related('hometown__province').select_related('living__province').get(firstname=u"张",lastname=u"三")
zhangs.hometown.province
zhangs.living.province
若是你在1.7如下版本这样作了,你只会得到最后一个操做的结果,在本例中就是只有现居地而没有故乡。在你打印故乡省份的时候就会形成两次SQL查询。
depth 参数
select_related() 接受depth参数,depth参数能够肯定select_related的深度。Django会递归遍历指定深度内的全部的OneToOneField和ForeignKey。以本例说明:
zhangs = Person.objects.select_related(depth = d)
d=1至关于 select_related(‘hometown’,’living’)
d=2至关于 select_related(‘hometown__province’,’living__province’)
无参数
select_related() 也能够不加参数,这样表示要求Django尽量深的select_related。例如:zhangs = Person.objects.select_related().get(firstname=u”张”,lastname=u”三”)。但要注意两点:
Django自己内置一个上限,对于特别复杂的表关系,Django可能在你不知道的某处跳出递归,从而与你想的作法不同。具体限制是怎么工做的我表示不清楚。
Django并不知道你实际要用的字段有哪些,因此会把全部的字段都抓进来,从而会形成没必要要的浪费而影响性能。
小结
select_related主要针一对一和多对一关系进行优化。
select_related使用SQL的JOIN语句进行优化,经过减小SQL查询的次数来进行优化、提升性能。
能够经过可变长参数指定须要select_related的字段名。也能够经过使用双下划线“__”链接字段名来实现指定的递归查询。没有指定的字段不会缓存,没有指定的深度不会缓存,若是要访问的话Django会再次进行SQL查询。
也能够经过depth参数指定递归的深度,Django会自动缓存指定深度内全部的字段。若是要访问指定深度外的字段,Django会再次进行SQL查询。
也接受无参数的调用,Django会尽量深的递归查询全部的字段。但注意有Django递归的限制和性能的浪费。
Django >= 1.7,链式调用的select_related至关于使用可变长参数。Django < 1.7,链式调用会致使前边的select_related失效,只保留最后一个。
3. prefetch_related()
对于多对多字段(ManyToManyField)和一对多字段,可使用prefetch_related()来进行优化。或许你会说,没有一个叫OneToManyField的东西啊。实际上 ,ForeignKey就是一个多对一的字段,而被ForeignKey关联的字段就是一对多字段了。
做用和方法
prefetch_related()和select_related()的设计目的很类似,都是为了减小SQL查询的数量,可是实现的方式不同。后者是经过JOIN语句,在SQL查询内解决问题。可是对于多对多关系,使用SQL语句解决就显得有些不太明智,由于JOIN获得的表将会很长,会致使SQL语句运行时间的增长和内存占用的增长。如有n个对象,每一个对象的多对多字段对应Mi条,就会生成Σ(n)Mi 行的结果表。prefetch_related()的解决方法是,分别查询每一个表,而后用Python处理他们之间的关系。继续以上边的例子进行说明,若是咱们要得到张三全部去过的城市,使用prefetch_related()应该是这么作:
zhangs = Person.objects.prefetch_related('visitation').get(firstname=u"张",lastname=u"三")
for city in zhangs.visitation.all() :
print city上述代码触发的SQL查询以下:
SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`,
`QSOptimize_person`.`lastname`, `QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`
FROM `QSOptimize_person`
WHERE (`QSOptimize_person`.`lastname` = '三'AND `QSOptimize_person`.`firstname` = '张');
SELECT (`QSOptimize_person_visitation`.`person_id`) AS `_prefetch_related_val`, `QSOptimize_city`.`id`,
`QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE `QSOptimize_person_visitation`.`person_id` IN (1);第一条SQL查询仅仅是获取张三的Person对象,第二条比较关键,它选取关系表QSOptimize_person_visitation中person_id为张三的行,而后和city表内联(INNER JOIN 也叫等值链接)获得结果表。
+----+-----------+----------+-------------+-----------+
| id | firstname | lastname | hometown_id | living_id |
+----+-----------+----------+-------------+-----------+
|1 | 张 | 三 | 3 | 1 |
+----+-----------+----------+-------------+-----------+
1 row in set (0.00 sec)
+-----------------------+----+-----------+-------------+
| _prefetch_related_val | id | name | province_id |
+-----------------------+----+-----------+-------------+
| 1 |1 | 武汉市 | 1 |
| 1 |2 | 广州市 | 2 |
| 1 |3 | 十堰市 | 1 |
+-----------------------+----+-----------+-------------+
3 rows in set (0.00 sec)
显然张三武汉、广州、十堰都去过。
又或者,咱们要得到湖北的全部城市名,能够这样:
hb = Province.objects.prefetch_related('city_set').get(name__iexact=u"湖北省")
for city in hb.city_set.all():
... city.name
...
触发的SQL查询:
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name` FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`name` LIKE '湖北省' ;
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`WHERE `QSOptimize_city`.`province_id` IN (1);获得的表:
+----+-----------+
| id | name |
+----+-----------+
|1 | 湖北省 |
+----+-----------+
1 row in set (0.00 sec)
+----+-----------+-------------+
| id | name | province_id |
+----+-----------+-------------+
|1 | 武汉市 | 1 |
|3 | 十堰市 | 1 |
+----+-----------+-------------+
2 rows in set (0.00 sec)
咱们能够看见,prefetch使用的是 IN 语句实现的。这样,在QuerySet中的对象数量过多的时候,根据数据库特性的不一样有可能形成性能问题。
使用方法
*lookups 参数
prefetch_related()在Django < 1.7 只有这一种用法。和select_related()同样,prefetch_related()也支持深度查询,例如要得到全部姓张的人去过的省:
zhangs = Person.objects.prefetch_related('visitation__province').filter(firstname__iexact=u'张')
for i in zhangs:
for city in i.visitation.all():
print city.province触发的SQL:
SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`,
`QSOptimize_person`.`lastname`, `QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`
FROM `QSOptimize_person`
WHERE `QSOptimize_person`.`firstname` LIKE '张' ;
SELECT (`QSOptimize_person_visitation`.`person_id`) AS `_prefetch_related_val`, `QSOptimize_city`.`id`,
`QSOptimize_city`.`name`, `QSOptimize_city`.`province_id` FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE `QSOptimize_person_visitation`.`person_id` IN (1, 4);
SELECT `QSOptimize_province`.`id`, `QSOptimize_province`.`name` FROM `QSOptimize_province`
WHERE `QSOptimize_province`.`id` IN (1, 2);得到的结果:
+----+-----------+----------+-------------+-----------+
| id | firstname | lastname | hometown_id | living_id |
+----+-----------+----------+-------------+-----------+
|1 | 张 | 三 | 3 | 1 |
|4 | 张 | 六 | 2 | 2 |
+----+-----------+----------+-------------+-----------+
2 rows in set (0.00 sec)
+-----------------------+----+-----------+-------------+
| _prefetch_related_val | id | name | province_id |
+-----------------------+----+-----------+-------------+
| 1 |1 | 武汉市 | 1 |
| 1 |2 | 广州市 | 2 |
| 4 |2 | 广州市 | 2 |
| 1 |3 | 十堰市 | 1 |
+-----------------------+----+-----------+-------------+
4 rows in set (0.00 sec)
+----+-----------+
| id | name |
+----+-----------+
|1 | 湖北省 |
|2 | 广东省 |
+----+-----------+
2 rows in set (0.00 sec)
值得一提的是,链式prefetch_related会将这些查询添加起来,就像1.7中的select_related那样。
要注意的是,在使用QuerySet的时候,一旦在链式操做中改变了数据库请求,以前用prefetch_related缓存的数据将会被忽略掉。这会致使Django从新请求数据库来得到相应的数据,从而形成性能问题。这里提到的改变数据库请求指各类filter()、exclude()等等最终会改变SQL代码的操做。而all()并不会改变最终的数据库请求,所以是不会致使从新请求数据库的。
举个例子,要获取全部人访问过的城市中带有“市”字的城市,这样作会致使大量的SQL查询:
plist = Person.objects.prefetch_related('visitation')
由于数据库中有4人,致使了2+4次SQL查询:
SELECT `QSOptimize_person`.`id`, `QSOptimize_person`.`firstname`, `QSOptimize_person`.`lastname`,
`QSOptimize_person`.`hometown_id`, `QSOptimize_person`.`living_id`
FROM `QSOptimize_person`;
SELECT (`QSOptimize_person_visitation`.`person_id`) AS `_prefetch_related_val`, `QSOptimize_city`.`id`,
`QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE `QSOptimize_person_visitation`.`person_id` IN (1, 2, 3, 4);
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE(`QSOptimize_person_visitation`.`person_id` = 1AND `QSOptimize_city`.`name` LIKE '%市%' );
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE (`QSOptimize_person_visitation`.`person_id` = 2AND `QSOptimize_city`.`name` LIKE '%市%' );
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE (`QSOptimize_person_visitation`.`person_id` = 3AND `QSOptimize_city`.`name` LIKE '%市%' );
SELECT `QSOptimize_city`.`id`, `QSOptimize_city`.`name`, `QSOptimize_city`.`province_id`
FROM `QSOptimize_city`
INNER JOIN `QSOptimize_person_visitation` ON (`QSOptimize_city`.`id` = `QSOptimize_person_visitation`.`city_id`)
WHERE (`QSOptimize_person_visitation`.`person_id` = 4AND `QSOptimize_city`.`name` LIKE '%市%' );详细分析一下这些请求事件。
众所周知,QuerySet是lazy的,要用的时候才会去访问数据库。运行到第二行Python代码时,for循环将plist看作iterator,这会触发数据库查询。最初的两次SQL查询就是prefetch_related致使的。
虽然已经查询结果中包含全部所需的city的信息,但由于在循环体中对Person.visitation进行了filter操做,这显然改变了数据库请求。所以这些操做会忽略掉以前缓存到的数据,从新进行SQL查询。
可是若是有这样的需求了应该怎么办呢?在Django >= 1.7,能够经过下一节的Prefetch对象来实现,若是你的环境是Django < 1.7,能够在Python中完成这部分操做。
plist = Person.objects.prefetch_related('visitation')
[ for p in plist]
Prefetch 对象
在Django >= 1.7,能够用Prefetch对象来控制prefetch_related函数的行为。
注:因为我没有安装1.7版本的Django环境,本节内容是参考Django文档写的,没有进行实际的测试。
Prefetch对象的特征:
一个Prefetch对象只能指定一项prefetch操做。
Prefetch对象对字段指定的方式和prefetch_related中的参数相同,都是经过双下划线链接的字段名完成的。
能够经过 queryset 参数手动指定prefetch使用的QuerySet。
能够经过 to_attr 参数指定prefetch到的属性名。
Prefetch对象和字符串形式指定的lookups参数能够混用。
继续上面的例子,获取全部人访问过的城市中带有“武”字和“州”的城市:
wus = City.objects.filter(name__icontains = u"武")
zhous = City.objects.filter(name__icontains = u"州")
plist = Person.objects.prefetch_related(
Prefetch('visitation', queryset = wus, to_attr = "wu_city"),
Prefetch('visitation', queryset = zhous, to_attr = "zhou_city"),)
注:这段代码没有在实际环境中测试过,如有不正确的地方请指正。
顺带一提,Prefetch对象和字符串参数能够混用。
None
能够经过传入一个None来清空以前的prefetch_related。就像这样:
prefetch_cleared_qset = qset.prefetch_related(None)
小结
prefetch_related主要针一对多和多对多关系进行优化。
prefetch_related经过分别获取各个表的内容,而后用Python处理他们之间的关系来进行优化。
能够经过可变长参数指定须要select_related的字段名。指定方式和特征与select_related是相同的。
在Django >= 1.7能够经过Prefetch对象来实现复杂查询,但低版本的Django好像只能本身实现。
做为prefetch_related的参数,Prefetch对象和字符串能够混用。
prefetch_related的链式调用会将对应的prefetch添加进去,而非替换,彷佛没有基于不一样版本上区别。
能够经过传入None来清空以前的prefetch_related。
[*]一些实例
选择哪一个函数
若是咱们想要得到全部家乡是湖北的人,最无脑的作法是先得到湖北省,再得到湖北的全部城市,最后得到故乡是这个城市的人。就像这样:
hb = Province.objects.get(name__iexact=u"湖北省")
people = []
for city in hb.city_set.all():
people.extend(city.birth.all())显然这不是一个明智的选择,由于这样作会致使1+(湖北省城市数)次SQL查询。反正是个反例,致使的查询和得到掉结果就不列出来了。
prefetch_related() 或许是一个好的解决方法,让咱们来看看。
hb = Province.objects.prefetch_related("city_set__birth").objects.get(name__iexact=u"湖北省")
people = []
for city in hb.city_set.all():
... people.extend(city.birth.all())
...
由于是一个深度为2的prefetch,因此会致使3次SQL查询:
SELECT QSOptimize_province.id, QSOptimize_province.name
FROM QSOptimize_province
WHERE QSOptimize_province.name LIKE '湖北省' ;
SELECT QSOptimize_city.id, QSOptimize_city.name, QSOptimize_city.province_id
FROM QSOptimize_cityWHERE QSOptimize_city.province_id IN (1);
SELECT QSOptimize_person.id, QSOptimize_person.firstname, QSOptimize_person.lastname,
QSOptimize_person.hometown_id, QSOptimize_person.living_id
FROM QSOptimize_personWHERE QSOptimize_person.hometown_id IN (1, 3);
嗯…看上去不错,可是3次查询么?倒过来查询可能会更简单?
people = list(Person.objects.select_related("hometown__province").filter(hometown__province__name__iexact=u"湖北省"))
SELECT QSOptimize_person.id, QSOptimize_person.firstname, QSOptimize_person.lastname,
QSOptimize_person.hometown_id, QSOptimize_person.living_id, QSOptimize_city.id,
QSOptimize_city.name, QSOptimize_city.province_id, QSOptimize_province.id, QSOptimize_province.name
FROM QSOptimize_person
INNER JOIN QSOptimize_city ON (QSOptimize_person.hometown_id = QSOptimize_city.id)
INNER JOIN QSOptimize_province ON (QSOptimize_city.province_id = QSOptimize_province.id)
WHERE QSOptimize_province.name LIKE '湖北省';
+----+-----------+----------+-------------+-----------+----+--------+-------------+----+--------+
| id | firstname | lastname | hometown_id | living_id | id | name | province_id | id | name |
+----+-----------+----------+-------------+-----------+----+--------+-------------+----+--------+
|1 | 张 | 三 | 3 | 1 |3 | 十堰市 | 1 |1 | 湖北省 |
|2 | 李 | 四 | 1 | 3 |1 | 武汉市 | 1 |1 | 湖北省 |
|3 | 王 | 麻子 | 3 | 2 |3 | 十堰市 | 1 |1 | 湖北省 |
+----+-----------+----------+-------------+-----------+----+--------+-------------+----+--------+
3 rows in set (0.00 sec)
彻底没问题。不只SQL查询的数量减小了,python程序上也精简了。
select_related()的效率要高于prefetch_related()。所以,最好在能用select_related()的地方尽可能使用它,也就是说,对于ForeignKey字段,避免使用prefetch_related()。
联用
对于同一个QuerySet,你能够同时使用这两个函数。
在咱们一直使用的例子上加一个model:Order (订单)
class Order(models.Model):
customer = models.ForeignKey(Person)
orderinfo= models.CharField(max_length=50)
time = models.DateTimeField(auto_now_add = True)
def unicode(self):
return self.orderinfo
若是咱们拿到了一个订单的id 咱们要知道这个订单的客户去过的省份。由于有ManyToManyField显然必需要用prefetch_related()。若是只用prefetch_related()会怎样呢?
plist = Order.objects.prefetch_related('customer__visitation__province').get(id=1)
for city in plist.customer.visitation.all():
... print city.province.name
...
显然,关系到了4个表:Order、Person、City、Province,根据prefetch_related()的特性就得有4次SQL查询
SELECT QSOptimize_order.id, QSOptimize_order.customer_id, QSOptimize_order.orderinfo, QSOptimize_order.time
FROM QSOptimize_order
WHERE QSOptimize_order.id = 1 ;
SELECT QSOptimize_person.id, QSOptimize_person.firstname, QSOptimize_person.lastname, QSOptimize_person.hometown_id, QSOptimize_person.living_id
FROM QSOptimize_person
WHERE QSOptimize_person.id IN (1);
SELECT (QSOptimize_person_visitation.person_id) AS _prefetch_related_val, QSOptimize_city.id, QSOptimize_city.name, QSOptimize_city.province_id
FROM QSOptimize_city
INNER JOIN QSOptimize_person_visitation ON (QSOptimize_city.id = QSOptimize_person_visitation.city_id)
WHERE QSOptimize_person_visitation.person_id IN (1);
SELECT QSOptimize_province.id, QSOptimize_province.name
FROM QSOptimize_province WHERE QSOptimize_province.id IN (1, 2);
+----+-------------+---------------+---------------------+
| id | customer_id | orderinfo | time |
+----+-------------+---------------+---------------------+
|1 | 1 | Info of Order | 2014-08-10 17:05:48 |
+----+-------------+---------------+---------------------+
1 row in set (0.00 sec)
+----+-----------+----------+-------------+-----------+
| id | firstname | lastname | hometown_id | living_id |
+----+-----------+----------+-------------+-----------+
|1 | 张 | 三 | 3 | 1 |
+----+-----------+----------+-------------+-----------+
1 row in set (0.00 sec)
+-----------------------+----+--------+-------------+
| _prefetch_related_val | id | name | province_id |
+-----------------------+----+--------+-------------+
| 1 |1 | 武汉市 | 1 |
| 1 |2 | 广州市 | 2 |
| 1 |3 | 十堰市 | 1 |
+-----------------------+----+--------+-------------+
3 rows in set (0.00 sec)
+----+--------+
| id | name |
+----+--------+
|1 | 湖北省 |
|2 | 广东省 |
+----+--------+
2 rows in set (0.00 sec)
更好的办法是先调用一次select_related()再调用prefetch_related(),最后再select_related()后面的表
plist = Order.objects.select_related('customer').prefetch_related('customer__visitation__province').get(id=1)
for city in plist.customer.visitation.all():
... print city.province.name
...
这样只会有3次SQL查询,Django会先作select_related,以后prefetch_related的时候会利用以前缓存的数据,从而避免了1次额外的SQL查询:
SELECT QSOptimize_order.id, QSOptimize_order.customer_id, QSOptimize_order.orderinfo,
QSOptimize_order.time, QSOptimize_person.id, QSOptimize_person.firstname,
QSOptimize_person.lastname, QSOptimize_person.hometown_id, QSOptimize_person.living_id
FROM QSOptimize_order
INNER JOIN QSOptimize_person ON (QSOptimize_order.customer_id = QSOptimize_person.id)
WHERE QSOptimize_order.id = 1 ;
SELECT (QSOptimize_person_visitation.person_id) AS _prefetch_related_val, QSOptimize_city.id,
QSOptimize_city.name, QSOptimize_city.province_id
FROM QSOptimize_city
INNER JOIN QSOptimize_person_visitation ON (QSOptimize_city.id = QSOptimize_person_visitation.city_id)
WHERE QSOptimize_person_visitation.person_id IN (1);
SELECT QSOptimize_province.id, QSOptimize_province.name
FROM QSOptimize_province
WHERE QSOptimize_province.id IN (1, 2);
+----+-------------+---------------+---------------------+----+-----------+----------+-------------+-----------+
| id | customer_id | orderinfo | time | id | firstname | lastname | hometown_id | living_id |
+----+-------------+---------------+---------------------+----+-----------+----------+-------------+-----------+
|1 | 1 | Info of Order | 2014-08-10 17:05:48 |1 | 张 | 三 | 3 | 1 |
+----+-------------+---------------+---------------------+----+-----------+----------+-------------+-----------+
1 row in set (0.00 sec)
+-----------------------+----+--------+-------------+
| _prefetch_related_val | id | name | province_id |
+-----------------------+----+--------+-------------+
| 1 |1 | 武汉市 | 1 |
| 1 |2 | 广州市 | 2 |
| 1 |3 | 十堰市 | 1 |
+-----------------------+----+--------+-------------+
3 rows in set (0.00 sec)
+----+--------+
| id | name |
+----+--------+
|1 | 湖北省 |
|2 | 广东省 |
+----+--------+
2 rows in set (0.00 sec)
小结
由于select_related()老是在单次SQL查询中解决问题,而prefetch_related()会对每一个相关表进行SQL查询,所以select_related()的效率一般比后者高。
鉴于第一条,尽量的用select_related()解决问题。只有在select_related()不能解决问题的时候再去想prefetch_related()。
你能够在一个QuerySet中同时使用select_related()和prefetch_related(),从而减小SQL查询的次数。
只有prefetch_related()以前的select_related()是有效的,以后的将会被无视掉。
Django中的queryset
1、django中的queryset是一个查询集,支持链式调用的接口如下:
all接口,用于查询所有数据
filter接口,根据条件进行过滤
exclude接口,与filter一样,只是结果与filter相反
reverse接口,把queryset中的结果倒序排列
distinct接口,用来进行去重查询
none接口,返回空的查询集
2、Django的queryset是惰性的
例如:data = Data.objects.filter(name__contains="game"),data是一个名称包含game的查询集。但是如果只有这一句,那么Django的数据接口queryset并没有对数据库进行任何查询。无论你加多少过滤条件,Django都不会对数据库进行查询。只有当你需要对data做进一步运算时(比如打印出查询结果,判断是否存在,统计查询结果长度),Django才会真正执行对数据库的查询。
其实Django这样设计的本意是尽量减少对数据库的无效操作,比如查询了结果而不用,那么就是对资源的很大浪费,对吧。
3、Django的queryset自带缓存(Cache)
先看个例子如下:
for i in data:
print i.name
上面的例子中我们对查询集进行了遍历,所有匹配的记录会从数据库获取,也就是在这个时候才会去操作数据库。这些结果会载入内存并保存在queryset内置的cache中。这样如果你再次遍历或读取这个data时,Django就不需要重复查询了,这样也可以减少对数据库的查询。
再看如下例子:
例一
data = Data.objects.filter(name__contains='game')
for i in data:
print i.name
例二
for i in Data.objects.filter(name__contains='game'):
print i.name
以上两个例子中例一要优于例二,因为在使用for循环后,Django不仅执行了查询,还把查询到的data放在了缓存里。这个data是可以复用的,例二就不行了。后续如果还要使用data就不用再去查询数据库,而是直接从缓存里读取。
使用if判断也会执行,一般来说我们在进行遍历的时候都要加上一层判断,if也会导致queryset执行, 缓存data,所以我们在遍历data时不用担心Django会对数据库进行二次查询。
data = Data.objects.filter(name__contains='game')
if data:
for i in data:
print i.name
上面的示例中,在进行if判断就已经去查询数据库了,所以在我们for遍历的时候拿的是缓存里的数据。
有时我们只希望了解查询的结果是否存在,而不需要使用整个数据集,这时使用if,就会触发整个queryset的缓存就变成了一件坏事情。当然了,解决方法就是使用exists。
resp = Data.objects.filter(name__contains='game').exists()
resp是True或False,与if判断不同,exists只会检查查询结果是否存在,返回True或False,而不会缓存。当然了,使用哪种方法取决于我们逻辑哈。
有时候我们需要统计查询结果数量,len()与count()均能统计查询结果的数量。一般来说count更快,因为它是从数据库层面直接获取查询结果的数量,而不是返回整个数据集,而len会导致queryset的执行,需要将整个queryset载入内存后才能统计其长度。但事情也没有绝对,如果数据集queryset已经在缓存里了,使用len更快,因为它不需要跟数据库再次打交道。
number1
number1 = Data.objects.filter(name__contains='game').count()
number2
number2 = Data.objects.filter(name__contains='game').len()
number3
data = Data.objects.filter(name__contains='game')
number3 = data.len()
以上三个例子中,不考虑别的因素下,number1和number3都是比较好的,number2就尽量别考虑了。
有时候后端返回数据量较大,会大量占用内存(缓存)。我们可以使用values和value_list方法按需提取数据。比如,我们只要数据里的name,而不用其它的信息,诸如:性别,年龄之类的,那么我们就可以使用values和value_list方法。
values()
data = Data.objects.filter(name__contains='game').values('name')
print data
data:[{'name': 'gameboy'}, {'name': 'gameheny'}, {'name': 'game'}, ...]
print type(data)
values_list()
data = Data.objects.filter(name__contains='game').values_list('name')
print data
data:[('gameboy',), ('gameheny',), ('game',), ...]
print type(data)
data = Data.objects.filter(name__contains='game').values_list('name', flat=True)
print data
data:['gameboy', 'gameheny', 'game', ...]
print type(data)
以上示例可以知道,无论是values还是value_list,返回的数据都不是列表,而是查询集。
有时候需要对数据库中的某条已有数据或某些字段进行更新,更好的方式是用update,而不是save方法。
save()
data = Data.objects.get(id=1)
data.name = "gamebox"
data.save()
update()
data = Data.objects.filter(id=1).update(name="gamebox")
save()需要把整个Data对象的数据(姓名,年龄,性别…..)先提取出来,缓存到内存中,变更信息后再写入数据库。而update()直接对name做了更新,不需要把整个对象的数据载入内存,显然更高效。
尽管单个数据占用内存不多,但是万一用户非常多呢,那么占用的内存加起来也是很恐怖的。
update()还会返回已更新条目的数量,这点也非常有用。当然事情也没有绝对,save()对于单个模型的更新还是很有优势的。
如何从Django QuerySet中获取字段名称,即使它是一个空集?
Django和Pandas之间的一个很酷的绑定是能够直接从QuerySet构建DataFrame,使用:
queryset = models.A.objects.filter(...).annotate(...)
frame = pd.DataFrame(queryset.values())
只要QuerySet至少返回一条记录,它就能很好地工作。在QuerySet级别上操作很有意思,因为在那里我们可以使用所有注解和本机列。
但是这个方法将返回一个完全空的DataFrame(没有定义列),比如说:
queryset = models.A.objects.filter(id__lt=0).annotate(...)
frame = pd.DataFrame(queryset.values())
DataFrame完全为空:
Empty DataFrame
Columns: []
Index: []
而我们想要的是这样的东西:
Empty DataFrame
Columns: ["id", "key", "prop1", ...]
Index: []
其中保留了列名,以便使该帧能够与其他帧无缝合并。
pandas的方法是在创建DataFrame时使用columns开关强制列名。
queryset = models.A.objects.filter(...)
frame = pd.DataFrame(queryset.values(), columns=queryset.get_fields())
不幸的是,get_fields或类似的对象似乎没有实现,或者乍一看对QuerySet对象来说并不明显。
我已经知道我可以从QuerySet中获取exists()的列名,使用这个脏的:
frame = pd.DataFrame(queryset.values(), columns=queryset.dict.keys() )
但是,实际上它不会对空的QuerySet起作用。
我还知道我可以得到模型列如下:
frame = pd.DataFrame( queryset.values(), columns= + [...])
但是这样我就错过了QuerySet创建的所有注解列,或者需要手动编码,这是我们想要避免的。
我有一种感觉,不知何故,QuerySet可能知道它应该返回的所有列。至少它应该在查询执行之后知道它,因为空的SQL结果集肯定会包含列名和类型。
所以我的问题是如何从Django QuerySet中获取字段名称,即使它是一个空集?
如果构造有点奇怪或复杂,只要它还允许获取注解列名,这就不是问题。
可以这样尝试:
fields = +
frame = pd.DataFrame(queryset.values(*fields),columns=fields)
我在调试queryset对象时发现了这个解决方案。它有一个名为query的属性,指向这个类Query的示例。在Query类中,有一个名为annotations的属性。此属性包含所有注解信息。您可以使用它来获取所有带注解的字段。
Django之QuerySet详解
从数据库中查询出来的结果一般是一个集合,这个集合叫做 QuerySet。
一、QuerySet何时被提交
在内部,创建、过滤、切片和传递一个QuerySet不会真实操作数据库,在你对查询集提交之前,不会发生任何实际的数据库操作。可以使用下列方法对QuerySet提交查询操作:
迭代:QuerySet是可迭代的,在首次迭代查询集时执行实际的数据库查询。 例如, 下面的语句会将数据库中所有Entry的headline打印出来:
for e in Entry.objects.all():
print(e.headline)
切片:如果使用切片的”step“参数,Django 将执行数据库查询并返回一个列表。
Pickling/缓存
repr()
len():当你对QuerySet调用len()时, 将提交数据库操作。
list():对QuerySet调用list()将强制提交操作entry_list = list(Entry.objects.all())
bool()
测试布尔值,像这样:
if Entry.objects.filter(headline="Test"):
print("There is at least one Entry with the headline Test")
注:如果你需要知道是否存在至少一条记录(而不需要真实的对象),使用exists() 将更加高效。
二、QuerySet
class QuerySet(model=None, query=None, using=None)
QuerySet类具有两个公有属性用于内省:
ordered:如果QuerySet是排好序的则为True,否则为False。
db:如果现在执行,则返回使用的数据库。
三、返回新QuerySets的API
以下的方法都将返回一个新的QuerySets。重点是加粗的几个API,其它的使用场景很少。
方法名 解释
filter() 过滤查询对象。 exclude() 排除满足条件的对象
annotate() 使用聚合函数 order_by() 对查询集进行排序
reverse() 反向排序 distinct() 对查询集去重
values() 返回包含对象具体值的字典的QuerySet
values_list() 与values()类似,只是返回的是元组而不是字典。
dates() 根据日期获取查询集 datetimes() 根据时间获取查询集
none() 创建空的查询集 all() 获取所有的对象
union() 并集 intersection() 交集
difference() 差集 select_related() 附带查询关联对象
prefetch_related() 预先查询 extra() 附加SQL查询
defer() 不加载指定字段 only() 只加载指定的字段
using() 选择数据库 select_for_update() 锁住选择的对象,直到事务结束。
raw() 接收一个原始的SQL查询
[*]filter() filter(kwargs)
返回满足查询参数的对象集合。查找的参数(kwargs)应该满足下文字段查找中的格式。多个参数之间是和AND的关系。
[*]exclude()exclude(kwargs)返回一个新的QuerySet,它包含不满足给定的查找参数的对象。
查找的参数(kwargs)应该满足下文字段查找中的格式。多个参数通过AND连接,然后所有的内容放入NOT() 中。
下面的示例排除所有pub_date晚于2005-1-3且headline为“Hello” 的记录:
Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline='Hello')
下面的示例排除所有pub_date晚于2005-1-3或者headline 为“Hello” 的记录:
Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3)).exclude(headline='Hello')
[*]annotate() annotate(args, *kwargs)使用提供的聚合表达式查询对象。
表达式可以是简单的值、对模型(或任何关联模型)上的字段的引用或者聚合表达式(平均值、总和等)。
annotate()的每个参数都是一个annotation,它将添加到返回的QuerySet每个对象中。
关键字参数指定的Annotation将使用关键字作为Annotation 的别名。 匿名参数的别名将基于聚合函数的名称和模型的字段生成。 只有引用单个字段的聚合表达式才可以使用匿名参数。 其它所有形式都必须用关键字参数。
例如,如果正在操作一个Blog列表,你可能想知道每个Blog有多少Entry:
from django.db.models import Count
q = Blog.objects.annotate(Count('entry'))
The name of the first blog
q.name
'Blogasaurus'
The number of entries on the first blog
q.entry__count
42
Blog模型本身没有定义entry__count属性,但是通过使用一个关键字参数来指定聚合函数,可以控制Annotation的名称:
q = Blog.objects.annotate(number_of_entries=Count('entry'))
The number of entries on the first blog, using the name provided
q.number_of_entries
42
[*]order_by()
order_by(*fields)
默认情况下,根据模型的Meta类中的ordering属性对QuerySet中的对象进行排序
Entry.objects.filter(pub_date__year=2005).order_by('-pub_date', 'headline')
上面的结果将按照pub_date降序排序,然后再按照headline升序排序。"-pub_date"前面的负号表示降序顺序。 升序是默认的。 要随机排序,使用"?",如下所示:
Entry.objects.order_by('?')
注:order_by('?')可能耗费资源且很慢,这取决于使用的数据库。
若要按照另外一个模型中的字段排序,可以使用查询关联模型的语法。即通过字段的名称后面跟两个下划线(__),再加上新模型中的字段的名称,直到希望连接的模型。 像这样:
Entry.objects.order_by('blog__name', 'headline')
如果排序的字段与另外一个模型关联,Django将使用关联的模型的默认排序,或者如果没有指定Meta.ordering将通过关联的模型的主键排序。 例如,因为Blog模型没有指定默认的排序:
Entry.objects.order_by('blog')
与以下相同:
Entry.objects.order_by('blog__id')
如果Blog设置了ordering = ['name'],那么第一个QuerySet将等同于:
Entry.objects.order_by('blog__name')
还可以通过调用表达式的desc()或者asc()方法:
Entry.objects.order_by(Coalesce('summary', 'headline').desc())
考虑下面的情况,指定一个多值字段来排序(例如,一个ManyToManyField 字段或者ForeignKey 字段的反向关联):
class Event(Model):
parent = models.ForeignKey(
'self',on_delete=models.CASCADE,related_name='children',
)
date = models.DateField()
Event.objects.order_by('children__date')
在这里,每个Event可能有多个排序数据;具有多个children的每个Event将被多次返回到order_by()创建的新的QuerySet中。 换句话说,用order_by()方法对QuerySet对象进行操作会返回一个扩大版的新QuerySet对象。因此,使用多值字段对结果进行排序时要格外小心。
没有方法指定排序是否考虑大小写。 对于大小写的敏感性,Django将根据数据库中的排序方式排序结果。
可以通过Lower将一个字段转换为小写来排序,它将达到大小写一致的排序:
Entry.objects.order_by(Lower('headline').desc())
可以通过检查QuerySet.ordered属性来知道查询是否是排序的。
每个order_by()都将清除前面的任何排序。 例如下面的查询将按照pub_date排序,而不是headline:
Entry.objects.order_by('headline').order_by('pub_date')
5. reverse()
反向排序QuerySet中返回的元素。 第二次调用reverse()将恢复到原有的排序。
如要获取QuerySet中最后五个元素,可以这样做:
my_queryset.reverse()[:5]
这与Python直接使用负索引有点不一样。 Django不支持负索引,只能曲线救国。
6. distinct() distinct(*fields)
去除查询结果中重复的行。
默认情况下,QuerySet不会去除重复的行。当查询跨越多张表的数据时,QuerySet可能得到重复的结果,这时候可以使用distinct()进行去重。
7. values() values(fields, *expressions)
返回一个包含数据的字典的queryset,而不是模型实例。
每个字典表示一个对象,键对应于模型对象的属性名称。
下面的例子将values() 与普通的模型对象进行比较:
列表中包含的是Blog对象
Blog.objects.filter(name__startswith='Beatles')
列表中包含的是数据字典
Blog.objects.filter(name__startswith='Beatles').values()
该方法接收可选的位置参数*fields,它指定values()应该限制哪些字段。如果指定字段,每个字典将只包含指定的字段的键/值。如果没有指定字段,每个字典将包含数据库表中所有字段的键和值。
例如:
Blog.objects.values()
Blog.objects.values('id', 'name')
values()方法还有关键字参数**expressions,这些参数将传递给annotate():
from django.db.models.functions import Lower
Blog.objects.values(lower_name=Lower('name'))
在values()子句中的聚合应用于相同values()子句中的其他参数之前。 如果需要按另一个值分组,请将其添加到较早的values()子句中。 像这样:
from django.db.models import Count
Blog.objects.values('author', entries=Count('entry'))
Blog.objects.values('author').annotate(entries=Count('entry'))
注意:如果你有一个字段foo是一个ForeignKey,默认的foo_id参数返回的字典中将有一个叫做foo 的键,因为这是保存实际值的那个隐藏的模型属性的名称。 当调用foo_id并传递字段的名称,传递foo 或values()都可以,得到的结果是相同的。像这样:
Entry.objects.values()
Entry.objects.values('blog')
Entry.objects.values('blog_id')
当values()与distinct()一起使用时,注意排序可能影响最终的结果。
如果values()子句位于extra()调用之后,extra()中的select参数定义的字段必须显式包含在values()调用中。 values( 调用后面的extra( 调用将忽略选择的额外的字段。
在values()之后调用only()和defer()不太合理,所以将引发一个NotImplementedError。
可以通过ManyToManyField、ForeignKey 和 OneToOneFiel 属性反向引用关联的模型的字段:
Blog.objects.values('name', 'entry__headline')
[*]values_list() values_list(*fields, flat=False)
与values()类似,只是在迭代时返回的是元组而不是字典。每个元组包含传递给values_list()调用的相应字段或表达式的值,因此第一个项目是第一个字段等。 像这样:
Entry.objects.values_list('id', 'headline')
from django.db.models.functions import Lower
Entry.objects.values_list('id', Lower('headline'))
如果只传递一个字段,还可以传递flat参数。 如果为True,它表示返回的结果为单个值而不是元组。 如下所示:
Entry.objects.values_list('id').order_by('id')
Entry.objects.values_list('id', flat=True).order_by('id')
如果有多个字段,传递flat将发生错误。
如果不传递任何值给values_list(),它将返回模型中的所有字段,以在模型中定义的顺序。
常见的情况是获取某个模型实例的特定字段值。可以使用values_list(),然后调用get():
Entry.objects.values_list('headline', flat=True).get(pk=1)
'First entry'
values()和values_list()都用于特定情况下的优化:检索数据子集,而无需创建模型实例。
注意通过ManyToManyField进行查询时的行为:
Author.objects.values_list('name', 'entry__headline')
类似地,当查询反向外键时,对于没有任何作者的条目,返回None。
Entry.objects.values_list('authors')
[*]dates() dates(field, kind, order='ASC')
返回一个QuerySet,表示QuerySet内容中特定类型的所有可用日期的datetime.date对象列表。
field参数是模型的DateField的名称。 kind参数应为"year","month"或"day"。 结果列表中的每个datetime.date对象被截取为给定的类型。
"year" 返回对应该field的所有不同年份值的列表。
"month"返回字段的所有不同年/月值的列表。
"day"返回字段的所有不同年/月/日值的列表。
order参数默认为'ASC',或者'DESC'。 它指定如何排序结果。
例子:
Entry.objects.dates('pub_date', 'year')
Entry.objects.dates('pub_date', 'month')
Entry.objects.dates('pub_date', 'day')
Entry.objects.dates('pub_date', 'day', order='DESC')
Entry.objects.filter(headline__contains='Lennon').dates('pub_date', 'day')
[*]datetimes() datetimes(field_name, kind, order='ASC', tzinfo=None)
返回QuerySet,为datetime.datetime对象的列表,表示QuerySet内容中特定种类的所有可用日期。
field_name应为模型的DateTimeField的名称。
kind参数应为"hour","minute","month","year","second"或"day"。
结果列表中的每个datetime.datetime对象被截取到给定的类型。
order参数默认为'ASC',或者'DESC'。 它指定如何排序结果。
tzinfo参数定义在截取之前将数据时间转换到的时区。
[*]none() 调用none()将创建一个不返回任何对象的查询集,并且在访问结果时不会执行任何查询。
例子:
Entry.objects.none()
from django.db.models.query import EmptyQuerySet
isinstance(Entry.objects.none(), EmptyQuerySet)
True
[*]all() 返回当前QuerySet(或QuerySet子类)的副本。通常用于获取全部QuerySet对象。
[*]union() union(*other_qs, all=False)集合中并集
使用SQL的UNION运算符组合两个或更多个QuerySet的结果。例如:
qs1.union(qs2, qs3)
默认情况下,UNION操作符仅选择不同的值。 要允许重复值,请使用all=True参数。
[*]intersection() intersection(*other_qs) 集合中交集
使用SQL的INTERSECT运算符返回两个或更多个QuerySet的共有元素。例如:
qs1.intersection(qs2, qs3)
[*]difference() difference(*other_qs)集合中差集
使用SQL的EXCEPT运算符只保留QuerySet中的元素,但不保留其他QuerySet中的元素。例如:
qs1.difference(qs2, qs3)
[*]select_related() select_related(*fields)
沿着外键关系查询关联的对象的数据。这会生成一个复杂的查询并引起性能的损耗,但是在以后使用外键关系时将不需要再次数据库查询。
下面的例子解释了普通查询和select_related()查询的区别。 下面是一个标准的查询:
访问数据库。
e = Entry.objects.get(id=5)
再次访问数据库以得到关联的Blog对象。
b = e.blog
下面是一个select_related查询:
访问数据库。
e = Entry.objects.select_related('blog').get(id=5)
不会访问数据库,因为e.blog已经在前面的查询中获得了。
b = e.blog
select_related()可用于objects任何的查询集:
from django.utils import timezone
Find all the blogs with entries scheduled to be published in the future.
blogs = set()
for e in Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog'):
# 没有select_related(),下面的语句将为每次循环迭代生成一个数据库查询,以获得每个entry关联的blog。
blogs.add(e.blog)
filter()和select_related()的顺序不重要。 下面的查询集是等同的:
Entry.objects.filter(pub_date__gt=timezone.now()).select_related('blog')
Entry.objects.select_related('blog').filter(pub_date__gt=timezone.now())
可以沿着外键查询。 如果有以下模型:
from django.db import models
class City(models.Model):
# ...
pass
class Person(models.Model):
# ...
hometown = models.ForeignKey(
City,
on_delete=models.SET_NULL,
blank=True,
null=True,
)
class Book(models.Model):
# ...
author = models.ForeignKey(Person, on_delete=models.CASCADE)
调用Book.objects.select_related('author__hometown').get(id=4)将缓存相关的Person 和相关的City:
b = Book.objects.select_related('author__hometown').get(id=4)
p = b.author # Doesn't hit the database.
c = p.hometown # Doesn't hit the database.
b = Book.objects.get(id=4) # No select_related() in this example.
p = b.author # Hits the database.
c = p.hometown # Hits the database.
在传递给select_related()的字段中,可以使用任何ForeignKey和OneToOneField。
在传递给select_related的字段中,还可以反向引用OneToOneField。也就是说,可以回溯到定义OneToOneField 的字段。 此时,可以使用关联对象字段的related_name,而不要指定字段的名称。
17. prefetch_related() prefetch_related(*lookups)
在单个批处理中自动检索每个指定查找的相关对象。
与select_related类似,但是策略是完全不同的。
假设有这些模型:
from django.db import models
class Topping(models.Model):
name = models.CharField(max_length=30)
class Pizza(models.Model):
name = models.CharField(max_length=50)
toppings = models.ManyToManyField(Topping)
def str(self): # unicode on Python 2
return "%s (%s)" % (
self.name,
", ".join(topping.name for topping in self.toppings.all()),
)
并运行:
Pizza.objects.all()
["Hawaiian (ham, pineapple)", "Seafood (prawns, smoked salmon)"...
问题是每次QuerySet要求Pizza.objects.all()查询数据库,因此self.toppings.all()将在Pizza Pizza.str()中的每个项目的Toppings表上运行查询。
可以使用prefetch_related减少为只有两个查询:
Pizza.objects.all().prefetch_related('toppings')
这意味着现在每次self.toppings.all()被调用,不会再去数据库查找,而是在一个预取的QuerySet缓存中查找。
还可以使用正常连接语法来执行相关字段的相关字段。 假设在上面的例子中增加一个额外的模型:
class Restaurant(models.Model):
pizzas = models.ManyToManyField(Pizza, related_name='restaurants')
best_pizza = models.ForeignKey(Pizza, related_name='championed_by')
以下是合法的:
Restaurant.objects.prefetch_related('pizzas__toppings')
这将预取所有属于餐厅的比萨饼,和所有属于那些比萨饼的配料。 这将导致总共3个查询 - 一个用于餐馆,一个用于比萨饼,一个用于配料。
Restaurant.objects.prefetch_related('best_pizza__toppings')
这将获取最好的比萨饼和每个餐厅最好的披萨的所有配料。 这将在3个表中查询 - 一个为餐厅,一个为“最佳比萨饼”,一个为一个为配料。
当然,也可以使用best_pizza来获取select_related关系,以将查询数减少为2:
Restaurant.objects.select_related('best_pizza').prefetch_related('best_pizza__toppings')
[*]extra() extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
有些情况下,Django的查询语法难以简单的表达复杂的WHERE子句,对于这种情况,可以在extra()生成的SQL从句中注入新子句。使用这种方法作为最后的手段,这是一个旧的API,在将来的某个时候可能被弃用。仅当无法使用其他查询方法表达查询时才使用它。
例如:
qs.extra(
... select={'val': "select col from sometable where othercol = %s"},
... select_params=(someparam,),
... )
相当于:
qs.annotate(val=RawSQL("select col from sometable where othercol = %s", (someparam,)))
[*]defer() defer(*fields)
在一些复杂的数据建模情况下,模型可能包含大量字段,其中一些可能包含大尺寸数据(例如文本字段),将它们转换为Python对象需要花费很大的代价。
当最初获取数据时不知道是否需要这些特定字段的情况下,如果正在使用查询集的结果,可以告诉Django不要从数据库中检索它们。
通过传递字段名称到defer()实现不加载:
Entry.objects.defer("headline", "body")
具有延迟加载字段的查询集仍将返回模型实例。
每个延迟字段将在你访问该字段时从数据库中检索(每次只检索一个,而不是一次检索所有的延迟字段)。
可以多次调用defer()。 每个调用都向延迟集添加新字段:
延迟body和headline两个字段。
Entry.objects.defer("body").filter(rating=5).defer("headline")
字段添加到延迟集的顺序无关紧要。对已经延迟的字段名称再次defer()没有问题(该字段仍将被延迟)。
可以使用标准的双下划线符号来分隔关联的字段,从而加载关联模型中的字段:
Blog.objects.select_related().defer("entry__headline", "entry__body")
如果要清除延迟字段集,将None作为参数传递到defer():
立即加载所有的字段。
my_queryset.defer(None)
defer()方法(及其兄弟,only())仅适用于高级用例,它们提供了数据加载的优化方法。
20. only() only(*fields)
only()方法与defer()相反。
如果有一个模型几乎所有的字段需要延迟,使用only()指定补充的字段集可以使代码更简单。
假设有一个包含字段biography、age和name的模型。 以下两个查询集是相同的,就延迟字段而言:
Person.objects.defer("age", "biography")
Person.objects.only("name")
每当你调用only()时,它将替换立即加载的字段集。因此,对only()的连续调用的结果是只有最后一次调用的字段被考虑:
This will defer all fields except the headline.
Entry.objects.only("body", "rating").only("headline")
由于defer()以递增方式动作(向延迟列表中添加字段),因此你可以结合only()和defer()调用:
Final result is that everything except "headline" is deferred.
Entry.objects.only("headline", "body").defer("body")
Final result loads headline and body immediately (only() replaces any
existing set of fields).
Entry.objects.defer("body").only("headline", "body")
当对具有延迟字段的实例调用save()时,仅保存加载的字段。
21. using() using(alias)
如果正在使用多个数据库,这个方法用于指定在哪个数据库上查询QuerySet。方法的唯一参数是数据库的别名,定义在DATABASES。
例如:
queries the database with the 'default' alias.
Entry.objects.all()
queries the database with the 'backup' alias
Entry.objects.using('backup')
[*]select_for_update() select_for_update(nowait=False, skip_locked=False)
返回一个锁住行直到事务结束的查询集,如果数据库支持,它将生成一个SELECT ... FOR UPDATE语句。
例如:
entries = Entry.objects.select_for_update().filter(author=request.user)
所有匹配的行将被锁定,直到事务结束。这意味着可以通过锁防止数据被其它事务修改。
一般情况下如果其他事务锁定了相关行,那么本查询将被阻塞,直到锁被释放。使用select_for_update(nowait=True)将使查询不阻塞。如果其它事务持有冲突的锁,那么查询将引发DatabaseError异常。也可以使用select_for_update(skip_locked=True)忽略锁定的行。nowait和skip_locked是互斥的。
目前,postgresql,oracle和mysql数据库后端支持select_for_update()。但是,MySQL不支持nowait和skip_locked参数。
[*]raw() raw(raw_query, params=None, translations=None)
接收一个原始的SQL查询,执行它并返回一个django.db.models.query.RawQuerySet实例。
这个RawQuerySet实例可以迭代,就像普通的QuerySet一样。
四、不返回QuerySets的API
以下的方法不会返回QuerySets,但是作用非常强大,尤其是粗体显示的方法,需要背下来。
方法名 解释
get() 获取单个对象
create() 创建对象,无需save()
get_or_create() 查询对象,如果没有找到就新建对象
update_or_create() 更新对象,如果没有找到就创建对象
bulk_create() 批量创建对象
count() 统计对象的个数
in_bulk() 根据主键值的列表,批量返回对象
iterator() 获取包含对象的迭代器
latest() 获取最近的对象
earliest() 获取最早的对象
first() 获取第一个对象
last() 获取最后一个对象
aggregate() 聚合操作
exists() 判断queryset中是否有对象
update() 批量更新对象
delete() 批量删除对象
as_manager() 获取管理器
[*]get() get(**kwargs) 返回按照查询参数匹配到的单个对象,参数的格式应该符合Field lookups的要求。
如果匹配到的对象个数不只一个的话,触发MultipleObjectsReturned异常
如果根据给出的参数匹配不到对象的话,触发DoesNotExist异常。例如:
Entry.objects.get(id='foo') # raises Entry.DoesNotExist
DoesNotExist异常从django.core.exceptions.ObjectDoesNotExist继承,可以定位多个DoesNotExist异常。 例如:
from django.core.exceptions import ObjectDoesNotExist
try:
e = Entry.objects.get(id=3)
b = Blog.objects.get(id=1)
except ObjectDoesNotExist:
print("Either the entry or blog doesn't exist.")
如果希望查询器只返回一行,则可以使用get()而不使用任何参数来返回该行的对象:
entry = Entry.objects.filter(...).exclude(...).get()
2. create() create(**kwargs)在一步操作中同时创建并且保存对象的便捷方法.
p = Person.objects.create(first_name="Bruce", last_name="Springsteen")
等于:
p = Person(first_name="Bruce", last_name="Springsteen")
p.save(force_insert=True)
参数force_insert表示强制创建对象。如果model中有一个你手动设置的主键,并且这个值已经存在于数据库中, 调用create()将会失败并且触发IntegrityError因为主键必须是唯一的。如果你手动设置了主键,做好异常处理的准备。
3. get_or_create() get_or_create(defaults=None, **kwargs)
通过kwargs来查询对象的便捷方法(如果模型中的所有字段都有默认值,可以为空),如果该对象不存在则创建一个新对象。
该方法返回一个由(object, created)组成的元组,元组中的object 是一个查询到的或者是被创建的对象, created是一个表示是否创建了新的对象的布尔值。
对于下面的代码:
try:
obj = Person.objects.get(first_name='John', last_name='Lennon')
except Person.DoesNotExist:
obj = Person(first_name='John', last_name='Lennon', birthday=date(1940, 10, 9))
obj.save()
如果模型的字段数量较大的话,这种模式就变的非常不易用了。 上面的示例可以用get_or_create()重写 :
obj, created = Person.objects.get_or_create(
first_name='John',
last_name='Lennon',
defaults={'birthday': date(1940, 10, 9)},
)
任何传递给get_or_create()的关键字参数,除了一个可选的defaults,都将传递给get()调用。 如果查找到一个对象,返回一个包含匹配到的对象以及False 组成的元组。 如果查找到的对象超过一个以上,将引发MultipleObjectsReturned。如果查找不到对象,get_or_create()将会实例化并保存一个新的对象,返回一个由新的对象以及True组成的元组。新的对象将会按照以下的逻辑创建:
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()
它表示从非'defaults' 且不包含双下划线的关键字参数开始。然后将defaults的内容添加进来,覆盖必要的键,并使用结果作为关键字参数传递给模型类。
如果有一个名为defaults__exact的字段,并且想在get_or_create()时用它作为精确查询,只需要使用defaults,像这样:
Foo.objects.get_or_create(defaults__exact='bar', defaults={'defaults': 'baz'})
当你使用手动指定的主键时,get_or_create()方法与create()方法有相似的错误行为 。 如果需要创建一个对象而该对象的主键早已存在于数据库中,IntegrityError异常将会被触发。
这个方法假设进行的是原子操作,并且正确地配置了数据库和正确的底层数据库行为。如果数据库级别没有对get_or_create中用到的kwargs强制要求唯一性(unique和unique_together),方法容易导致竞态条件,可能会有相同参数的多行同时插入。(简单理解,kwargs必须指定的是主键或者unique属性的字段才安全。)
最后建议只在Django视图的POST请求中使用get_or_create(),因为这是一个具有修改性质的动作,不应该使用在GET请求中,那样不安全。
可以通过ManyToManyField属性和反向关联使用get_or_create()。在这种情况下,应该限制查询在关联的上下文内部。 否则,可能导致完整性问题。
例如下面的模型:
class Chapter(models.Model):
title = models.CharField(max_length=255, unique=True)
class Book(models.Model):
title = models.CharField(max_length=256)
chapters = models.ManyToManyField(Chapter)
可以通过Book的chapters字段使用get_or_create(),但是它只会获取该Book内部的上下文:
book = Book.objects.create(title="Ulysses")
book.chapters.get_or_create(title="Telemachus")
(, True)
book.chapters.get_or_create(title="Telemachus")
(, False)
Chapter.objects.create(title="Chapter 1")
book.chapters.get_or_create(title="Chapter 1")
Raises IntegrityError
发生这个错误是因为尝试通过Book “Ulysses”获取或者创建“Chapter 1”,但是它不能,因为它与这个book不关联,但因为title 字段是唯一的它仍然不能创建。
在Django1.11在defaults中增加了对可调用值的支持。
4. update_or_create() update_or_create(defaults=None, **kwargs) 类似前面的get_or_create()。
通过给出的kwargs来更新对象的便捷方法, 如果没找到对象,则创建一个新的对象。defaults是一个由 (field, value)对组成的字典,用于更新对象。defaults中的值可以是可调用对象(也就是说函数等)。
该方法返回一个由(object, created)组成的元组,元组中的object是一个创建的或者是被更新的对象, created是一个标示是否创建了新的对象的布尔值。
update_or_create方法尝试通过给出的kwargs 去从数据库中获取匹配的对象。 如果找到匹配的对象,它将会依据defaults 字典给出的值更新字段。
像下面的代码:
defaults = {'first_name': 'Bob'}
try:
obj = Person.objects.get(first_name='John', last_name='Lennon')
for key, value in defaults.items():
setattr(obj, key, value)
obj.save()
except Person.DoesNotExist:
new_values = {'first_name': 'John', 'last_name': 'Lennon'}
new_values.update(defaults)
obj = Person(**new_values)
obj.save()
如果模型的字段数量较大的话,这种模式就变的非常不易用了。
上面的示例可以用update_or_create() 重写:
obj, created = Person.objects.update_or_create(
first_name='John', last_name='Lennon',
defaults={'first_name': 'Bob'},
)
kwargs中的名称如何解析的详细描述可以参见get_or_create()。
和get_or_create()一样,这个方法也容易导致竞态条件,如果数据库层级没有前置唯一性会让多行同时插入。
在Django1.11在defaults中增加了对可调用值的支持。
5. bulk_create() bulk_create(objs, batch_size=None)
以高效的方式(通常只有1个查询,无论有多少对象)将提供的对象列表插入到数据库中:
Entry.objects.bulk_create([
... Entry(headline='This is a test'),
... Entry(headline='This is only a test'),
... ])
注意事项:
不会调用模型的save()方法,并且不会发送pre_save和post_save信号。
不适用于多表继承场景中的子模型。
如果模型的主键是AutoField,则不会像save()那样检索并设置主键属性,除非数据库后端支持。
不适用于多对多关系。
batch_size参数控制在单个查询中创建的对象数。
[*]count() 返回在数据库中对应的QuerySet对象的个数。count()永远不会引发异常。
返回总个数.
Entry.objects.count()
返回包含有'Lennon'的对象的总数
Entry.objects.filter(headline__contains='Lennon').count()
7. in_bulk() in_bulk(id_list=None)
获取主键值的列表,并返回将每个主键值映射到具有给定ID的对象的实例的字典。 如果未提供列表,则会返回查询集中的所有对象。
例如:
Blog.objects.in_bulk()
{1: }
Blog.objects.in_bulk()
{1: , 2: }
Blog.objects.in_bulk([])
{}
Blog.objects.in_bulk()
{1: , 2: , 3: }
如果向in_bulk()传递一个空列表,会得到一个空的字典。
在旧版本中,id_list是必需的参数,现在是一个可选参数。
[*]iterator() 提交数据库操作,获取QuerySet,并返回一个迭代器。
QuerySet通常会在内部缓存其结果,以便在重复计算时不会导致额外的查询。而iterator()将直接读取结果,不在QuerySet级别执行任何缓存。对于返回大量只需要访问一次的对象的QuerySet,这可以带来更好的性能,显著减少内存使用。
请注意,在已经提交了的iterator()上使用QuerySet会强制它再次提交数据库操作,进行重复查询。此外,使用iterator()会导致先前的prefetch_related()调用被忽略,因为这两个一起优化没有意义。
[*]latest() latest(field_name=None)使用日期字段field_name,按日期返回最新对象。
下例根据Entry的'pub_date'字段返回最新发布的entry:
Entry.objects.latest('pub_date')
如果模型的Meta指定了get_latest_by,则可以将latest()参数留给earliest()或者field_name。 默认情况下,Django将使用get_latest_by中指定的字段。
earliest()和latest()可能会返回空日期的实例,可能需要过滤掉空值:
Entry.objects.filter(pub_date__isnull=False).latest('pub_date')
[*]earliest() earliest(field_name=None)
类同latest()。
[*]first() 返回结果集的第一个对象, 当没有找到时返回None。如果QuerySet没有设置排序,则将会自动按主键进行排序。例如:
p = Article.objects.order_by('title', 'pub_date').first()
first()是一个简便方法,下面的例子和上面的代码效果是一样:
try:
p = Article.objects.order_by('title', 'pub_date')
except IndexError:
p = None
[*]last() 工作方式类似first(),只是返回的是查询集中最后一个对象。
[*]aggregate() aggregate(args, *kwargs)
返回汇总值的字典(平均值,总和等),通过QuerySet进行计算。每个参数指定返回的字典中将要包含的值。
使用关键字参数指定的聚合将使用关键字参数的名称作为Annotation 的名称。 匿名参数的名称将基于聚合函数的名称和模型字段生成。 复杂的聚合不可以使用匿名参数,必须指定一个关键字参数作为别名。
例如,想知道Blog Entry 的数目:
from django.db.models import Count
q = Blog.objects.aggregate(Count('entry'))
{'entry__count': 16}
通过使用关键字参数来指定聚合函数,可以控制返回的聚合的值的名称:
q = Blog.objects.aggregate(number_of_entries=Count('entry'))
[*]exists() 如果QuerySet包含任何结果,则返回True,否则返回False。
查找具有唯一性字段(例如primary_key)的模型是否在一个QuerySet中的最高效的方法是:
entry = Entry.objects.get(pk=123)
if some_queryset.filter(pk=entry.pk).exists():
print("Entry contained in queryset")
它将比下面的方法快很多,这个方法要求对QuerySet求值并迭代整个QuerySet:
if entry in some_queryset:
print("Entry contained in QuerySet")
若要查找一个QuerySet是否包含任何元素:
if some_queryset.exists():
print("There is at least one object in some_queryset")
将快于:
if some_queryset:
print("There is at least one object in some_queryset")
[*]update() update(**kwargs)
对指定的字段执行批量更新操作,并返回匹配的行数(如果某些行已具有新值,则可能不等于已更新的行数)。
例如,要对2010年发布的所有博客条目启用评论,可以执行以下操作:
Entry.objects.filter(pub_date__year=2010).update(comments_on=False)
可以同时更新多个字段 (没有多少字段的限制)。 例如同时更新comments_on和headline字段:
Entry.objects.filter(pub_date__year=2010).update(comments_on=False, headline='This is old')
update()方法无需save操作。唯一限制是它只能更新模型主表中的列,而不是关联的模型,例如不能这样做:
Entry.objects.update(blog__name='foo') # Won't work!
仍然可以根据相关字段进行过滤:
Entry.objects.filter(blog__id=1).update(comments_on=True)
update()方法返回受影响的行数:
Entry.objects.filter(id=64).update(comments_on=True)
1
Entry.objects.filter(slug='nonexistent-slug').update(comments_on=True)
0
Entry.objects.filter(pub_date__year=2010).update(comments_on=False)
132
如果你只是更新一下对象,不需要为对象做别的事情,最有效的方法是调用update(),而不是将模型对象加载到内存中。 例如,不要这样做:
e = Entry.objects.get(id=10)
e.comments_on = False
e.save()
建议如下操作:
Entry.objects.filter(id=10).update(comments_on=False)
用update()还可以防止在加载对象和调用save()之间的短时间内数据库中某些内容可能发生更改的竞争条件。
如果想更新一个具有自定义save()方法的模型的记录,请循环遍历它们并调用save(),如下所示:
for e in Entry.objects.filter(pub_date__year=2010):
e.comments_on = False
e.save()
[*]delete() 批量删除QuerySet中的所有对象,并返回删除的对象个数和每个对象类型的删除次数的字典。
delete()动作是立即执行的。
不能在QuerySet上调用delete()。
例如,要删除特定博客中的所有条目:
b = Blog.objects.get(pk=1)
Delete all the entries belonging to this Blog.
Entry.objects.filter(blog=b).delete()
(4, {'weblog.Entry': 2, 'weblog.Entry_authors': 2})
默认情况下,Django的ForeignKey使用SQL约束ON DELETE CASCADE,任何具有指向要删除的对象的外键的对象将与它们一起被删除。 像这样:
blogs = Blog.objects.all()
This will delete all Blogs and all of their Entry objects.
blogs.delete()
(5, {'weblog.Blog': 1, 'weblog.Entry': 2, 'weblog.Entry_authors': 2})
这种级联的行为可以通过的ForeignKey的on_delete参数自定义。(什么时候要改变这种行为呢?比如日志数据,就不能和它关联的主体一并被删除!)
delete()会为所有已删除的对象(包括级联删除)发出pre_delete和post_delete信号。
[*]as_manager()
class method as_manager()一个类方法,返回Manager的实例与QuerySet的方法的副本。
Django中的queryset查询方法
在django中,我们常常需要对得到的queryset进行查询,django会将我们的查询语句转换为slite3的查询语法然后去数据库查询数据,我们要做的就是使用django给我们的一些查询方法进行查询
我们可以通过模型名称+objects.all()来获取一个完整的queryset,然后使用这个queryset进行查询和筛选
QuerySet.filter(condition)
该方法将根据condition提取queryset中的模型实例,并且返回一个queryset
QuerySet.get(condition)
需要注意get方法仅返回一个模型实例,不返回queryset,如果查询的结果超过1将报错
QuerySet.exclude(condition)
该方法是filter方法的取反
链式查询有了上述的三个方法我们就可以进行简单的查询了,而且我们还可以进行连续的查询,例如:
queryset.filter(tag='user').filter(date =date(2018,11,06))
queryset.filter(tag='user').order_by('release_time')
使用Q()进行查询
除此以外,我们还可以使用一些更为复杂的筛选方法,有一些辅助函数可以使用,例如Q():
from django.db.models import Q
condition1 = Q(tag='user1')
condition2 = Q(tag='user2')
condition3 = Q(date__gte=date(2018,11,01))
queryset.filter(condition1|condition2) #提取并集
queryset.filter(condition1,condition3) #提取交集
queryset.filter(condtion1,condition2) #取反
常用的查询条件
上述中有一个双下划线加gte的东西出现,这个东西的意思是greater than or equal to的意思,实际上,django还提供了一些常用的用于比较大小,包含某些字体,是否精确匹配的方法用于查询。
queryset.filter(date__lte=date(2018,11,01)) #时间早于20181101
queryset.filter(date__gte=date(2018,11,01)) #时间晚于20181101
queryset.filter(tag__contain='tag1') #tag字段中包含字符串tag1的模型实例
queryset.filter(tag__icontain='tag1') #同上,同时忽略大小写
queryset.filter(tag__iexact='tag1')#tag字段中等于tag1的模型实例,忽略大小写
queryset.filter(tag__exact='tag1')#精确匹配
queryset.filter(tag__isnull=True)#提取tag字段为缺失值的模型实例
跨模型查询
有时候我们需要根据那些django的关系字段OneToOneField,ForeignKey,ManyToManyField的关系来直接找到关联的模型并进行查询,对于这种需求,django进行查询也较为方便,只需要使用双下划线进行连接即可。
queryset.filter(othermodel__tag='tag1')
找到与该模型关联模型othermodel的tag为tag1的该模型的实例
queryset.filter(othermodel__otherothermodel__tag='tag1')
穿越两个模型进行查询也是可以的。
[*]Queryset简介
每个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)
a = UserEx.objects.all()
type(a)
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.nameclass Author(models.Model):
name = models.CharField(max_length=50)
email = models.EmailField()
def __unicode__(self):
return self.nameclass 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为例。
1.1 遍历
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。
1.2 切片切片不会立即执行,除非显示指定了步长,如a= Entry.objects.all(),步长为2。
1.3 序列化,即Pickling 序列化QuerySet很少用。
1.4repr()和str()功能相似,将对象转为字符串,很少用。
1.5 len() 计算QuerySet元素的数量,并不推荐使用len(),除非QuerySet是求过值的(即evaluated),否则,用QuerySet.count()获取元素数量,这个效率要高。
1.6 list()将QuerySet转为list。
1.7 bool(),判断是否为空
if Entry.objects.filter(headline="Test"):
print("There is at least one Entry with the headline Test")
同样不建议这种方法判断是否为空,而应该使用QuerySet.exists(),查询效率高。
[*]QuerySet的方法
数据库的常用操作就四种:增、删、改、查,QuerySet的方法涉及删、改、查。后面还会讲model对象的方法,model方法主要是增、删、改、还有调用model实例的字段。
2.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.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()会出错。
2.3 查询 filter(kwargs)、exclude(kwargs)、get(**kwargs)
相当于select-from-where,select-from-join-where,很多网站读数据库操作最多。可以看到,filter()的参数是变个数的键值对,而不会出现>, '2005-1-3' AND headline = 'Hello')
</blockquote></blockquote></blockquote>2.4 SQL其它关键字在django中的实现
在SQL中,很多关键词在删、改、查时都是可以用的,如order by、 like、in、join、union、and、or、not等等,我们以查询为例,说一下django如何映射SQL的这些关键字的(查、删、改中这些关键字的使用方法基本相同)。
2.4.1F类(无对应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'))
2.4.2 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类,待测试。
2.4.3annotate(无对应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.
2.4.4 order_by——对应order by
函数原型 order_by(*fields) 返回QuerySet
正向的反向关联表跟filter的方式一样。如果直接用字段名,那就是升序asc排列;如果字段名前加-,就是降序desc
2.4.5distinct——对应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
2.4.6 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)
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
2.4.7select_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)
2.4.8prefetch_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):
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
2.4.9extra()——实现复杂的where子句
函数原型:extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
基本上,查询时用django提供的方法就够用了,不过有时where子句中包含复杂的逻辑,这种情况下django提供的方法可能不容易做到,还好,django有extra(), extra()中直接写一些SQL语句。不过,不同的数据库用的SQL有些差异,所以尽可能不要用extra()。需要时再看使用方法吧。
2.4.10 aggregate(*args, kwargs)——对应聚合函数
参数为聚合函数,最好用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()方法。
2.4.11exists()、count()、len()
如果只是想知道一个QuerySet是否为空,而不想获取QuerySet中的每个元素,那就用exists(),它要比len()、count()、和直接进行if判断效率高。如果只想知道一个QuerySet有多大,而不想获取QuerySet中的每个元素,那就用count();如果已经从数据库获取到了QuerySet,那就用len()
2.4.12contains/startswith/endswith——对应like
字段名加双下划线,除了它,还有icontains,即Case-insensitive contains,这个是大小写不敏感的,这需要相应数据库的支持。有些数据库需要设置
才能支持大小写敏感。
2.4.13in——对应in字段名加双下划线
2.4.14exclude(field__in=iterable)——对应not in iterable是可迭代对象
2.4.15gt/gte/lt/lte——对应于>,>=,
页:
[1]