Django框架学习——10—(QuerySet源码、QuerySet的方法、将QuerySet转换为SQL去执行、ORM模型练习)

1、QuerySet源码

我们通常做查询操作的时候,都是通过模型名字.objects的方式进行操作。其实模型名字.objects是一个django.db.models.manager.Manager对象,而Manager这个类是一个“空壳”的类,他本身是没有任何的属性和方法的。他的方法全部都是通过Python动态添加的方式,从QuerySet类中拷贝过来的
在这里插入图片描述
所以我们如果想要学习ORM模型的查找操作,首先要学会QuerySet上的一些API的使用。

def index(request):
    from django.db.models.manager import Manager
    print(Book.objects)
    print(type(Book.objects))
    return HttpResponse("index")


""" type 源码
    type(object_or_name, bases, dict)   # object_or_name类名, bases继承的父类,dict类方法或者静态方法
    type(object) -> the object's type
    type(name, bases, dict) -> a new type

# 可以创建类,也可以打印数据类型
"""

2、QuerySet的方法

在使用QuerySet进行查找操作的时候,可以提供多种操作。比如过滤完后还要根据某个字段进行排序,那么这一系列的操作我们可以通过一个非常流畅的链式调用的方式进行。比如要从文章表中获取标题为123,并且提取后要将结果根据发布的时间进行排序,那么可以使用以下方式来完成。

articles = Article.objects.filter(title='123').order_by('create_time')

可以看到order_by方法是直接在filter执行后调用的。这说明filter返回的对象是一个拥有order_by方法的对象。而这个对象正是一个新的QuerySet对象。因此可以使用order_by方法。

那么以下将介绍在那些会返回新的QuerySet对象的方法。

  • 1.filter:将满足条件的数据提取出来,返回一个新的QuerySet
  • 2.exclude:排除满足条件的数据,返回一个新的QuerySet
def exclude_func(request):
    # 提取那些标题不包含`hello`的图书
    book = Book.objects.exclude(name__contains='hello')

    # book = Book.objects.filter(name__icontains='hello')
    print(book)  
    print(book.query)   
    return HttpResponse("exclude_func")
  • 3.annotate:给QuerySet中的每个对象都添加一个使用查询表达式(聚合函数、F表达式、Q表达式、Func表达式等)的新字段.(F表达式暂时理解为提取某个字段的值)
将在每个对象中都添加一个`author__name`的字段,用来显示这个文章的作者的年龄
articles = Article.objects.annotate(author_name=F("author__name"))
  • 4.order_by:指定将查询的结果根据某个字段进行排序。如果要倒叙排序,那么可以在这个字段的前面加一个负号.
 # 根据创建的时间正序排序(从小到大,默认排序规则)
 articles = Article.objects.order_by("create_time")

 # 根据创建的时间倒序排序
 articles = Article.objects.order_by("-create_time")

 # 根据作者的名字进行排序
 articles = Article.objects.order_by("author__name")

 # 首先根据创建的时间进行排序,如果时间相同,则根据作者的名字进行排序
 articles = Article.objects.order_by("create_time",'author__name')

 # 根据图书订单的评分来排序
articles = BookOrder.objects.order_by("book__rating")
  • 5.values:用来指定在提取数据出来,需要提取哪些字段。默认情况下会把表中所有的字段全部都提取出来,可以使用values来进行指定,并且使用了values方法后,提取出的QuerySet中的数据类型不是模型,而是在values方法中指定的字段和值形成的字典.
def values__func(request):
    books = Book.objects.values('id', 'name')
    print(books.query)
    print(type(books))                            # <class 'django.db.models.query.QuerySet'>
    
    print(books)                                  # 列表中包含字典
    # <QuerySet [{'id': 1, 'name': '三国演义'},
    # {'id': 2, 'name': '水浒传'}, {'id': 3, 'name': '西游记'}, {'id': 4, 'name': '红楼梦'}]>
    
    for book in books:                            # book为字典
        print(book.get('id'), book.get('name'))
    
    return HttpResponse("values__func")

  • 6.values_list:类似于values。只不过返回的QuerySet中,存储的不是字典,而是元组
