date: 2021-12-03title: Django之模型层_多表操作 #标题
tags: #标签
categories: python # 分类

项目地址:Gitee

表和表之间的关系有:一对一、多对一、多对多这三种。

我们来假定下面这些概念,字段和关系:

  • 作者模型:一个作者有姓名和年龄。
  • 作者详细模型:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息。作者详情模型和作者模型之间是一对一的关系(one-to-one)
  • 出版商模型:出版商有名称,所在城市以及email。
  • 书籍模型: 书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many);一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)。

创建模型

数据模型建立如下:

  1. from django.db import models
  2. # Create your models here.
  3. # 比较常用的信息放到这个表里面(也就是作者模型)
  4. class Author(models.Model):
  5. nid = models.AutoField(primary_key=True)
  6. name = models.CharField(max_length=32)
  7. age = models.IntegerField()
  8. # 下面是与AuthorDetail表建立一对一的关系,一对一的这个关系字段写在两个表的任意一个表里面都可以
  9. authorDetail = models.OneToOneField(to="AuthorDetail", to_field="nid", on_delete=models.CASCADE)
  10. # on_delete:删除时的一些级联效果,to_field可以不写,默认是关联到另一张表的主键,on_delete在1.x版本的django中不用写,默认是级联删除的,2.x版本的django要写。
  11. # 每个模型中都定义了 __str__方法,主要是为了在使用django的admin管理页面时,可以人性化的显示一些数据
  12. # 具体参考:https://lvjianzhao.gitee.io/lvjianzhao/2021/11/25/django%E4%B9%8Badmin%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F%E4%BD%BF%E7%94%A8/
  13. def __str__(self):
  14. return self.name
  15. # 不常用的字段放到这个表里面
  16. class AuthorDetail(models.Model):
  17. nid = models.AutoField(primary_key=True)
  18. birthday = models.DateField()
  19. telephone = models.CharField(max_length=32)
  20. addr = models.CharField(max_length=64)
  21. # def __str__(self):
  22. # return self.telephone
  23. # 出版社表 和 书籍表 是 一对多的关系
  24. # 注意哦,我们如果不写主键,django会自动给我们添加一个id列,并设置为主键。
  25. class Publish(models.Model):
  26. name = models.CharField(max_length=32)
  27. city = models.CharField(max_length=32)
  28. email = models.EmailField()
  29. '''
  30. 关于EmailField字段,是django提供的一个校验规则,
  31. 如果通过django的admin管理页面来添加数据,那么就会自动验证你添加的数据合法性,
  32. 是否为一个邮箱地址,但如果直接在mysql终端或通过程序来添加,则不会对此字段进行验证。
  33. '''
  34. def __str__(self):
  35. return self.name
  36. '''
  37. 多对多的表关系,在mysql中是手动创建一个第三张表,然后写上两个字段,每个字段外键关联到另外两张多对多关系的表,
  38. orm的manytomany自动帮我们创建第三张表,两种方式建立关系都可以,我们暂时用orm自动创建的第三张表,
  39. 因为手动创建的第三张表我们进行orm操作的时候,很多关于多对多关系的表之间的orm语句方法无法使用。
  40. # 关于mysql中创建多对多的关系,可以看下这个文章:https://blog.csdn.net/puyu9714/article/details/82872984
  41. 如果你想删除某张表,你只需要将这个表注释掉,然后执行那两个数据库同步指令就可以了,自动就删除了。
  42. '''
  43. # 注意哦,我们如果不写主键,django会自动给我们添加一个id列,并设置为主键。
  44. class Book(models.Model):
  45. title = models.CharField(max_length=32)
  46. publishDate = models.DateField()
  47. price = models.DecimalField(max_digits=5, decimal_places=2)
  48. # 与Publish表建立一对多的关系,外键字段建立在多的一方,字段publish如果是外键字段,那么它自动是int类型
  49. publish = models.ForeignKey(to="Publish", to_field="id", on_delete=models.CASCADE)
  50. '''
  51. 上面的to是指向表,to_field指向你关联的字段,不写这个,默认会自动关联主键字段,on_delete:级联删除,
  52.    字段名称不需要写成publish_id,orm在翻译foreignkey的时候会自动给你这个字段拼上一个_id,
  53. 这个字段名称在数据库里面就自动变成了publish_id
  54. '''
  55. authors = models.ManyToManyField(to='Author', )
  56. '''
  57. 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表,
  58. 并且注意一点,你查看book表的时候,你看不到这个字段,因为这个字段就是创建第三张表的意思,不是创建字段的意思,
  59. 所以只能说这个book类里面有authors这个字段属性。
  60. 注意不管是一对多还是多对多,写to这个参数的时候,最后后面的值是个字符串,不然你就需要将你要关联的那个表放到这个表的上面
  61. '''
  62. def __str__(self):
  63. return self.title

