Django orm高级用法以及查询优化

基础查询

python 复制代码
#全表查询
Author.objects.all()
#<QuerySet [<Author: Author object (4)>, <Author: Author object (1)>, <Author: Author object (10)>, <Author: Author object (8)>, <Author: Author object (5)>, <Author: Author object (6)>, <Author: Author object (2)>, <Author: Author object (9)>, <Author: Author object (7)>]>

#查出数据库前三条数据
Author.objects.all().order_by('id')[:3]

#查询某个字段 数据以列表形式返回,列表元素以字典表示
v=Author.objects.values('name')
#<QuerySet [{'name': '吴承恩'}, {'name': '孙咯'}, {'name': '孙权'}, {'name': '张三'}, {'name': '李四'}, {'name': '林黛玉'}, {'name': '赵咯'}, {'name': '赵武'}]>

#查询某个字段 数据以列表形式返回,列表元素以元组表示
v=Author.objects.values_list('name')
#<QuerySet [('吴承恩',), ('孙咯',), ('孙权',), ('张三',), ('李四',), ('林黛玉',), ('赵咯',), ('赵武',)]>

#查询特定数据get和filter
v=Author.objects.get(id=2)
#<Author: Author object (2)>
v=Author.objects.filter(id=2)
#[<Author: Author object (2)>]

#相当于SQL的and查询
v=Author.objects.filter(id=2,name='林黛玉')
#<QuerySet [<Author: Author object (2)>]>
v=Author.objects.filter(id=2).first()
#<Author: Author object (2)>

#SQL中的or查询需要引入Q
from django.db.models import Q
v=Author.objects.filter(Q(id=2)|Q(name='李四'))
#<QuerySet [<Author: Author object (6)>, <Author: Author object (2)>]>

#实现不等于查询,在Q前面加一个~,或者使用exclude
Author.objects.filter(~Q(id=2))
Author.objects.exclude(id=2) 
#<QuerySet [<Author: Author object (4)>, <Author: Author object (1)>, <Author: Author object (10)>, <Author: Author object (8)>, <Author: Author object (5)>, <Author: Author object (6)>, <Author: Author object (9)>, <Author: Author object (7)>]>

#统计数量
Author.objects.exclude(id=2).count()

#去重查询
v=Author.objects.values_list('name').distinct()

#聚合查询annotate aggregate
#annotate相当于SQL中的group by
#SQL:select job,SUM(id) as id__sum from index_vocation group by job
Author.objects.values('name').annotate(Sum('id')) 
Author.objects.values('name').annotate(id__sum=Sum('id'))
#<QuerySet [{'name': '吴承恩', 'id__sum': Decimal('1')}, {'name': '林黛玉', 'id__sum': Decimal('2')}, {'name': '何玉梅', 'id__sum': Decimal('4')}, {'name': '张三', 'id__sum': Decimal('5')}, {'name': '李四', 'id__sum': Decimal('6')}, {'name': '赵武', 'id__sum': Decimal('7')}, {'name': '孙权', 'id__sum': Decimal('8')}, {'name': '赵咯', 'id__sum': Decimal('9')}, {'name': '孙咯', 'id__sum': Decimal('10')}]>

#aggregate 计算某个字段的值并返回结果
#SQL:select count(id) as id id_count from author
Author.objects.aggregate(id_count=Count('id'))
#{'id_count': 9}

# union、intersection 和 difference 语法
# 每次查询结果的字段必须相同
# 第一次查询结果 v1
vl = Vocation.objects.filter(payment__gt=9000)
<QuerySet [<Vocation: 1>, <Vocation: 5>]>
# 第二次查询结果 v2
v2 = Vocation.objects.filter(payment__gt=5000)
<QuerySet [<Vocation: 1>,<Vocation: 3>,<Vocation: 4>,<Vocation: 5>]>
#使用SQL的UNION 来组合两个或多个查询结果的并集#获取两次查询结果的并集
vl.union(v2)
<QuerySet [<Vocation: 1>,<Vocation: 3>,<Vocation: 5>]>
#使用SQL的NTERSECT来获取两个或多个查询结果的交集#获取两次查询结果的交集
vl.intersection(v2)
<QuerySet [<Vocation: 1>, <Vocation: 5>]>?
#使用 SQL的EXCEPT来获取两个或多个查询结果的差
#以v2为目标数据,去除 v1和v2的共同数据
 v2.difference(v1)