def values_list__func(request):
    books = Book.objects.values_list('id', 'name')
    print(books.query)
    print(type(books))  # <class 'django.db.models.query.QuerySet'>
    
    print(books)   # <QuerySet [(1, '三国演义'), (2, '水浒传'), (3, '西游记'), (4, '红楼梦')]>
    
    for book in books:                             # 'tuple' object has no attribute 'get', book为元祖
        print(book[0], book[1])
    
    return HttpResponse("values_list__func")
  • 7.all:获取这个ORM模型的QuerySet对象。
  • 8.select_related:在提取某个模型的数据的同时,也提前将相关联的数据提取出来。比如提取文章数据,可以使用select_related将author信息提取出来,以后再次使用article.author的时候就不需要再次去访问数据库了。可以减少数据库查询的次数。(查询效率高)
article = Article.objects.get(pk=1)
>> article.author # 重新执行一次查询语句
article = Article.objects.select_related("author").get(pk=2)
>> article.author # 不需要重新执行查询语句了

selected_related只能用在一对多或者一对一中,不能用在多对多或者多对一中。
比如可以提前获取文章的作者,但是不能通过作者获取这个作者的文章,或者是通过某篇文章获取这个文章所有的标签

  • 9.prefetch_related:这个方法和select_related非常的类似,就是在访问多个表中的数据的时候,减少查询的次数。这个方法是为了解决多对一和多对多的关系的查询问题。比如要获取标题中带有hello字符串的文章以及他的所有标签。
from django.db import connection
articles = Article.objects.prefetch_related("tag_set").filter(title__contains='hello')
print(articles.query) # 通过这条命令查看在底层的SQL语句
for article in articles:
     print("title:",article.title)
     print(article.tag_set.all())
  • 10.create:创建一条数据,并且保存到数据库中。这个方法相当于先用指定的模型创建一个对象,然后再调用这个对象的save方法
def create_func(request):
    # 之前常用的创建对象方式
    user = Author(name='zzz', age=18, email="123@qq.com")
    user.save()
    
    # 利用create创建对象方式,这行代码相当于以上两行代码 
    user = Author.objects.create(name='yyy', age=0, email="456@qq.com")
    
    return HttpResponse("create_func")

  • 11.get_or_create:根据某个条件进行查找,如果找到了那么就返回这条数据,如果没有查找到,那么就创建一个。
def get_or_create(request):
    # 根据某个条件进行查找对象,如果找到了那么就返回这条数据,如果没有查找到,那么就创建这个对象
    obj, created = Author.objects.get_or_create(name="xxx", age=11)
    print(obj)                             # Author object (7)
    print(type(obj))                       # <class 'book.models.Author'>
    print(created)                         # True  未查询到时创建新对象时为真,能够查询出来的对象为False
    print(type(created))                   # <class 'bool'>
    return HttpResponse("get_or_create")

如果有标题等于默认分类的分类,那么就会查找出来,如果没有,则会创建并且存储到数据库中。
这个方法的返回值是一个元组,元组的第一个参数obj是这个对象,第二个参数created代表是否创建的。

  • 12.exists:判断某个条件的数据是否存在。如果要判断某个条件的元素是否存在,那么建议使用exists,这比使用count或者直接判断QuerySet更有效得多。
def exists_func(request):
    # user = Author.objects.filter(name__contains='xxx')
    # user = Author.objects.filter(name__in=['xxx'])
    # user = Author.objects.filter(name='xxx')
    user = Author.objects.filter(name__contains='xxx').count()    # 计数大于0就说明数据存在
    print(user)
    if user:
        print("存在")
        
    # exists执行效率更高,bool值
    if Author.objects.filter(name__contains='xxx').exists():
        print("cunzai")
    return HttpResponse("exists_func")
  • 13.update:执行更新操作,在SQL底层走的也是update命令。比如要将所有category为空的article的article字段都更新为默认的分类.