关于多对多表的三种创建方式(先了解一下)

方式一:自行创建第三章表

# 先创建两张普通表
class Book(models.Model):
    title = models.CharField(max_length=32, verbose_name="书名")


class Author(models.Model):
    name = models.CharField(max_length=32, verbose_name="作者姓名")


# 自己创建第三张表,分别通过外键关联书和作者
class Author2Book(models.Model):
    author = models.ForeignKey(to="Author")
    book = models.ForeignKey(to="Book")

    class Meta:
        unique_together = ("author", "book")

方式二:通过manytomanyField自动创建

class Book(models.Model):
    title = models.CharField(max_length=32, verbose_name="书名")


# 通过ORM自带的ManyToManyField自动创建第三张表
class Author(models.Model):
    name = models.CharField(max_length=32, verbose_name="作者姓名")
    books = models.ManyToManyField(to="Book", related_name="authors")  # 自动生成的第三张表我们是没有办法添加其他字段的

方式三:设置ManyTomanyField并指定自行创建的第三张表(称为中介模型)

class Book(models.Model):
    title = models.CharField(max_length=32, verbose_name="书名")


# 自己创建第三张表,并通过ManyToManyField指定关联
class Author(models.Model):
    name = models.CharField(max_length=32, verbose_name="作者姓名")
    books = models.ManyToManyField(to="Book", through="Author2Book", through_fields=("author", "book"))
    # through_fields接受一个2元组('field1','field2'):
    # 其中field1是定义ManyToManyField的模型外键的名(author),field2是关联目标模型(book)的外键名。


class Author2Book(models.Model):
    author = models.ForeignKey(to="Author")
    book = models.ForeignKey(to="Book")

    # 可以扩展其他的字段了
    class Meta:
        unique_together = ("author", "book")

注意:当我们需要在第三张关系表中存储额外的字段时,就要使用第三种方式,第三种方式还是可以使用多对多关联关系操作的接口(all、add、clear等等)。当我们使用第一种方式创建多对多关联关系时,就无法使用orm提供的set、add、remove、clear方法来管理多对多的关系了。

字段参数和元信息设置

我们在创建一对一、一对多、多对多时,有些参数是需要解释下的。这里就来记录下。

创建一对一关系字段时的一些参数

  • to:设置要关联的表。
  • to_field:设置要关联的字段。
  • on_delete:同ForeignKey字段。

创建一对多关系字段时的一些参数

  • to:设置要关联的表
  • to_field:设置要关联的表的字段
  • related_name:反向操作时,使用的字段名,用于代替原反向查询时的’表名_set’。
  • related_query_name:反向查询操作时,使用的连接前缀,用于替换表名。
  • on_delete:当删除关联表中的数据时,当前表与其关联的行的行为。

创建多对多字段时的一些参数

  • to:设置要关联的表
  • related_name:同ForeignKey字段。
  • related_query_name:同ForeignKey字段。
  • through:在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过 through 来指定第三张表的表名。
  • through_fields:设置关联的字段。
  • db_table:默认创建第三张表时,数据库中表的名称。

创建表时的一些元信息设置

ORM对应的类里面包含另一个Meta类,而Meta类封装了一些数据库的信息。主要字段如下:

class Author2Book(models.Model):
    author = models.ForeignKey(to="Author")
    book = models.ForeignKey(to="Book")
    class Meta:
        unique_together = ("author", "book")
  • dbtable:ORM在数据库中的表名默认是 app类名,可以通过db_table可以重写表名。db_table = ‘book_model’
  • index_together:联合索引。
  • unique_together:联合唯一索引。
  • ordering:指定默认按什么字段排序。
  • ordering = [‘pub_date’,]:只有设置了该属性,我们查询到的结果才可以被reverse(),否则是能对排序了的结果进行反转(order_by()方法排序过的数据)。

获取元信息,可以通过model对象._meta.verbose_name等获取自己通过verbose_name指定的表名,model对象._meta.model_name获取小写的表名,还有model对象.app_label可以获取这个对象的app应用名等等操作。例如:book_obj = models.Book.objects.get(id=1),book_obj._meta.model_name。

多表的增删改查

1、urls.py文件内容如下:

from django.contrib import admin
from django.urls import path
from sample_app import views

# 访问下面对应的路径,就可以出发views.py文件中定义的一些增删改操作
urlpatterns = [
    path('admin/', admin.site.urls),
    path('add_book/', views.add_book.as_view()),
    path('del_book/', views.del_book.as_view()),
    path('update_book/', views.update_book.as_view()),
]

增删改操作都写在了views.py文件中,如下:

from django.shortcuts import render, HttpResponse
from django.views import View
from sample_app import models


# Create your views here.

class add_book(View):
    def get(self, request):
        #################### 一对一的表关系增加数据   ####################
        # 需要先给要关联的字段更新数据(或者查询到要关联的数据)

        # 方式一:先创建作者详细信息,再插入作者表
        # 先插入作者详细信息
        new_authordetail = models.AuthorDetail.objects.create(
            birthday='1996-09-19',
            telephone='13661040254',
            addr='河北邯郸'
        )
        models.Author.objects.create(
            name='吕建钊',
            age=32,
            authorDetail=new_authordetail,  # 这里直接写上面接收的models对象名称
            # 注意:authorDetail为模型中的属性名
        )

        # 方式二:先查出来作者详细信息,然后再插入作者信息表(常用,适用于作者详细信息已经存在表中,并且此详细信息没有对应关系)
        # 先查(查的这条数据一定一定不可以有对应关系)
        obj = models.AuthorDetail.objects.filter(addr='天津').first()
        # 再插入作者信息表
        models.Author.objects.create(
            name='尼古拉斯·赵四',
            age=26,
            authorDetail_id=obj.nid,  # 这里通过查到的对象,获取其nid字段的值
            # 注意:authorDetail_id为author中的实际字段名,不要搞混了
        )

        #################### 一对多的表关系增加数据   ####################
        # 一对多添加记录和一对一类似,唯一不同的就是,一条记录可以对应多条关系,而一对一不行
        # 方式一:用属性和对象的方式添加
        obj = models.Publish.objects.get(id=2)  # 先获取要关联的对象

        models.Book.objects.create(
            title='深入理解OpenStack neutron',
            publishDate='2019-07-21',
            price=45.5,
            publish=obj
        )

        # 方式二:通过字段名称和对象值来添加
        models.Book.objects.create(
            title='python cookbook',
            publishDate='2009-07-21',
            price=95.5,
            publish_id=obj.id  # publish_id是表中的实际字段名,obj是复用上面查出来的对象
        )

        #################### 多对多的表关系增加数据   ####################
        # 方式一:常用
        # 这里就把书籍表和作者表进行多对多记录下来
        book_obj = models.Book.objects.get(id=3)  # 先查出来要操作哪本书的记录
        book_obj.authors.add(*[1, 5])
        '''
        然后调用其authors属性,就可以找到to='Author' 这个表,
        通过列表传入Author中的主键id值,使用星号打散传入即可
        '''

        # 方式二:先获取作者的记录对象,然后将对象传入给Book对象中
        author1 = models.Author.objects.get(nid=2)
        author2 = models.Author.objects.get(nid=7)
        book_obj = models.Book.objects.get(id=1)
        book_obj.authors.add(*[author1, author2])

        return HttpResponse('ok!')