<QuerySet [<Vocation: 3>, <Vocation: 4>]>

orm高级用法

创建表:

python 复制代码
# models.py
from django.db import models

class Book(models.Model):
    id = models.AutoField(primary_key=True) # 自增,主键
    title = models.CharField(verbose_name='书名', max_length=32)
    price = models.DecimalField(verbose_name='价格', max_digits=8, decimal_places=2)
    publish_date = models.DateField(verbose_name='出版日期',auto_now_add=True) # 年月日类型
     # 阅读数
    # reat_num=models.IntegerField(default=0)
    # 评论数
    # commit_num=models.IntegerField(default=0)
    # 建议加引号,也可以不加引号
    #models.CASCADE:级联删除,设为默认值,设为空,设为指定的值,不做处理
    # 2.x以后必须加on_dekete,否则报错
    # publish = models.ForeignKey(to=Publish,to_field='id',on_delete=models.CASCADE)  
     # 一对多 外键字段建在多的一方
    publish = models.ForeignKey(to='Publish',to_field='id',on_delete=models.CASCADE)
    
    # 多对多 必须建立第三张表(orm中,可以用一个字段表示,在数据库中,根本没有这个字段,
    # orm用来查中介模型询,映射成一个表了,如果我不这么写,则需要手动建立第三张表,)
    authors = models.ManyToManyField(to='Author')
        
     def __str__(self):
        return self.title

class Publish(models.Model):
    id = models.AutoField(primary_key=True)
    title = models.CharField(verbose_name='名称',max_length=32)
    addr = models.CharField(verbose_name='地址',max_length=128)
    email = models.EmailField(verbose_name='邮箱') # 本质是varchar类型


