Django model查询
# 直接获取表对应字段的值,列表嵌元组形式返回
Entry.objects.values_list('id', 'headline')
#<QuerySet [(1, 'First entry'), ...]>
from django.db.models.functions import Lower
# 使用函数。对查询的结果进行处理,这里将对应字段headline的值全部转为小写。
# 更多对结果处理对函数都在该模块内
Entry.objects.values_list('id', Lower('headline'))
# 查询的结果被处理了
#<QuerySet [(1, 'first entry'), ...]>
Entry.objects.values_list('id')
# 对于单个字段的值的结果,默认是这样的结果,也是元组形式返回,通常这并不是你想要的结果
#<QuerySet[(1,), (2,), (3,), ...]>
# 使用关键字参数flat,就只能将单个字段的结果以列表形式直接返回了
Entry.objects.values_list('id', flat=True)
# <QuerySet [1, 2, 3, ...]>
# 当有多个字段需要返回时,也可以以命名元组形式返回
# 使用关键字参数named
Entry.objects.values_list('id', 'headline', named=True)
#<QuerySet [Row(id=1, headline='First entry'), ...]>
# 当你需要只获取指定字段的值,可以使用get方法获取,前提是你能保证存在pk=1这条记录,否则会抛出异常
Entry.objects.values_list('headline', flat=True).get(pk=1)
# 这个显示就是pk为1的字段headline对应的值
# 'First entry'
# 使用values得到是字典形式的结果,也支持函数参数对值进行处理
Blog.objects.values()
# <QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>
# 对指定key的结果转换为小写输出
from django.db.models.functions import Lower
Blog.objects.values(lower_name=Lower('name'))
#<QuerySet [{'lower_name': 'beatles blog'}]>
# 使用count方法将相同的字段数据进行计数统计
from django.db.models import Count
Blog.objects.values('author', entries=Count('entry'))
# <QuerySet [{'author': 1, 'entries': 20}, {'author': 1, 'entries': 13}]>
# 如果需要使用group by形式分组查询结果。
Blog.objects.values('author').annotate(entries=Count('entry'))
# <QuerySet [{'author': 1, 'entries': 33}]>
# 当获取一个外键字段值时。如字段名称foo为外键字段。values查询时。使用foo 和foo_id是等价当效果
Entry.objects.values()
# <QuerySet [{'blog_id': 1, 'headline': 'First Entry', ...}, ...]>
Entry.objects.values('blog')
# <QuerySet [{'blog': 1}, ...]>
Entry.objects.values('blog_id')
# <QuerySet [{'blog_id': 1}, ...]>
# 以下这种方式查询是等价的
Blog.objects.values().order_by('id')
Blog.objects.order_by('id').values()
# 合并查询结果
qs1 = Author.objects.values_list('name')
qs2 = Entry.objects.values_list('headline')
qs1.union(qs2).order_by('name')
# 合并多个查询结果
qs1.union(qs2, qs3)
# 返回交集查询结果
qs1.intersection(qs2, qs3)
# 返回差集查询结果
qs1.difference(qs2, qs3)
select_related()查询优化
# 对于有外键关联的表查询。如果不使用select_related查询。那么最终只会查询单条没有任何关联的结果
# 例如。获取ID为5的entry记录,第一次会查询一次数据库
e = Entry.objects.get(id=5)
# 当访问这条记录对应的外键关联字段时。还会再次查询数据库。显然。这不是我们想要的。我们希望查询上面的记录同时把对应的blog也一并查询
b = e.blog
# 使用这种方式。明确告知查询外键字段blog对应的实例对象结果
# select_related不指定参数时。则获取所以与之外键关联的对象
e = Entry.objects.select_related('blog').get(id=5)
# 这时再去访问blog对象时。不会进行第二次数据库查询。
b = 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)
# 下面这种查询方式将会把City对象缓存起来。
b = Book.objects.select_related('author__hometown').get(id=4)
p = b.author # 不从数据库查询获取,直接从上面查询的缓存获取结果
c = p.hometown # 不从数据库查询获取,直接从上面查询的缓存获取结果
b = Book.objects.get(id=4) # 没有使用select_related查询
p = b.author # 这时会从数据库查询获取结果
c = p.hometown # 这时会从数据库查询获取结果
# select_related同样支持链式操作
select_related('foo', 'bar')
# 等价于
select_related('foo').select_related('bar')
prefetch_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):
return "%s (%s)" % (
self.name,
", ".join(topping.name for topping in self.toppings.all()),
)
class Restaurant(models.Model):
pizzas = models.ManyToManyField(Pizza, related_name='restaurants')
best_pizza = models.ForeignKey(Pizza, related_name='championed_by', on_delete=models.CASCADE)
# 在大量查询集的情况下。没有进行优化。那么每次调用
# Pizza.__str__()时(print会自动触发该方法),会每触发一次,查询一次数据库
Pizza.objects.all()
# ["Hawaiian (ham, pineapple)", "Seafood (prawns, smoked salmon)"...
# 将上面的方式优化为下面这种
Pizza.objects.all().prefetch_related('toppings')
# 这时再调用self.toppings.all()将从查询缓存中获取数据。不是去查询数据库
# 如果这时再对缓存数据进行子查询,那么依然会查询数据库。而不是从缓存过滤查询集
pizzas = Pizza.objects.prefetch_related('toppings')
# 下面的子句过滤会从数据库查询获取,这时的prefetch_related链式调用并没有效果,反而降低了性能,所以使用这个功能需要格外小心
[list(pizza.toppings.filter(spicy=True)) for pizza in pizzas]
#
Restaurant.objects.prefetch_related('pizzas__toppings')
# 下面这种方式会产生三次查询
Restaurant.objects.prefetch_related('best_pizza__toppings')
# 应该优化写成这种形式,这样会优化查询次数为2次
Restaurant.objects.select_related('best_pizza').prefetch_related('best_pizza__toppings')
- select_related 和 prefetch_related。前者适用于单条数据的查询集缓存。后者使用于大的查询集缓存
- 普通的foreign key用select_related,many to many用prefetch_related
extra,扩展sql表达式,该功能会被遗弃,使用RawSQL替代
# 使用这种方式要避免SQL注入攻击
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,)))
# 以查询结果方式展示,is_recent相当于查询的别名
Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"})
# SQL等价于
# SELECT blog_entry.*, (pub_date > '2006-01-01') AS is_recent FROM blog_entry
Blog.objects.extra( select={
'entry_count': 'SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id= blog_blog.id'
}, )
# 等价于
# SELECT blog_blog.*, (SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id =blog_blog.id) AS entry_count FROM blog_blog;
Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
# SELECT * FROM blog_entry WHERE (foo='a' OR bar='a') AND (baz='a')
# 对需要排序的查询。需要使用正确的查询字段
q = Entry.objects.extra(select={'is_recent': "pub_date > '2006-01-01'"})
q = q.extra(order_by = ['-is_recent'])
# 对于参数查询,推荐使用这种查询方式
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
# 不推荐使用下面这种方式查询
Entry.objects.extra(where=["headline='Lennon'"])
defer 延迟查询方法。
- 当你需要访问该字段的值时才去查询。实际上有的类型values。指定查询字段,只不过这里是取反,参数里的字段不查。而values是查参数里的字段
- 对主键貌似无效
class Organization(models.Model):
"""
组织关系架构表
"""
name = models.CharField(max_length=255, verbose_name='组织名称')
parent = models.ForeignKey('self', verbose_name='所属组织', blank=True,
null=True, on_delete=models.SET_NULL)
company = models.ForeignKey('Company', verbose_name='所属公司', blank=True,
null=True, on_delete=models.SET_NULL)
admin = models.ForeignKey('User', verbose_name='管理负责人', blank=True,
null=True, on_delete=models.SET_NULL,
related_name='organization_admin')
members = models.ManyToManyField('User', verbose_name='组织成员', blank=True,
related_name='organization_members')
label = models.ManyToManyField('Label', verbose_name='标签集合', blank=True)
Organization.objects.defer('id').query.__str__()
# SELECT `organization`.`id`, `organization`.`name`, `organization`.`parent_id`, `organization`.`company_id`, `organization`.`admin_id` FROM `organization`
Organization.objects.defer('company').query.__str__()
# SELECT `organization`.`id`, `organization`.`name`, `organization`.`parent_id`, `organization`.`admin_id` FROM `organization`
Organization.objects.defer('company', 'admin').query.__str__()
# SELECT `organization`.`id`, `organization`.`name`, `organization`.`parent_id` FROM `organization`
# 看出效果了么?
- 如果对于外键部分字段想延迟查询,也可以使用defer
# headline为外键字段。select_related会获取外键对应对表的数据查询。使用defer会过滤掉暂时不需要外键某些表的数据字段
Blog.objects.select_related().defer("entry__headline", "entry__body")
- 当你不想花过多时间考虑在需要过滤掉哪些字段需要延迟查询时,可以考虑通过创建同名表model。
# 两个表名一样
class CommonlyUsedModel(models.Model):
f1 = models.CharField(max_length=10)
class Meta:
managed = False # 这个属性告诉Django在迁移数据库配置时,不要把它考虑进去。否则如果存在两张一样的表,Django迁移会出错的
db_table = 'app_largetable'
class ManagedModel(models.Model):
f1 = models.CharField(max_length=10)
f2 = models.CharField(max_length=10)
class Meta:
db_table = 'app_largetable'
# 下面这两种查询方式完全一样的查询结果。第一种不需要关注哪些需要延迟查询的字段。
CommonlyUsedModel.objects.all()
ManagedModel.objects.all().defer('f2')
# 注意。当在使用defer查询再调用save方法时。保存的只有已经加载的字段数据。延迟的字段值不会保存,也就是说,这里的f2即使有对应的f2关键字参数赋值。也不会更新f2的值到数据库
only 相对于defer作的优化。也是上面这个例子的一个解决方案
- 顾名思义。只查询某些字段
# 第一种,排除两个字段。实际也就剩余name字段需要现在查询
Person.objects.defer("age", "biography")
# 第二种是直接查询name字段,其它字段不管
Person.objects.only("name")
Organization.objects.only('company', 'admin').query.__str__()
# SELECT `organization`.`id`, `organization`.`company_id`, `organization`.`admin_id` FROM `organization`
# 注意。在链式查询时。只会保留最后一个only的查询字段。其余被排除
Organization.objects.only('company').only('admin').query.__str__()
# SELECT `organization`.`id`, `organization`.`admin_id` FROM `organization`
# defer在前与在后调用区别
# defer在only后。那么only与defer重合的字段会被延迟。只查询only与defer的差集字段
Organization.objects.only('company').defer('admin').query.__str__()
# SELECT `organization`.`id`, `organization`.`company_id` FROM `organization`
Organization.objects.only('company', 'admin').defer('admin').query.__str__()
# SELECT `organization`.`id`, `organization`.`company_id` FROM `organization`
Organization.objects.only('company', 'admin').defer('company').query.__str__()
# SELECT `organization`.`id`, `organization`.`admin_id` FROM `organization`
# defer在前已经看不透了。。。
Organization.objects.defer('company', 'admin').only('company').query.__str__()
'SELECT `organization`.`id`, `organization`.`name`, `organization`.`parent_id`, `organization`.`company_id`, `organization`.`admin_id` FROM `organization`'
Organization.objects.defer('company', 'admin').only('admin').query.__str__()
'SELECT `organization`.`id`, `organization`.`name`, `organization`.`parent_id`, `organization`.`company_id`, `organization`.`admin_id` FROM `organization`'
Organization.objects.defer('admin').only('admin').query.__str__()
'SELECT `organization`.`id`, `organization`.`name`, `organization`.`parent_id`, `organization`.`company_id`, `organization`.`admin_id` FROM `organization`'
Organization.objects.defer('admin').only('admin','company').query.__str__()
'SELECT `organization`.`id`, `organization`.`company_id` FROM `organization`'
select_for_update(nowait=False, skip_locked=False, of=())
-
返回一个查询集前将会进行行级锁,直到这个事务完成
意味着在事务完成前,所有匹配的行会被加锁。不允许修改。直到该事务完成。才会释放锁 -
nowait=False, skip_locked=False这两个参数只支持oracle和Postgres数据库。mysql暂时不支持
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关键字参数意味着总是能保证将新的对象写入到数据库,前提是你没有自定义主键或联合唯一索引。否则可能创建失败
get_or_create(defaults=None, **kwargs)
-
defaults实际是是一个字典形式参数
-
通常这种方法调用用于POST请求接口
-
获取一个对象,如果对象不存在则创建。返回一个元组形式数据。结构为(对象实例,是否创建)
-
创建的前提是你必须提供创建的必须参数,如一些不能为空的字段,如果没有提供,则创建失败
# 存在则返回
Organization.objects.get_or_create(id=1)
# (<Organization: 中民投国际>, False)
# 不存在则创建
Organization.objects.get_or_create(id=100)
# (<Organization: >, True)
- 这个功能实际上是下面操作的一个简化版
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()
# 转换成下面这种形式
obj, created = Person.objects.get_or_create(
first_name='John',
last_name='Lennon',
defaults={'birthday': date(1940, 10, 9)},
)
# defaults参数明确用于get(birthday=date(1940, 10, 9)) 条件获取调用。相当于获取该条件记录。如果不存在。则将其余信息包括该信息一起写入数据库。创建一条新记录返回
- 下面是这个方法的实现算法
# 先将非默认关键字参数获取。然后将默认关键字参数更新到非默认关键字参数里。
# kwargs的key不能包含双下划线,通常这种参数会被认为是多表查询或条件匹配参数
# 也就是defaults和kwargs同时存在相同key.则defaults最终会被写入数据库,defaults的value可以是可调用函数方法。这里可以看出。defaults就是一个字典对象
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__exact是指字段)明确匹配为bar的记录。
Foo.objects.get_or_create(defaults__exact='bar', defaults={'defaults': 'baz'})
- 对于多对多操作时。外键表包含唯一索引。要避免插入一个重复的错误数据
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 = Book.objects.create(title="Ulysses")
# 为这本书添加一个章节。不存在。则创建。也没问题
book.chapters.get_or_create(title="Telemachus")
# (<Chapter: Telemachus>, True)
# 重复该操作。存在。则返回,不创建。也没问题
book.chapters.get_or_create(title="Telemachus")
#(<Chapter: Telemachus>, False)
# 现在通过正常方式创建一个章节。但是并没有与书进行关联,创建章节操作是成功的
Chapter.objects.create(title="Chapter 1")
# <Chapter: Chapter 1>
# 这时获取与书匹配的章节。是不存在的。应该创建。但是章节要求唯一索引。所以最终这个章节创建失败,且抛出异常
book.chapters.get_or_create(title="Chapter 1")
# Raises IntegrityError