class del_book(View):
    def get(self, request):
        ################   一对一删除   ################
        # 一对一删除:表一外键关联到表二,表一删除,不影响表2,表2删除会影响表1
        # 下面只会删除Author表中nid为7的记录,它关联的AuthorDetail表中的记录不会被删除
        models.Author.objects.get(nid=7).delete()

        '''
        删除 AuthorDetail 中nid为5的数据,此时Author表中关联了这条数据的记录,也会被删除
        相当于下面的语句,会删除AuthorDetail表和Author表中两条记录
        '''
        models.AuthorDetail.objects.get(nid=5).delete()

        ################   一对多删除   ################
        # 以出版社和书籍为例(一个书籍是一个出版社出的,但一个出版社会出版很多书籍,这就是一(出版社)对多(书籍))
        # 删除一个书籍(出版社表的记录不会受到影响)
        models.Book.objects.get(id=1).delete()

        # 删除一个出版社,那属于此出版社的书籍记录都会被删除
        models.Publish.objects.get(id=2).delete()

        ################  多对多删除   ################
        '''
        多对多,就是操作book表和Author的关系表:sample_app_book_authors表
        '''

        # 先查到book表,通过它的authors属性,
        # 也就是多对多关联到Author表的那个字段,去进行删除
        book_obj = models.Book.objects.get(id=3)

        # 删除book表id为3的记录和author表id为8的关系记录
        book_obj.authors.remove(8)

        # 删除book表id为3的记录和author表id为1和2的关系记录
        book_obj.authors.remove(*[1, 2])

        # 清空book表id为3,和author表的所有关系记录
        book_obj.authors.clear()

        return HttpResponse('ok!')


class update_book(View):
    def get(self, requeset):
        ##############   一对一更新    ###############

        models.Author.objects.filter(nid=8).update(
            name='刘老师',
            age=36,

            # 可以使用Author表的字段名去更新,需要指定authorDetail表中的id号
            # authorDetail_id=6,

            # 也可以通过Author模型的属性来更新,值为module对象类型
            authorDetail=models.AuthorDetail.objects.get(nid=3),
        )

        ##############   一对多更新    ###############
        # 下面的pk表示primary key,也就是主键的意思,pk=3 等同于 id=3
        models.Book.objects.filter(pk=3).update(
            title='坏蛋是怎样炼成的',

            # 和一对一更新类似,如果写的是数据模型的属性名,那么就需要用module对象来作为值
            # publish=models.Publish.objects.get(id=2),

            # 如果指定的是表的列名,那就应该用publish中的某个主键id作为值
            publish_id=3,
        )

        ##############   多对多更新    ###############
        # 获取到要操作的记录
        book_obj = models.Book.objects.get(id=3)

        # 给book表的id为3的记录增加一条和author表id为8的关系
        book_obj.authors.add(*[8, ])

        # 将book表的id为3的记录,对应的关系设置为 1(先将原来的值清空,再设置新值)
        book_obj.authors.set('1')

        # 将book表的id为3的记录,对应的关系设置为 1和8(先将原来的值清空,再设置新值)
        book_obj.authors.set(['1', '8'])

        return HttpResponse('update success!!!')


