Django Queryset用法


|- apps
|-----Blog
|---------models.py: author, blog, category

filter/get

  • get()不到会抛异常,但get到多条也会报错!!!
author = Author.objects.filter(name="GG", status=0).first()
if not author:
    continue
 
# get()不到会抛异常,但get到多条也会报错;可用filter().first()代替
try:
    author = Author.objects.get(name="GG", status=0)
except Exception:
    return

get/update_or_create

  • get_or_create() 在查询时使用defaults以外的参数进行查询;当实例不存在时,将包含default参数一起创建一个新实例。
  • update_or_create() 数据存在则更新,当实例不存在时,将包含default参数一起创建一个新实例。
au, created = Author.objects.get_or_create(id=6, defaults={'name': "GG"})                                                                                          # 返回(obj, created),obj是查询或创建的对象实例,created是个布尔值,表示是否是新创建的实例。  

BlogAuthoItem.objects.get_or_create(author=au, blog=blog, defaults=info) # django中的字段名
au, created = Author.objects.update_or_create(id=6, defaults=defaults) # 数据存在则更新,不存在则保存

get_or_create() 如果查找到的对象超过一个以上,将引发MultipleObjectsReturned。

update

  • update()会立即执行,不用再执行save()。但用=赋值操作要.save()。
    • 不会产生pre_save或post_save信号(调用save()方法产生)或服从auto_now字段选项, 因此update_time要手动更新!!!
  • update更加适用于批量数据更新,而save则只适合做单条数据更新操作,效率比update低。
Blog.objects.all()[:10]  # 切片操作,获取10个人,不支持负索引,切片可节约内存
v1 = Business.objects.all()  # object  <QuerySet [<Business: Business object>,...]
v2 = Business.objects.all().values('id','caption')  # list  <QuerySet [{'caption': '运维部', 'id': 1}, {'caption': '开发', 'id': 2}, {'caption': '市场', 'id': 3}, {'caption': '测试', 'id': 4}]>
v3 = Business.objects.all().values_list('id','caption')  # tuple <QuerySet [(1, '运维部'), (2, '开发'), (3, '市场'), (4, '测试')]>
 
 
# 批量更新update:适用于 .all() .filter() .exclude()等后面.  危险??
Blog.objects.filter(name__contains="abc").update(name='xxx', publish_time=datetime.now())
Author.objects.filter(id=1).update(**defaults)   # defaults是个dict
Blog.objects.all().delete()  
 
# 单条更新save():适合于.get() .get_or_create() .update_or_create() 等得到的obj.  修改字段后不要忘记 .save()!!!!!!
au.save()

django查询之Q对象、F对象、聚合查询、分组查询

from django.db.models import Q, F, Avg
from datetime import timedelta, datetime
from django.db.models import Avg, Max, Min, Count
from django.db.models.functions import TruncDate, TruncDay, TruncHour, TruncMinute, TruncSecond

