Django model对象接口

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=())

  • 返回一个查询集前将会进行行级锁,直到这个事务完成
    意味着在事务完成前,所有匹配的行会被加锁。不允许修改。直到该事务完成。才会释放锁

猜你喜欢

转载自www.cnblogs.com/zengchunyun/p/9175721.html