class query_book(View):
    def get(self, request):
        ########################### 基于对象的跨表查询(类似于mysql中的子查询) ####################################
        '''
        在跨表查询中,有两个重要的概念,就是正向查询和反向查询。
        关系属性(字段)写在哪个类(表)里面,从当前类(表)的数据去查询它关联类(表)的数据叫做正向查询,
        反之叫做反向查询。

        在多表查询中,不管是一对多还是多对对,都有正向查询和反向查询这个概念。
        '''

        #################  一对一的多表查询  ###################

        # 查询刘老师的电话号码(正向查询)
        author_obj = models.Author.objects.filter(name='刘老师').first()
        print(author_obj.authorDetail.telephone)  # 13888888888

        # 查询下哪位老师是河北邯郸的(反向查询)
        authorDetail = models.AuthorDetail.objects.filter(addr='河北邯郸').first()
        # 下面的author是(作者表)小写的类名称
        print(authorDetail.author.name)  # 李四

        #################  一对多的多表查询  ###################

        # 查询linux基础这本书是哪个出版社出版的(正向查询)
        book_obj = models.Book.objects.filter(title='linux基础').first()
        print(book_obj.publish.name)  # 铁路出版社

        # 查询上海出版社出版了哪些书(反向查询)
        publish_obj = models.Publish.objects.filter(name='上海出版社').first()
        # 一对多的反向查询中,在通过已知对象调关系表的时候,需要在小写类名后面加_set,表示返回的可能是多个结果
        print(publish_obj.book_set.all())  # <QuerySet [<Book: 坏蛋是怎样炼成的>, <Book: shell基础>]>

        #################  多对多的多表查询  ###################

        # 查询 shell基础 是谁写的(正向查询)
        book_obj = models.Book.objects.filter(title='shell基础').first()
        # 下面的authors是Book模型中定义的属性(用于关联到作者表的字段:to='Author')
        print(book_obj.authors.all())  # <QuerySet [<Author: 张三>, <Author: 刘老师>]>

        # 查询 id为8的刘老师都写过哪些书(pk表示为主键字段)
        author_obj = models.Author.objects.filter(pk=8).first()
        # 多对多的反向查询中,在通过已知对象调关系表的时候,需要在小写类名后面加_set,表示返回的可能是多个结果
        print(author_obj.book_set.all())  # <QuerySet [<Book: shell基础>, <Book: 坏蛋是怎样炼成的>]>

        ########################### 基于对象的跨表查询到此结束 ####################################

        ########################### 基于双下划线的跨表查询(类似mysql中的连表join) ####################################
        #################  一对一的多表查询  ###################
        # 方式一:正向查询(查询刘老师的电话号码)
        # authorDetail__telephone表示为authorDetail数据模型中的telephone模型
        obj = models.Author.objects.filter(name='刘老师').values('authorDetail__telephone')
        print(obj)  # <QuerySet [{'authorDetail__telephone': '13888888888'}]>

        # 方式二:反向查询(查询刘老师的电话号码)
        # author__name表示为author数据模型中的name属性
        obj = models.AuthorDetail.objects.filter(author__name='刘老师').values('telephone')
        print(obj)  # <QuerySet [{'telephone': '13888888888'}]>

        # 下面是根据电话号码查出对应的作者姓名
        obj = models.Author.objects.filter(authorDetail__telephone='13888888888').values('name')
        print(obj)  # <QuerySet [{'name': '刘老师'}]>

        obj = models.AuthorDetail.objects.filter(telephone='13888888888').values('author__name')
        print(obj)  # <QuerySet [{'author__name': '刘老师'}]>

        #################  一对多的多表查询  ###################

        # 查询下linux基础这本书的出版社是哪个
        # 正向查询
        obj = models.Book.objects.filter(title='linux基础').values('publish__name')
        print(obj)  # <QuerySet [{'publish__name': '铁路出版社'}]>
        # 反向查询
        obj = models.Publish.objects.filter(book__title='linux基础').values('name')
        print(obj)  # <QuerySet [{'name': '铁路出版社'}]>

        # 上海出版社 出版了哪些书
        # 反向查询
        obj = models.Publish.objects.filter(name='上海出版社').values('book__title')
        print(obj)  # <QuerySet [{'book__title': '坏蛋是怎样炼成的'}, {'book__title': 'shell基础'}]>
        # 正向查询
        obj = models.Book.objects.filter(publish__name='上海出版社').values('title')
        print(obj)  # <QuerySet [{'title': '坏蛋是怎样炼成的'}, {'title': 'shell基础'}]>

        #################  多对多的多表查询  ###################
        # 正向查询(shell基础是谁写的)
        # 下面authors__name中的authors是Book数据模型中的一个属性
        obj = models.Book.objects.filter(title='shell基础').values('authors__name')
        print(obj)  # <QuerySet [{'authors__name': '张三'}, {'authors__name': '刘老师'}]>
        # 反向查询(下面的book__title中的book,是Book数据模型的小写名字)
        obj = models.Author.objects.filter(book__title='shell基础').values('name')
        print(obj)  # <QuerySet [{'name': '张三'}, {'name': '刘老师'}]>

        # 查询刘老师写过哪些书
        # 正向查询(下面authors__name中的authors是Book数据模型中的一个属性)
        obj = models.Book.objects.filter(authors__name='刘老师').values('title')
        print(obj)  # <QuerySet [{'title': 'shell基础'}, {'title': '坏蛋是怎样炼成的'}]>

        # 反向查询(下面的book__title中的book,是Book数据模型的小写名字)
        obj = models.Author.objects.filter(name='刘老师').values('book__title')
        print(obj)  # <QuerySet [{'book__title': 'shell基础'}, {'book__title': '坏蛋是怎样炼成的'}]>

        ########################### 基于双下划线的进阶跨表查询 ####################################

        # 查询人民出版社出版了哪些书以及作者的名字
        # 方式一:
        obj = models.Book.objects.filter(publish__name='人民出版社').values('title', 'authors__name')
        print(
            obj)  # <QuerySet [{'title': 'openstack基础', 'authors__name': '张三'}, {'title': 'openstack基础', 'authors__name': '李四'}]>

        # 方式二
        obj = models.Author.objects.filter(book__publish__name='人民出版社').values('book__title', 'name')
        print(
            obj)  # <QuerySet [{'book__title': 'openstack基础', 'name': '张三'}, {'book__title': 'openstack基础', 'name': '李四'}]>

        # 查询手机号以8结尾的作者出版过的所有书籍以及出版社的名字
        obj = models.AuthorDetail.objects.filter(telephone__endswith='8').values('author__book__title',
                                                                                 'author__book__publish__name')
        print(
            obj)  # <QuerySet [{'author__book__title': 'linux基础', 'author__book__publish__name': '铁路出版社'}, {'author__book__title': 'shell基础', 'author__book__publish__name': '上海出版社'}, {'author__book__title': '坏蛋是怎样炼成的', 'author__book__publish__name': '上海出版社'}]>

        return HttpResponse('query ok !!!')