def update_func(request):
    # 将图书价格更新为100
    book = Book.objects.filter(name="三国演义").update(price=100)
    '''
    注意这个方法走的是更新的逻辑。所以更新完成后保存到数据库中不会执行save方法,因此不会更新auto_now设置的字段
    '''
    # F表达式    所有图书价格 +5
    book = Book.objects.update(price=F('price')+5)
    
    return HttpResponse("update_func")

注意这个方法走的是更新的逻辑。所以更新完成后保存到数据库中不会执行save方法,因此不会更新auto_now设置的字段.

  • 14.切片操作:有时候我们查找数据,有可能只需要其中的一部分。
books = Book.objects.all()[1:3]
for book in books:
    print(book)

切片操作并不是把所有数据从数据库中提取出来再做切片操作。而是在数据库层面使用LIMIE和OFFSET来帮我们完成 (分页操作)。所以如果只需要取其中一部分的数据的时候,建议大家使用切片操作。

3、将QuerySet转换为SQL去执行

生成一个QuerySet对象并不会马上转换为SQL语句去执行

from django.db import connection
books = Book.objects.all()
print(connection.queries)

我们可以看到在打印connection.quries的时候打印的是一个空的列表。说明上面的QuerySet并没有真正的执行。
在以下情况下QuerySet会被转换为SQL语句执行

  1. 迭代:在遍历QuerySet对象的时候,会首先先执行这个SQL语句,然后再把这个结果返回进行迭代。比如以下代码就会转换为SQL语句:
for book in Book.objects.all():
    print(book)
  1. 使用步长做切片操作:QuerySet可以类似于列表一样做切片操作。做切片操作本身不会执行SQL语句,但是如果如果在做切片操作的时候提供了步长,那么就会立马执行SQL语句。需要注意的是,做切片后不能再执行filter方法,否则会报错。
  2. 调用len函数:调用len函数用来获取QuerySet中总共有多少条数据也会执行SQL语句。
  3. 调用list函数:调用list函数用来将一个QuerySet对象转换为list对象也会立马执行SQL语句。
  4. 判断:如果对某个QuerySet进行判断,也会立马执行SQL语句。

4、ORM模型练习

假设有以下ORM模型

from django.db import models

class Student(models.Model):
    """学生表"""
    name = models.CharField(max_length=100)
    gender = models.SmallIntegerField()

    class Meta:
        db_table = 'student'

class Course(models.Model):
    """课程表"""
    name = models.CharField(max_length=100)
    teacher = models.ForeignKey("Teacher",on_delete=models.SET_NULL,null=True)
    class Meta:
        db_table = 'course'

class Score(models.Model):
    """分数表"""
    student = models.ForeignKey("Student",on_delete=models.CASCADE)
    course = models.ForeignKey("Course",on_delete=models.CASCADE)
    number = models.FloatField()

    class Meta:
        db_table = 'score'

class Teacher(models.Model):
    """老师表"""
    name = models.CharField(max_length=100)

    class Meta:
        db_table = 'teacher'

表之间关系如下图:(重要,方便查看外键关联关系)
在这里插入图片描述
使用之前学到过的操作实现下面的查询操作:

1.查询平均成绩大于60分的同学的id和平均成绩;

	# 1.查询平均成绩大于60分的同学的id和平均成绩;score__number外键关联
    student = Student.objects.annotate(avg=Avg("score__number")).filter(avg__gt=60).values("id", "avg")

2.查询所有同学的id、姓名、选课的数量、总成绩;

	# 2.查询所有同学的id、姓名、选课的数量、总成绩;score__course外键关联
    student2 = Student.objects.annotate(course_count=Count("score__course"), total_score=Sum("score__number"))\
        .values("id", "name", "course_count", "total_score")
    print(student2)