# F()  比较同一个model实例中两个不同字段的值。
Blog.objects.filter(like_num__lt=F('hate_num')
q = Q(update_time__gt=F("publish_time") - timedelta(days=1))  # 时间增量
q &= Q(create_time__gt=datetime.now() - timedelta(days=3))
 
# Q对象:支持 & | ~ 组合。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。
blogs = Blog.objects.filter(Q(authors__name="yuan")| Q(authors__name="egon"))
 
# 聚合查询: aggregate()是QuerySet的一个终止子句,它返回一个包含一些键值对的字典
Blog.objects.all().aggregate(Avg("price"),Max("price"))  # 计算价格的均值、最大值,返回 {'price__avg': 192.593333, 'price__max': Decimal('233.33')}    
 
# 分组查询:annotate为调用的QuerySet中每一个对象都生成一个独立的统计值authorNum,适合多对多关系查询
blogs = Blog.objects.all().annotate(authorNum=Count("authors")).order_by('authorNum')
 
Blog.objects.annotate( hour=TruncHour('start_datetime', output_field=TimeField())).values('hour').annotate(experiments=Count('id'))
 
 
# Q对象 和 filter比较:
Blog.objects.filter(Q(category__id=category.id))
Blog.objects.filter(category=category) 
Blog.objects.filter(lcategory__id=category.id)

QuerySet

  • 支持链式查询: Blog.objects.filter(category=2).exclude(private=True)
  • 查询结果排序: Author.objects.all().order_by('name')
  • 不支持负索引: Blog.objects.all().reverse()[:2] # 最后两条
    Blog.objects.order_by('-id')[:20] # id最大的20条
  • 去重: qs = qs.distinct() # distinct('author')
blogs = Blog.objects.values('number')  # [{'number': u'1'}, {'number': u'2'}, {'number': u'3'}]  
blogs = Blog.objects.values_list('number')  # [(u'1',), (u'2',), (u'3',)]  
blogs = Blog.objects.values_list('number', flat=True) .distinct()   # [u'1', u'2', u'3']  

queryset.extra(select={'length': 'Length(title)'}).order_by('length')
并不是执行Objects.all(),或者filter之后就会与数据库交互
  1. queryset总是惰性的
    一个创建QuerySets的动作不会立刻导致任何的数据库行为。你可以不断进行filter动作一整天,Django不会运行任何实际的数据库查询动作,直到QuerySets被提交(evaluated)。
    # 返回QuerySet对象,程序并没有真的在数据库中执行SQL语句查询数据,但支持迭代,可用for循环可获取数据
    blogs = Blog.objects.all()
    blogs = Blog.objects.filter(name__icontains="Django")
    
    # 返回Model对象,说明用get方法会直接执行sql语句获取数据
    blog = Blogs.objects.get(id='1')  
    
  2. queryset具有cache的,可减少数据库查询
    • for语句遍历queryset时,所有匹配的记录会从数据库获取,然后转换成Django的model,这被称为执行(evaluation)。
    • 这些model会保存在queryset内置的cache中,若再次遍历这个queryset,不需要重复运行通用的查询。即代码只会执行一次数据库查询。
    • if queryset 触发执行: 用if语句判断会完全执行整个queryset并把数据放入cache,虽然你并不需要这些数据!!
  3. 避免cache问题
    1. 为避免if,可用queryset.exists()来检查是否有数据,从而节省带宽和内存.
    2. 避免遍历数据时产生cache,可用 .iterator() 来获取数据防止生成cache,处理完数据就将其丢弃。 但多次遍历时会重复执行查询!!!
    3. exists() + iterator() : 用两次数据库查询来避免使用查询集缓存。

len()方法相当于会把整个queryset遍历一次,把所有的数据都取出来对象化,消耗大量资源

  • 数据是否存在:.exists() > count() > len()
  • 数据数量:count() > len()
  • 批量操作QuerySet.update()/delete()`: 不会执行自定义行为。
  • 无需获取不使用字段
    - 通过 values() 和 values_list() 可以提高查询速度
    - defer()排除字段和only()指定字段
    - 请注意,如果确实使用它们,ORM将不得不在一个单独的查询中获取它们,如果您不恰当地使用它们,那么这将是一个悲观化。
优化: 减少查询次数
Book.objects.raw('select * from Book')  # 用RawSQL
book = Book.objects.get(id=27)          # 主键索引更快
book = Book.objects.get(name__startswith='将')  # 字段模糊查询会更慢
  • select_relatedinner join、主要针查询外键(ForeignKey)或一对一(OneToOneField)关系进行优化。通过减少SQL查询的次数来进行优化、提高性能。
  • prefetch_related 执行一个单独的查找,它允许预先读取多对多和多对一的对象数据,这是 select_related 做不到的。另外 perfetch_related 也可以与通用外键和关系一起使用。
blog = Blog.objects.get(id=124)   # 访问一次数据库
author = blog.author   # 再次访问数据库

blog = Blog.objects.select_related('author').get(id=124)   # 访问数据库
author = blog.author  # 不会再次访问数据库,在之前的查询中已经取到了对应的数据

单纯只要外键值:

blog = Blog.objects.get(id=2)
author_id = blog.author.id    # Bad query, additional db lookup in author table

blog = Blog.objects.select_related('author').get(id=2)
author_id = blog.author.id  # A better version of above query but still a bad query

blog = Blog.objects.get(id=2)
author_id = blog.author_id   # Good Query 最佳实践

MTM: add() / remove() / update() / delete() / clear() 方法

1、为对象添加多对多关系前,需要保持对象,即写入数据库产生id列,然后才能利用id列建立多对多关系表。
2、可以用create()函数,连建立to_*_id代表的目标对象,带add()新建对象到多对多关系表一起,一步完成两个操作。
3、.distinct().count()组合使用

au.blogs.all().delete()  # 需指出类似all()的查询集。
au.blogs.clear()

.add(obj1[, obj2, ...])  # 不需要再调用save(), 多次添加同一个关系不会重复。但参数类型不能错,否则引发TypeError

.remove(obj1[, obj2, ...]) # 解绑指定关系。 只删除关系,保留源侧和目标侧对象。
.set(queryset)   # 批量设多个关系

.clear()  # 不会删除对象,只会清除他们之间的关系
.delete() # 删除单个或批量删除对象。  级联删除,相关联的一对多,多对多关系都会删除。 【任何有外键指向要删除对象的对象将一起被删除】
          # 该方法将返回被删除对象的总数量和一个字典,字典包含每种被删除对象的类型和该类型的数量。

Article.objects.filter(publications__title__startswith="Science")

Article.objects.filter(publications__id=1) 
Article.objects.filter(publications__pk=1)
Article.objects.filter(publications=1)   # (publications__in=[1,2])
Article.objects.filter(publications=p1)  # (publications__in=[p1,p2])

性能分析

方法一: code

from django.db import connection
dbsql = connection.queries # 具体sql执行情况和耗时情况

方法二: pip install django-extensions

- INSTALLED_APPS = (
    ...
    'django_extensions',
    ...
 )
- python manage.py shell_plus --print-sql     # 查看执行sql和结果

参考文献:

Django ORM数据查询操作优化

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值