related_name:替换被关联的表名

related_name用于给你要关联的表定义一个别名,然后只能通过这个别名来调用。在ForeignKey和ManyToManyField中,都可以使用related_name来定义别名,注意:别名只对反向查询生效,对正向查询无效

假设现在book表的数据模型修改为下面这样:

class Book(models.Model):
    title = models.CharField(max_length=32)
    publishDate = models.DateField()
    price = models.DecimalField(max_digits=5, decimal_places=2)
    publish = models.ForeignKey(to="Publish", to_field="id", on_delete=models.CASCADE, related_name='pl')
    authors = models.ManyToManyField(to='Author', related_name='at')

那么对应的查询语句,如下:

# 查询下linux基础这本书的出版社是哪个
        # 方式一:正向查询(不影响,还是按照Book的authors属性去找对应的字段)
        obj = models.Book.objects.filter(title='linux基础').values('publish__name')
        print(obj)      # <QuerySet [{'publish__name': '铁路出版社'}]>

        # 方式二:反向查询,之前是按照小写的表名字加双下划线来连表,现在不用了,直接使用related_name定义的值即可
        obj=models.Publish.objects.filter(pl__title='linux基础').values('name')
        print(obj)    # <QuerySet [{'name': '铁路出版社'}]>

如果反向查询时,再按照之前的语句去查:

        # 通过小写的表名字去连表
        obj = models.Publish.objects.filter(book__title='linux基础').values('name')
        print(obj)

就会报错如下:

Django之模型层_多表操作 - 图1

在基于对象的反向查询中,还记得需要通过小写表名字加_set,来取返回的结果么?如下:

# 查询上海出版社出版了哪些书(反向查询)
 publish_obj = models.Publish.objects.filter(name='上海出版社').first()
# 一对多的反向查询中,在通过已知对象调关系表的时候,需要在小写类名后面加_set,表示返回的可能是多个结果
print(publish_obj.book_set.all())  # <QuerySet [<Book: 坏蛋是怎样炼成的>, <Book: shell基础>]>