class Author(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(verbose_name='姓名',max_length=32)
    age = models.IntegerField(verbose_name='年龄')
    # 一对一 外键字段推荐建在查询频率较高的表中
    author_detail = models.OneToOneField(to='AuthorDetail',to_field='id',unique=True,on_delete=models.CASCADE)


class AuthorDetail(models.Model):
    id = models.AutoField(primary_key=True)
    phone = models.BigIntegerField(verbose_name='电话')
    addr = models.CharField(verbose_name='地址',max_length=32)

聚合查询

聚合函数,Sum,Max,Min,Count,Avg

总数,最大值,最小值,数量,平均值

把聚合结果字段重命名

res=models.Book.objects.all().aggregate(aaa=Sum('price'))

aggregate()

aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。

python 复制代码
# 聚合查询
    from django.db.models import Sum,Avg,Max,Min,Count
# 计算所有图书的平均价格
     res=models.Book.objects.all().aggregate(Avg('price'))
    print(res)
# 计算所有图书的最高价格
     res=models.Book.objects.all().aggregate(Max('price'))
    print(res)
# 计算所有图书的总价格
     res=models.Book.objects.all().aggregate(Sum('price'))
    print(res)
# pmj出版图书的总价格
    res = models.Book.objects.filter(authors__name='pmj').aggregate(Sum('price'))
    print(res)
# 北京的出版社出版的书的最高价格
     res = models.Book.objects.filter(publish__addr='北京').aggregate(Sum('price'))
    print(res)
# 计算所有图书的总价格
   res=models.Book.objects.all().aggregate(book_sum=Sum('price'),book_avg=Avg('price'))
   print(res)

分组查询

annotate()

annotate()为调用的QuerySet中每一个对象都生成一个独立的统计值(统计方法用聚合函数,所以使用前要先从 django.db.models 引入 Avg,Max,Min,Count,Sum(首字母大写))。

返回值:

  • 分组后,用 values 取值,则返回值是 QuerySet 数据类型里面为一个个字典;
  • 分组后,用 values_list 取值,则返回值是 QuerySet 数据类型里面为一个个元组。

MySQL 中的 limit 相当于 ORM 中的 QuerySet 数据类型的切片。

注意:

annotate ()里面放聚合函数。

  • values或者 values_list 放在 annotate 前面:values 或者 values_list 是声明以什么字段分组,annotate 执行分组。
  • values或者 values_list 放在 annotate 后面:** **annotate****表示直接以当前表的pk执行分组,values 或者 values_list 表示查询哪些字段, 并且要将 annotate 里的聚合函数起别名,在 values 或者 values_list 里写其别名。
  • filter放在 annotate 前面: 表示where条件
  • filter放在annotate后面: 表示having

总结 :跨表分组查询本质就是将关联表join成一张表,再按单表的思路进行分组查询。

python 复制代码
import os

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day53.settings")
if __name__ == '__main__':
    import django

    django.setup()

    from app01 import models

  # 查询每一个出版社id,以及出书平均价格(单表)
    # 原生sql
    # select publish_id,avg(price) from book group by publish_id;
    # orm实现
    '''标准 annotate() 内写聚合函数
    values在前,表示group by 的字段
    values在后,表示取字段
    filter在前,表示where条件
    filter在后,表示having
    '''
    from django.db.models import Avg,Count,Max
  # 查询每一个出版社id,以及出书平均价格(单表)
    res = models.Book.objects.all().values('publish_id').annotate(price_ave=Avg('price')).values('publish_id','price_ave')
    print(res)

  # 查询出版社id大于1的出版社id,以及出书平均价格
    res = models.Book.objects.values('publish_id').filter(publish_id__gt=1).annotate(price_ave=Avg('price')).values('publish_id','price_ave')
    print(res)

   # 查询出版社id大于1的出版社id,以及出书平均价格大于30的
    res = models.Book.objects.values('publish_id').filter(publish_id__gt=1).annotate(price_ave=Avg('price')).filter(price_ave__gt=60).values('publish_id','price_ave')
    print(res)

   # 查询每一个出版社出版的名称和书籍个数(连表)
   # 联表的话最好以group by的表作为基表
    res = models.Publish.objects.values('nid').annotate(book_count=Count('book__nid')).values('name','book_count')
    # 简写成,如果基表是group by的表,就可以不写values
    res=models.Publish.objects.annotate(book_count=Count('book')).values('name','book_count')

    # 以book为基表
     res = models.Book.objects.values('publish__nid').annotate(book_count=Count('nid')).values('publish__name','book_count')
    print(res)

    # 查询每个作者的名字,以及出版过书籍的最高价格(建议使用分组的表作为基表)
    # 多对多如果不以分组表作为基表,可能会出数据问题
    res = models.Author.objects.annotate(price_max=Max('book__price')).values('name','price_max')

    res = models.Book.objects.values('authors__nid').annotate(price_max=Max('price')).values('authors__name','price_max')
    print(res)

    # 查询每一个书籍的名称,以及对应的作者个数
    res=models.Book.objects.annotate(count=Count('authors')).values('name','count')
    print(res)

    # 统计不止一个作者的图书
     ret = models.Author.objects.values('book__id').annotate(count=Count('id')).filter(count__gt=1).values('book__name', 'count')
    print(ret)
    # 统计价格数大于10元,作者的图书
    ret = models.Book.objects.values('pk').filter(price__gt=10).annotate(count=Count('authors__id')).values('name',
'count')
    print(ret)
    #统计价格数大于10元,作者个数大于1的图书
    res = models.Book.objects.filter(price__gt=10).annotate(count=Count('authors')).filter(count__gt=1).values('name','price','count')
    print(res)

annotate()高级用法

  1. F() : 使用字段的值参与计算。F()对象允许在查询中引用模型字段的值,而不仅仅是其名称。这对于在聚合函数中进行字段间的运算非常有用。

    python 复制代码
    from django.db.models import F
    
    queryset.annotate(new_stock=F('stock') + F('incoming_stock'))
  2. ExpressionWrapper(): 创建一个自定义的表达式,并指定其输出字段类型。常用于组合多个表达式或调整现有表达式的输出类型。

    python 复制代码
    from django.db.models import ExpressionWrapper, FloatField
    
    queryset.annotate(
       discount_price=ExpressionWrapper(
            F('original_price') * (1 - F('discount_percentage') / 100),
            output_field=FloatField()
        )
    )
  3. Case()When()Value()Q() : 构造复杂的条件判断和基于条件的计算。Case()用于创建一个分支结构,When()用于定义一个条件分支及其结果值,Value()用于插入一个常数值,Q()用于构建布尔表达式。

    python 复制代码
    from django.db.models import Case, When, Value, Q
    
    queryset.annotate(
        status_label=Case(
            When(Q(status='active') & Q(date_expired__gte=datetime.now()), then=Value('Active')),
            When(Q(status='inactive'), then=Value('Inactive')),
            default=Value('Unknown'),
            output_field=CharField()
        )
    )

F查询

在上面所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?

Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。

F查询
当查询条件的左右两边数据都来自于表中则可以使用F查询
F查询甚至还可以统一修改表中字段数据

针对字符串稍微复杂一点需要再导入两个模块
from django.db.models import F,Value
from django.db.models.functions import Concat

Go 复制代码
# F 查询,取出某个字段对应的值
    from django.db.models import F
  # 查询评论数大于阅读数的书籍
    res=models.Book.objects.filter(commit_num__gt=F('read_num'))
    print(res)

  # 把所有图书价格+1
    res=models.Book.objects.all().update(price=F('price')+1)
    print(res) # 影响的行数
    
  # 把egon出版的所有图书价格加10
    res = models.Book.objects.filter(authors__name='egon').update(price=F('price')+1)
    print(res)
    
        # 将所有书的后面加上爆款两个字
    from django.db.models.functions import Concat
    from django.db.models import Value
    # res = models.Book.objects.update(title = Concat(F('title'),Value('爆款')))

Q查询

filter()等方法中的关键字参数查询都是一起进行"AND" 的。 如果你需要执行更复杂的查询(例如OR 语句),你可以使用Q 对象。

Q查询
1.filter默认只支持and连接查询条件如果需要修改则需要使用Q
导入模块:from django.db.models import Q
filter(Q(),Q()) # and
filter(Q()|Q()) # or
filter(Q(),Q()) # not
2.查询条件还可以由变量名模式改为字符串形式
q = Q()
q.connector='or'
q.children.append(('字段名','条件'))
res = models.Book.objects.filter(q)

python 复制代码
# 1、 Q查询:构造出  与 &    或 |   非 ~
    from django.db.models import Q

   # 查询名字叫红楼梦或者价格大于100的书
    res=models.Book.objects.filter(Q(name='红楼梦')|Q(price__gt=100))
    res=models.Book.objects.filter(Q(name='红楼梦')& Q(price__gt=100))

   # 查询名字不是红楼梦的书
    res=models.Book.objects.filter(~Q(name='红楼梦'))
    
   # 查询名字不是红楼梦,并且价格大于100的书
    # res = models.Book.objects.filter(~Q(name='红楼梦'),price__gt='100')
    res = models.Book.objects.filter(~Q(name='红楼梦')&Q(price__gt='100'))
    print(res)
    
    
    2、进阶操作:
      # condition = input('请输入你需要按照什么字段查询数据>>>:')+'__contains'
    # data = input('请输入你需要查询的数据名称>>>:')
    # q = Q()
    # q.children.append((condition,data))
    # # print(q,type(q))
    # res = models.Book.objects.filter(q)
    # print(res)

orm查询优化

什么是QuerySet

QuerySet是Django提供的强大的数据库接口(API)。正是因为通过它,我们可以使用filter, exclude, get等方法进行数据库查询,而不需要使用原始的SQL语言与数据库进行交互。从数据库中查询出来的结果一般是一个集合,这个集合叫就做 queryset。

Django的QuerySet是惰性的。

下例中article_list试图从数据库查询一个标题含有django的全部文章列表。

python 复制代码
article_list = Article.objects.filter(title__contains="django")

但是当我们定义article_list的时候,Django的数据接口QuerySet并没有对数据库进行任何查询。无论你加多少过滤条件,Django都不会对数据库进行查询。只有当你需要对article_list做进一步运算时(比如打印出查询结果,判断是否存在,统计查询结果长度),Django才会真正执行对数据库的查询(见下例1)。这个过程被称为queryset的执行(evaluation)。Django这样设计的本意是尽量减少对数据库的无效操作,比如查询了结果而不用是计算资源的很大浪费。

python 复制代码
# example 1
for article in article_list:
    print(article.title)
    

Django的QuerySet自带缓存(Cache)

在例1中,当你遍历queryset(article_list)时,所有匹配的记录会从数据库获取。这些结果会载入内存并保存在queryset内置的cache中。这样如果你再次遍历或读取这个article_list时,Django就不需要重复查询了,这样也可以减少对数据库的查询。

下例中例2比例3要好,因为在你打印文章标题后,Django不仅执行了查询,还把查询到的article_list放在了缓存里。这个article_list是可以复用的。例3就不行了。

python 复制代码
# Example 2: Good
article_list = Article.objects.filter(title__contains="django")
for article in article_list:
    print(article.title)
 
# Example 3: Bad
for article in Article.objects.filter(title__contains="django"):
    print(article.title)

用if也会导致queryset的执行

不知道你注意到上述例2中有个问题没有?万一article_list是个空数据集呢? 虽然for....in...用到空集合上也不会出现raise什么错误,但专业优秀的我们怎么能允许这样的低级事情发生呢?最好的做法就是在loop前加个if判断(例4)。因为django会对执行过的queryset进行缓存(if也会导致queryset执行, 缓存article_list),所以我们在遍历article_list时不用担心Django会对数据库进行二次查询。

python 复制代码
# Example 4: Good
article_list = Article.objects.filter(title__contains="django")
if article_list:
    for article in article_list:
        print(article.title)
else:
    print("No records")

但有时我们只希望了解查询的结果是否存在,而不需要使用整个数据集,这时if触发整个queryset的缓存变成了一件坏事情。哎,程序员要担心的事情着不少。这时你可以用exists()方法。与if判断不同,exists只会检查查询结果是否存在,返回True或False,而不会缓存article_list(见例5)。

python 复制代码
# Example 5: Good
article_list = Article.objects.filter(title__contains="django")
if article_list.exists():
    print("Records found.")
else:
    print("No records")

注意: 判断查询结果是否存在到底用if还是exists取决于你是否希望缓存查询数据集复用,如果是用if,反之用exists。

统计查询结果数量优选count方法

len()与count()均能统计查询结果的数量。一般来说count更快,因为它是从数据库层面直接获取查询结果的数量,而不是返回整个数据集,而len会导致queryset的执行,需要将整个queryset载入内存后才能统计其长度。但事情也没有绝对,如果数据集queryset已经在缓存里了,使用len更快,因为它不需要跟数据库再次打交道。

下面三个例子中,只有例7最差,尽量不要用。

python 复制代码
# Example 6: Good
count = Article.objects.filter(title__contains="django").count()
 
# Example 7:Bad
count = Article.objects.filter(title__contains="django").len()
 
# Example 8: Good
article_list = Article.objects.filter(title__contains="django")
if article_list:
    print("{} records found.".format(article_list.len()))

当queryset非常大时,数据请按需去取

当查询到的queryset的非常大时,会大量占用内存(缓存)。我们可以使用values和value_list方法按需提取数据。比如例1中我们只需要打印文章标题,这时我们完全没有必要把每篇文章对象的全部信息都提取出来载入到内存中。我们可以做如下改进(例9)。

python 复制代码
# Example 9: Good
article_list = Article.objects.filter(title__contains="django").values('title')
if article_list:
    print(article.title)
 
 
article_list = Article.objects.filter(title__contains="django").values_list('id', 'title')
if article_list:
    print(article.title)

更新数据库部分字段请用update方法

如果需要对数据库中的某条已有数据或某些字段进行更新,更好的方式是用update,而不是save方法。我们现在可以对比下面两个案例。例10中需要把整个Article对象的数据(标题,正文.....)先提取出来,缓存到内存中,变更信息后再写入数据库。而例11直接对标题做了更新,不需要把整个文章对象的数据载入内存,显然更高效。尽管单篇文章占用内存不多,但是万一用户非常多呢,那么占用的内存加起来也是很恐怖的。

python 复制代码
# Example 10: Bad
article = Article.objects.get(id=10)
Article.title = "Django"
article.save()
 
# Example 11: Good
Article.objects.filter(id=10).update(title='Django')

only与defer

python 复制代码
only会把括号内字段对应的值,封装到查询返回的对象中,通过对象点括号字段,不需要再走数据库查询,直接返回结果,一旦你点了不是括号内的字段 就会频繁的去走数据库查询

# 惰性查询
	用不到的数据即使写了orm语句也不会执行

     res = models.Book.objects.values('title')  # 列表套字典

     res1 = models.Book.objects.only('title')  # 列表套对象
    # print(res1)
     for i in res1:
         # print(i.title)
         print(i.price)
    """
    only括号内写什么字段
        生成的对象就含有对应的属性 在查找该属性的时候不再走数据库查询
        但是一旦查找括号内没有的字段属性 则每次都会走数据库查询

    """
    # res1 = models.Book.objects.defer('title')
    # # print(res1)  # 列表套对象
    # for i in res1:
    #     # print(i.title)
    #     print(i.title)
    """
    defer与only刚好相反
        生成的对象就不含有对应的属性 在查找该属性的时候需要每次走数据库查询
        但是一旦查找括号内没有的字段属性 则不需要走数据库查询
    总结:
        和 only相反,defer会将括号内的字段排除之外将其他字段对应的值, 直接封装到返回给你的对象中, 点其他字段 不需要再走数据库查询,一旦你点了括号内的字段就会有多少值,就会查询几次

Django ORM 中的 select_related()prefetch_related() 都是用来优化查询效率,减少数据库查询次数的工具,它们的主要区别在于处理关联数据的方式和适用场景。下面通过具体例子来阐述两者的不同:

1. select_related()

select_related() 主要针对一对一(OneToOneField)和一对多(ForeignKey)关系。当查询一个模型对象时,如果知道后续会访问其关联对象,可以使用 select_related() 将关联对象的查询合并到原始查询中,通过 SQL 内联(INNER JOIN)操作一次性获取所有所需数据。这样可以避免对每个主对象单独发起查询来获取关联对象,显著减少数据库查询次数。

示例 : 假设有一个 BlogPost 模型,它有一个外键 author 指向 Author 模型:

python 复制代码
class Author(models.Model):
    name = models.CharField(max_length=100)

class BlogPost(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)

如果我们想获取所有博客文章及其作者信息,不使用 select_related() 的情况下可能会这样查询:

python 复制代码
posts = BlogPost.objects.all()

for post in posts:
    print(post.title, post.author.name)

这样每次循环访问 post.author.name 时,都会触发一次数据库查询来获取作者信息。为了避免这种 N+1 查询问题,可以使用 select_related()

python 复制代码
posts = BlogPost.objects.select_related('author')

for post in posts:
    print(post.title, post.author.name)

此时,Django 会构造一条 SQL 查询,通过 INNER JOIN 把 Author 表的信息与 BlogPost 表一起查询出来,一次查询即可得到所有博客文章及其作者的完整数据。

2. prefetch_related()

prefetch_related() 主要用于处理多对多(ManyToManyField)关系以及其他类型的多对一(例如,一对一反向引用或一对多的多方向)关系。由于多对多关系可能导致 JOIN 后的数据集过于庞大,prefetch_related() 不使用 SQL JOIN,而是执行额外的查询来获取关联对象,然后在 Django 内存中完成对象之间的关联。这意味着它会在数据库层面分别查询主对象和关联对象,然后在 Python 环境中进行"连接",通常使用的是 Django 的 Prefetch 对象来更精细地控制关联数据的加载。

示例 : 假设 BlogPost 模型有一个多对多字段 tags,关联到 Tag 模型:

python 复制代码
class Tag(models.Model):
    name = models.CharField(max_length=50)

class BlogPost(models.Model):
    title = models.CharField(max_length=200)
    tags = models.ManyToManyField(Tag)

如果我们想获取所有博客文章及其标签列表,不使用 prefetch_related() 的情况下可能会这样查询:

python 复制代码
posts = BlogPost.objects.all()

for post in posts:
    for tag in post.tags.all():
        print(post.title, tag.name)

这样每次循环访问 post.tags.all() 时,都会触发一次数据库查询来获取文章的所有标签。为了避免这种 N+1 查询问题,可以使用 prefetch_related()

python 复制代码
posts = BlogPost.objects.prefetch_related('tags')

for post in posts:
    for tag in post.tags.all():
        print(post.title, tag.name)

posts = BlogPost.objects.prefetch_related('tags') 这一行代码表示对 BlogPost 模型的所有对象进行查询,并预先加载它们与 tags 多对多字段关联的所有 Tag 对象。Django 在执行这个查询时会遵循以下步骤:

  1. 查询所有博客文章 : Django 首先执行一条 SQL 查询,从数据库中检索所有 BlogPost 的记录。这一步与没有使用 prefetch_related() 时获取博客文章列表是一样的。

  2. 执行另一个查询获取所有相关的标签 : 在获取完所有博客文章之后,Django 会执行另一条 SQL 查询,用于检索与这些博客文章关联的所有 Tag 对象。这条查询通常会包含 IN 子句,其中包含先前查询出的所有博客文章的主键(通常是 id 字段)。例如,查询可能类似如下:

    python 复制代码
    SELECT * FROM tag WHERE id IN (1, 2, 3, ...);

    这个查询会返回所有与指定博客文章关联的标签数据。

  3. 在内存中将标签与对应的博客文章关联起来 : 获取到所有博客文章和相关标签的数据库记录后,Django 不会在数据库层面进行任何进一步的 JOIN 操作。相反,它会在 Python 环境中(即内存中)根据数据库返回的数据构建关联关系。具体来说,Django 会将每个 Tag 对象与正确的 BlogPost 对象关联起来,这样当你遍历 post.tags.all() 时,尽管看起来像是在访问数据库,但实际上 Django 已经在内存中为你准备好了所有关联的标签数据,无需再向数据库发送查询请求。

    因此,在接下来的循环中:

    python 复制代码
    for post in posts:
        for tag in post.tags.all():
            print(post.title, tag.name)

    即使遍历每个 post 的所有 tags,也不会触发额外的数据库查询。这是因为 post.tags.all() 返回的是一个已预加载的查询集,其数据已经在第一步和第二步中从数据库加载到内存,并在第三步中完成了与对应博客文章的关联。

参考文章:[Django框架之ORM操作:多表查询,聚合查询、分组查询、F查询、Q查询、choices参数] - 刘较瘦丫 - 博客园 (cnblogs.com)

Django基础(13): QuerySet特性及高级使用技巧,如何减少数据库的访问,节省内存,提升网站性能_django queryset怎么不缓存-CSDN博客

相关推荐
LuiChun16 小时前
Django 模板分割及多语言支持案例【需求文档】-->【实现方案】
数据库·django·sqlite
凡人的AI工具箱16 小时前
每天40分玩转Django:Django管理界面
开发语言·数据库·后端·python·django
中科院提名者16 小时前
Django连接mysql数据库报错ModuleNotFoundError: No module named ‘MySQLdb‘
数据库·mysql·django
碧水澜庭16 小时前
django中cookie与session的使用
python·django
鬼义II虎神17 小时前
将Minio设置为Django的默认Storage(django-storages)
python·django·minio·django-storages
大霸王龙18 小时前
在 Django 中使用 SMTP 发送邮件是一个常见的需求
数据库·django·sqlite
凡人的AI工具箱1 天前
每天40分玩转Django:Django测试
数据库·人工智能·后端·python·django·sqlite
qyq11 天前
Django框架与ORM框架
后端·python·django
LuiChun2 天前
Django 中的 reverse 【反向/逆转/扭转/逆向】使用详解以及使用案例
数据库·django·sqlite
江上挽风&sty2 天前
【Django篇】--创建第一个Django项目
后端·python·django