3.查询姓“李”的老师的个数;

	# 3.查询姓“李”的老师的个数;name__startswith="李",以李开始的
    teacher3 = Teacher.objects.filter(name__startswith="李").count()
    print(teacher3)

4.查询没学过“李老师”课的同学的id、姓名;

	# 4.查询没学过“李老师”课的同学的id、姓名;先查询分数表score中的外键course课程,再查询课程course关联的外键teacher表,再关联的name
    student4 = Student.objects.exclude(score__course__teacher__name="李老师").values("id", "name")
    print(student4)

5.查询学过课程id为1和2的所有同学的id、姓名;

	# 5.查询学过课程id为1和2的所有同学的id、姓名;
    student5 = Student.objects.filter(score__course__in=[1, 2]).values("id", "name")
    print(student5)

6.查询所有课程成绩小于60分的同学的id和姓名;

	# 6.查询所有课程成绩小于60分的同学的id和姓名;
    # student6 = Student.objects.filter(score__number__lt=60)     # 每一门不及格的同学
    student6 = Student.objects.exclude(score__number__gt=60).values("id", "name")   # 反向查询成绩大于60的
    print(student6)

7.查询没有学全所有课的同学的id、姓名;

	 # 7.查询没有学全所有课的同学的id、姓名;
    student7 = Student.objects.annotate(num=Count("score__course")).\
        filter(num__lt=4)
    print(student7)
    
    # 另一种查询方法
    student7 = Student.objects.annotate(num=Count("score__course")).\
        filter(num__lt=Course.objects.count()).values("id", "name")
    print(student7)
	
	# 另一种查询方法
    student7 = Student.objects.annotate(num=Count("score__course"))
    for student in student7:
        print(student.id, student.name, student.num)

8.查询所有学生的姓名、平均分,并且按照平均分从高到低排序;

	# 8.查询所有学生的姓名、平均分,并且按照平均分从高到低排序;"score__number"score表中的number字段
    student8 = Student.objects.annotate(avg=Avg("score__number")).order_by("-avg").values("name", "avg")
    for student in student8:
        print(student)

9.查询各科成绩的最高和最低分,以如下形式显示:课程ID,课程名称,最高分,最低分;

# 9.查询各科成绩的最高和最低分,以如下形式显示:课程ID,课程名称,最高分,最低分;
    course9 = Course.objects.annotate(min=Min("score__number"), max=Max("score__number"))\
        .values("id", "name", "min", "max")
    for course in course9:
        print(course)   

10.统计总共有多少女生,多少男生;

 	# 10.统计总共有多少女生,多少男生;
    student_male = Student.objects.filter(gender=1).count()
    student_female = Student.objects.filter(gender=2).count()
    print(student_male, student_female)
    
    # 第二种方法,合并输出
    student10 = Student.objects.aggregate(male_number=Count("gender", filter=Q(gender=1)),
                                          female_number=Count("gender", filter=Q(gender=2)))
    print(student10)
    

11.将“黄老师”的每一门课程都在原来的基础之上加5分;

	# 11.将“黄老师”的每一门课程都在原来的基础之上加5分;
    result11 = Score.objects.filter(course__teacher__name="黄老师").update(number=F("number")+5)
    print(result11)

12.查询两门以上不及格的同学的id、姓名、以及不及格课程数;

	# 12.查询两门以上不及格的同学的id、姓名、以及不及格课程数;
    student12 = Student.objects.annotate(count=Count("score__number", filter=Q(score__number__lt=60)))\
                                                    .filter(count__gte=2).values("id", "name", "count")
    for student in student12:
        print(student)

13.查询每门课的选课人数;

	# 13.查询每门课的选课人数;
    course13 = Course.objects.annotate(nums=Count("score__student"))
    for course in course13:
        print(course.id, course.nums)
    

注意上面的ORM模型语句写法不是固定的,可以多种方法,不是唯一答案。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值