如果定义了related_name,那么就需要通过如下方式进行调用:

      publish_obj = models.Publish.objects.filter(name='上海出版社').first()
        # 一对多的反向查询中,在通过已知对象调关系表的时候,不需要再用小写表名字加_set了,直接使用related_name定义的值即可
        print(publish_obj.pl.all())  # <QuerySet [<Book: 坏蛋是怎样炼成的>, <Book: shell基础>]>

聚合查询、分组查询、F查询和Q查询

聚合查询

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

class aggre_query(View):
    def get(self, request):
        # 计算所有书籍的平均价格、最贵的、最便宜的、所有书籍的总价
        from django.db.models import Avg, Sum, Max, Min
        # 平均值
        avg_price = models.Book.objects.all().aggregate(Avg('price'))
        # 最小值、最大值、总和(下面aggregate方法中的等于号左边,是给它起了个名字,在print时,这个名字就是输出的字典中的key)
        min_price = models.Book.objects.all().aggregate(min_price=Min('price'))
        max_price = models.Book.objects.all().aggregate(max_price=Max('price'))
        sum_price = models.Book.objects.all().aggregate(sum_price=Sum('price'))
        print(avg_price)  # {'price__avg': Decimal('59.825000')}
        print(min_price)  # {'min_price': Decimal('35.60')}
        print(max_price)  # {'max_price': Decimal('99.90')}
        print(sum_price)  # {'sum_price': Decimal('239.30')}
        return HttpResponse('query ok !!!')

如果你希望生成不止一个聚合,你可以向aggregate()子句中添加另一个参数。所以,如果你也想知道所有图书价格的最大值和最小值,可以这样查询:

many_aggre = models.Book.objects.aggregate(Avg('price'), Max('price'), Min('price'), Count('id'))
print(many_aggre)  
#输出: {'price__avg': Decimal('59.825000'), 'price__max': Decimal('99.90'), 'price__min': Decimal('35.60'), 'id__count': 4}

分组

所谓分组,就是sql语句中的group by语句。

class Group_query(View):
    def get(self, request):
        from django.db.models import Avg, Sum, Max, Min, Count
        # 查询所有出版社出版的书籍平均价格
        # 方式一:(下面的publish_id是表中的字段名,也可以用数据模型中的属性:publish)
        ret = models.Book.objects.values('publish_id').annotate(avg_price=Avg('price'))
        print(ret)  # <QuerySet [{'publish_id': 1, 'avg_price': Decimal('99.900000')}, {'publish_id': 2, 'avg_price': Decimal('58.300000')}, {'publish_id': 3, 'avg_price': Decimal('40.550000')}]>
        '''
        上面的查询语句等同于如下sql:
        SELECT avg(price) as avg_price from sample_app_book GROUP BY publish_id;
        '''

        # 方式二(反向查询):
        # 下面pl__price中的'pl'关键字,是在book数据模型中的publish属性中定义的related_name='pl',
        # 如果没有定义related_name,那么就需要将pl__price改为publish__price
        ret = models.Publish.objects.annotate(avg_price=Avg('pl__price')).values('name', 'avg_price')
        print(ret)  # <QuerySet [{'name': '铁路出版社', 'avg_price': Decimal('58.300000')}, {'name': '人民出版社', 'avg_price': Decimal('99.900000')}, {'name': '上海出版社', 'avg_price': Decimal('40.550000')}]>
        return HttpResponse('query ok !!!')

F查询和Q查询

F查询和Q查询我简单整理了下,可能示例不够多,如果想要深入了解下,可以看下原视频

F查询

在上面所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?我们在book表里面加上两个字段:评论数:comment,点赞数:dianzan

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

假设book表数据如下:

Django之模型层_多表操作 - 图2

可以使用F查询,来查出来评论数大于点赞数的记录:

class query(View):
    def get(self, request):
        from django.db.models import F  # 引入F方法
        ret=models.Book.objects.filter(comment__gt=F('dianzan')).values('title','comment','dianzan')
        print(ret)        <QuerySet [{'title': 'openstack基础', 'comment': 99, 'dianzan': 20}, {'title': 'shell基础', 'comment': 30, 'dianzan': 25}]>
        return HttpResponse('query ok !!!')

Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作。

# 查询评论数大于点赞数两倍的书籍
ret = models.Book.objects.filter(comment__gt=F('dianzan') * 2).values('title', 'comment', 'dianzan')
        print(ret)  # <QuerySet [{'title': 'openstack基础', 'comment': 99, 'dianzan': 20}]>

修改表数据的操作,也可以使用F函数,比如将每本书的价格提高30元:

models.Book.objects.all().update(price=F("price") + 30)

Q查询

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

Q 对象可以使用&(与) 、|(或)、~(非) 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。

class query(View):
    def get(self, request):
        from django.db.models import Q
        # 查询点赞数大于50或者评论数大于80的书籍信息
        ret = models.Book.objects.filter(Q(dianzan__gt=50) | Q(comment__gt=80)).values('title', 'comment', 'dianzan')
        print(ret)  # <QuerySet [{'title': 'linux基础', 'comment': 50, 'dianzan': 60}, {'title': 'openstack基础', 'comment': 99, 'dianzan': 20}, {'title': '坏蛋是怎样炼成的', 'comment': 150, 'dianzan': 200}]>


        return HttpResponse('query ok !!!')

上面是一个简单的“或”关系的查询案例,至于其他高阶查询,就不一一举例了,下面的模板,大家自行去按照需求写吧:

# xx=11或者 ss=22并且oo=33,&的优先级高
filter(Q(xx=11)|Q(ss=22)&Q(oo=33))

# 如果想让 或 关系优先级高,那就再用一个 Q()给两个或关系括起来
filter(Q(Q(xx=11)|Q(ss=22))&Q(oo=33))  

# 如果还要加and关系,可以直接使用filter原生的,通过逗号隔开,并且只能写到Q查询的后面,写到前面会报错
filter(Q(Q(xx=11)|Q(ss=22))&Q(oo=33),name='dd')

ORM执行原生sql

在模型查询API不够用的情况下,我们还可以使用原始的SQL语句进行查询。

Django 提供两种方法使用原始SQL进行查询:一种是使用raw()方法,进行原始SQL查询并返回模型实例;另一种是完全避开模型层,直接执行自定义的SQL语句。

raw方法执行原生sql

raw()管理器方法用于原始的SQL查询,并返回模型的实例。注意:raw()语法查询必须包含主键。

这个方法执行原始的SQL查询,并返回一个django.db.models.query.RawQuerySet 实例。 这个RawQuerySet 实例可以像一般的QuerySet那样,通过迭代来提供对象实例。

举个例子:

class query(View):
    def get(self, request):
        obj=models.Book.objects.raw('select * from sample_app_book')
        for i in obj:
            print(i.title)
            print(i.price)
            '''
            # 输出如下:
            linux基础
            88.30
            openstack基础
            129.90
            坏蛋是怎样炼成的
            75.50
            shell基础
            65.60
            '''
        return HttpResponse('query ok !!!')

直接执行自定义SQL

有时候raw()方法并不十分好用,很多情况下我们不需要将查询结果映射成模型,或者我们需要执行DELETE、 INSERT以及UPDATE操作。在这些情况下,我们可以直接访问数据库,完全避开模型层。

我们可以直接从django提供的接口中获取数据库连接,然后像使用pymysql模块一样操作数据库。如下:

class query(View):
    def get(self, request):
        from django.db import connection, connections
        print(connection.queries)
        cursor = connection.cursor()  # 获取游标
        cursor.execute("SELECT * from sample_app_author where nid = %s", [8])
        ret = cursor.fetchone()
        print(ret)      # (8, '刘老师', 36, 3)
        print(connection.queries)      # 查询原生sql,输出如下:[{'sql': 'SELECT @@SQL_AUTO_IS_NULL', 'time': '0.000'}, {'sql': 'SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED', 'time': '0.000'}, {'sql': 'SELECT * from sample_app_author where nid = 8', 'time': '0.000'}]