美文网首页python webPython之路
第19天,Django之ORM进阶续

第19天,Django之ORM进阶续

作者: CaiGuangyin | 来源:发表于2017-11-24 12:02 被阅读67次

    目录

    一、 基于对象的跨表查询
        1.1 一对多查询(Book与Publish)
        1.2 一对一查询(Author与AuthorDetail)
        1.3 多对多查询(Author与Book)
            1.3.1 练习前的准备
            1.3.2 开始查询
            1.3.3 特别注意
    
    二、基于双下划线的跨表查询
        2.1 练习一,一对多
        2.2 练习二,多对多
        2.3 练习三,三表联查
        2.4 练习四
    
    三、聚合与分组查询
        3.1 聚合
        3.2 分组
            对所查询对象的关联对象进行聚合
    
    四、F查询与Q查询
        4.1 F查询
        4.2 Q查询
        
    五、修改表记录
    六、删除表记录
    七、定义Django models中的meta选项
    
    

    models.py文件内容如下

    class Author(models.Model):
        nid = models.AutoField(primary_key=True)
        name=models.CharField( max_length=32)
        age=models.IntegerField()
     
        # 与AuthorDetail建立一对一的关系
        authorDetail=models.OneToOneField(to="AuthorDetail")
     
    class AuthorDetail(models.Model):
     
        nid = models.AutoField(primary_key=True)
        birthday=models.DateField()
        telephone=models.BigIntegerField()
        addr=models.CharField( max_length=64)
        
    class Publish(models.Model):
        nid = models.AutoField(primary_key=True)
        name=models.CharField( max_length=32)
        city=models.CharField( max_length=32)
        email=models.EmailField()
     
     
    class Book(models.Model):
     
        nid = models.AutoField(primary_key=True)
        title = models.CharField( max_length=32)
        publishDate=models.DateField()
        price=models.DecimalField(max_digits=5,decimal_places=2)
        keepNum=models.IntegerField()<br>    commentNum=models.IntegerField()
     
        # 与Publish建立一对多的关系,外键字段建立在多的一方
        publish=models.ForeignKey(to="Publish",to_field="nid")
     
        # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表
        authors=models.ManyToManyField(to='Author')  
    

    以下所有查询及操作均基于此models.py实现

    一、 基于对象的跨表查询

    1.1 一对多查询(Book与Publish)

    正向查询
    基于Book表,按Book表字段publish去查

    # 查询nid=1的书籍的出版社所在的城市
    book_obj=Book.objects.get(nid=1)
    city=book_obj.publish.city
        #book_obj.publish 是nid=1的书籍对象关联的出版社对象
    

    反向查询
    基于Publish表,通过表名加_set实现

    # 查询人民出版社出版过的所有书籍
    publish = Publish.objects.get(name="人民出版社")
    book_list=publish.book_set.all()
        # 取到与人民出版社关联的所有书籍对象的集合
    
    for book_obj in bool_list:
        print(book_obj.title)
    

    1.2 一对一查询(Author与AuthorDetail)

    正向查询
    基于Author表,按Author表下的authorDetail字段

    # 查询egon作者的手机号
    author_egon = Author.objects.get(name='egon')
    egon_phone = author_egon.authorDetail.telephone
    

    反向查询
    基于AuthorDetail表查询

    # 查询所有住址在北京的作者的姓名
    authorDetail_list = AuthorDetail.objects.filter(addr='beijing')
    
    for obj in authorDetail_list:
        print(obj.author.name)
    

    1.3 多对多查询(Author与Book)

    1.3.1 练习前的准备

    创建表和批量插入数据

    重新编辑models.py,内容如下:

    from django.db import models
    
    # Create your models here.
    
    class Publish(models.Model):
        name = models.CharField(max_length=32)
        address = models.CharField(max_length=64)
        email = models.EmailField(default='222@qq.com')
        date = models.DateField(default='1970-01-01')
        def __str__(self):
            return self.name
    
    class Author(models.Model):
        name = models.CharField(max_length=32)
        phone = models.IntegerField()
        qq = models.IntegerField(default=0)
        address = models.CharField(max_length=64,default='Beijing')
        def __str__(self):
            return self.name
    
    
    class Book(models.Model):
    
        title = models.CharField(max_length=60)
        price = models.DecimalField(max_digits=6,decimal_places=2)
        authors = models.ManyToManyField(to="Author")
        publishDate = models.DateField()
        publisher = models.ForeignKey(to="Publish")
    
        def __str__(self):
            return self.title
    

    批量插入数据代码如下:

    urls.py修改如下:

    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^batch/', views.batch),
    ]
    

    views.py修改如下:

    from django.shortcuts import render,HttpResponse
    import random,time
    from app01.models import *
    
    # Create your views here.
    
    # 生成随机日期:1999-09-12
    def randDate(num):
        # num 是产生多少个日期字符串
        a1 = (1976, 1, 1, 0, 0, 0, 0, 0, 0)  # 设置开始日期时间元组(1976-01-01 00:00:00)
        a2 = (1990, 12, 31, 23, 59, 59, 0, 0, 0)  # 设置结束日期时间元组(1990-12-31 23:59:59)
    
        start = time.mktime(a1)  # 生成开始时间戳
        end = time.mktime(a2)  # 生成结束时间戳
    
        date_list = []
    
        # 随机生成10个日期字符串
        for i in range(num):
            t = random.randint(start, end)  # 在开始和结束时间戳中随机取出一个
            date_touple = time.localtime(t)  # 将时间戳生成时间元组
            date = time.strftime("%Y-%m-%d", date_touple)  # 将时间元组转成格式化字符串(1976-05-21)
            date_list.append(date)
        return date_list
    
    
    book_list = ["俊友","基督山伯爵","呼啸山庄","傲慢与偏见",
                 "巴黎圣母院","威尼斯商人","福尔摩斯探案集",
                 "雾都孤儿","浮士德","蝴蝶梦","包法利夫人",
                 "红与黑","远大前程","小王子","飘","围城",
                 "小妇人","驴皮记","莎士比亚全集","悲惨世界",
                 "欧也妮·葛朗台","复活","简爱","爱玛","唐璜",
                 "大卫·科波菲尔","唐·吉诃德","安娜·卡列尼娜",
                 "笑面人","一千零一夜","环游世界八十天","贝姨",
                 "绿野仙踪","神秘岛","海底两万里","死魂灵",
                 "贝姨","海上劳工","于连","高老头","双城记",
                 "黑桃皇后","好兵帅克","罪与罚","斯巴达克思",
                 "上尉的女儿","名利场","圣女贞德","窈窕淑女",
                 "苔丝","鲁宾逊漂流记","曼斯菲尔德花园","白鲸",
                 "百万英镑",]
    
    # 随机生成书箱数据
    def batch(req):
        for book in book_list:
            rand_price = random.randint(20, 300)
            rand_pub_obj = random.choice(Publish.objects.all())
            rand_date = random.choice(randDate(len(book_list)))
            author_obj_list=[]
            for i in range(random.randint(1,3)):
                rand_author_obj = random.choice(Author.objects.all())
                author_obj_list.append(rand_author_obj)
    
            book_obj = Book.objects.create(title=book,price=rand_price,publishDate=rand_date,publisher=rand_pub_obj)
            book_obj.authors.add(*author_obj_list)
    
        return HttpResponse("插入数据成功")
    

    1.3.2 开始查询

    正向查询
    基于Book表,按Book表下的authors字段查询

    # 俊友这本书所有作者的名字以及手机号
    book_obj = Book.objects.filter(title="俊友").first()
    value = book_obj.authors.values('name','phone')
    print(value)
    

    结果如下:

    <QuerySet [{'phone': 1345439909, 'name': 'Alex'}, {'phone': 13898970093, 'name': 'Egon'}, {'phone': 13212567658, 'name': 'Oldboy'}]>
    

    反向查询
    基于Author表

    # 查询Egon出过的所有书籍的名字
    author_obj = Author.objects.filter(name='Egon').first()
    value = author_obj.book_set.values('title')
    print(value)
    

    以上代码结果如下:

    <QuerySet [{'title': '俊友'}, {'title': '呼啸山庄'}, {'title': '雾都孤儿'},...]>
    

    注意:book_set 是Book表名的小写加_set,反向查找时用到。这里用的是单下划线

    1.3.3 特别注意

    你可以通过在ForeignKey()ManyToManyField()的定义中设置 related_name 的值来覆写 FOO_set 的名称。例如,如果 Article model 中做一下更改: publish = ForeignKey(Blog, related_name='bookList'),那么接下来就会如我们看到这般

    # 查询 人民出版社出版过的所有书籍
       publish=Publish.objects.get(name="人民出版社")
       book_list=publish.bookList.all()  
          # 与人民出版社关联的所有书籍对象集合
    

    如上,一对多,多对多字段定义时设置了related_name时,查询调用时,就必须用related_name的值。

    二、基于双下划线的跨表查询

    Django 还提供了一种直观而高效的方式在查询(lookups)中表示关联关系,它能自动确认 SQL JOIN 联系。要做跨关系查询,就使用两个下划线来链接模型(model)间关联字段的名称,直到最终链接到你想要的 model 为止。

    关键点:正向查询按字段,反向查询按表名。

    以下所有查询均基于<1.3.1练习前的准备>中所创建的表

    2.1 练习一,一对多

    查询人民出版社出版过的所有书籍的名字与价格(一对多)

    正向查询
    基于Book表,按字段publisher查询

    value = Book.objects.filter(publisher__name='人民出版社').values('title','price')
    print(value)
    

    查询结果:

    <QuerySet [{'price': Decimal('295.00'), 'title': '基督山伯爵'}, {'price': Decimal('44.00'), 'title': '包法利夫人'}, {'price': Decimal('108.00'), 'title': '小妇
    人'}, {'price': Decimal('98.00'), 'title': '神秘岛'}, {'price': Decimal('172.00'), 'title': '曼斯菲尔德花园'}]>
    

    反向查询
    基于Publish表,按表名Book的小写book

    value = Publish.objects.filter(name='人民出版社').values_list('book__title','book__price')
    print(value)
    

    查询结果如下:

    <QuerySet [('基督山伯爵', Decimal('295.00')), ('包法利夫人', Decimal('44.00')), ('小妇人', Decimal('108.00')), ('神秘岛', Decimal('98.00')), ('曼斯菲尔德花园', Decimal('172.00'))]>
    

    注意: values()方法获取到的是QuerySet列表中包含的字典,而values_list()方法获取到的是QuerySet列表中包含的元组。
    特别注意:filter()中使用双下划线做跨表查询不需要加引号;而在values()values_list()中使用则需要加引号

    2.2 练习二,多对多

    查询Egon写过的所有书籍的名字(多对多

    正向查询
    基于Book表的author字段

    value=Book.objects.filter(authors__name='Egon').values('title')
    print(value)
    

    查询结果:

    <QuerySet [{'title': '俊友'}, {'title': '呼啸山庄'}, {'title': '雾都孤儿'}, 省略...]>
    

    反向查询
    基于Author表,通过Book表的小写book加“__”双下划线加字段名查询,例如:book__title

    value=Author.objects.filter(name='Egon').values('book__title')
    print(value)
    

    查询结果:

    <QuerySet [{'book__title': '俊友'}, {'book__title': '呼啸山庄'}, {'book__title': '雾都孤儿'}, 省略...]>
    

    注意:从这个查询结果上看,字典的key是由django自动命令成“表名小写”+“双下划线__”+“字段名”,如果需要通过字典的键去获取值的时候,要特别注意了。

    2.3 练习三,三表联查

    查询人民出版社出版过的所有书籍的名字以及作者的姓名

    正向查询
    基于Book表查询

    value=Book.objects.filter(publisher__name='人民出版社').values('title','authors__name')
    print(value)
    

    查询结果:

    <QuerySet [{'title': '基督山伯爵', 'authors__name': 'Alven'}, {'title': '包法利夫人', 'authors__name': 'Egon'}, {'title': '包法利夫人', 'authors__name': 'Alven'}, {'title': '小妇人', 'authors__name': 'Alven'}, {'title': '神秘岛', 'authors__name': 'Alex'}, {'title': '曼斯菲尔德花园', 'authors__name': 'Alex'}, {'title': '曼斯菲尔德花园', 'authors__name': 'Oldboy'}]>
    

    反向查询
    基于Publish表查询

    value = Publish.objects.filter(name='人民出版社').values('book__title','book__authors__name')
    print(value)
    

    查询结果:

    <QuerySet [{'book__title': '基督山伯爵', 'book__authors__name': 'Alven'}, {'book__title': '包法利夫人', 'book__authors__name': 'Egon'}, {'book__title': '包法利夫人', 'book__authors__name': 'Alven'}, {'book__title': '小妇人', 'book__authors__name': 'Alven'}, {'book__title': '神秘岛', 'book__authors__name': 'Alex'}, {'book__title': '曼斯菲尔德花园', 'book__authors__name': 'Alex'}, {'book__title': '曼斯菲尔德花园', 'book__authors__name': 'Oldboy'}]>
    

    2.4 练习四

    手机号以176开头的作者出版过的所有书籍名称以及出版社名称

    value = Book.objects.filter(authors__phone__startswith=176).values_list('title','publisher__name')
    print(value)
    

    查询结果:

    <QuerySet [('基督山伯爵', '人民出版社'), ('呼啸山庄', '人民邮电出版社'), ('傲慢与偏见', '人民邮电出版社'), ('蝴蝶梦', '武汉大学出版社'), 省略...]>
    

    注意:
    反向查询时,如果定义了related_name ,则用related_name替换表名,例如: publish = ForeignKey(Blog, related_name='bookList')

    # 练习1:  查询人民出版社出版过的所有书籍的名字与价格>>(一对多)
       # 反向查询 不再按表名:book,而是related_name:bookList
    queryResult=Publish.objects.filter(name="人民出版社").values_list("bookList__title","bookList__price")
    

    三、聚合与分组查询

    将一张表当作一个大的分组,且不以表中的某个字段来分组时,就可以直接用aggregate(),当需要以表中某个字段进行分组,再对每个分组的进行聚合计算时,必须用annotate()

    3.1 聚合

    aggregate(*args,*kwargs)*

    例如:计算所有图书的平均价格

    # 需要先导入Avg(),Max(),Min(),Count(),Sum()等聚合函数
    from django.db.models import Avg,Max,Min,Count
    value = Book.objects.aggregate(Avg('price'))
    print(value)
    

    计算结果:

    {'price__avg': 148.85185185185185}
    

    注意:结果是一个字典,字典的key是django自动用“字段名”+"聚合函数名"来命名的,还可以自定义这个字典的键名,只需将上述代码中Avg('price')替换为avgPrice=Avg('price')即可,avgPrice就是你自定义的key。代码如下:

    value = Book.objects.aggregate(avgPrice=Avg('price'))
    print(value)
    

    结果如下:

    {'avgPrice': 148.85185185185185}
    

    aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。

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

    value = Book.objects.aggregate(Avg('price'),Max('price'),Min('price'))
    print(value)
    

    计算结果:

    {'price__avg': 148.85185185185185, 'price__max': Decimal('296.00'), 'price__min': Decimal('23.00')}
    

    3.2 分组

    annotate()

    为调用的QuerySet中每一个对象都生成一个独立的统计值(统计方法用聚合函数)。

    annotate的返回值是querySet,如果不想遍历对象,可以用上valuelist()或values()显示.

    例如:统计每一本书的作者个数

    value = Book.objects.annotate(author_count=Count('authors')).values_list('title','author_count')
    print(value)
    

    计算结果:

    <QuerySet [('基督山伯爵', 1), ('包法利夫人', 2), ('小妇人', 1), ('神秘岛', 1), ('曼斯菲尔德花园', 2), 省略...]>
    

    SQL语句解析

    SELECT
        "app01_book"."title",
        COUNT(
            "app01_book_authors"."author_id"
        ) AS "author_count"
    FROM
        "app01_book"
    LEFT OUTER JOIN "app01_book_authors" ON (
        "app01_book"."id" = "app01_book_authors"."book_id"
    )
    GROUP BY
        "app01_book"."id",
        "app01_book"."title",
        "app01_book"."price",
        "app01_book"."publishDate",
        "app01_book"."publisher_id"
    LIMIT 21;
    

    解析:
    Book.objects.annotate(author_count=Count('authors'))
    拆分解析:
    Book.objects等同于Book.objects.all(),翻译成的sql类似于: select id,name,.. from Book
    这样得到的对象一定是每一本书对象,有n本书籍记录,就分n个组,不会有重复对象,每一组再由annotate分组统计。'''

    对所查询对象的关联对象进行聚合

    练习1:统计每一个出版社的最便宜的书
    方式一:

    value = Publish.objects.annotate(minBook=Min('book__price')).values_list('name','minBook')
    print(value)
    

    查询结果:

    <QuerySet [('人民出版社', Decimal('44.00')), ('人民教育出版社', Decimal('23.00')), ('机械工业出版社', Decimal('27.00')), 省略...]>
    

    注意:方式一,如果有一个出版社没有出版过书籍,那么值就是None

    方式二:

    value = Book.objects.values('publisher__name').annotate(minBook=Min('price'))
    print(value)
    

    查询结果:

    <QuerySet [{'publisher__name': '人民农业出版社', 'minBook': Decimal('59.00')}, {'publisher__name': '人民出版社', 'minBook': Decimal('44.00')}, ...]>
    

    解析:方式二中,查看 Book.objects.values("publish__name")的结果和对应的sql语句可以理解为values内的字段即group by的字段

    注意:方式二,如果某个出版社没有出版书籍,则不统计。

    以下练习不再附查询结果,仅看操作语法。

    练习2: 统计每一本以py开头的书籍的作者个数

    queryResult=Book.objects.filter(title__startswith="Py").annotate(num_authors=Count('authors'))
    

    练习3:统计不止一个作者的图书

    queryResult = Book.objects.annotate(num_authors=Count('authors')).filter(num_authors__gt=1)
    

    练习4:根据每本图书作者数量的多少对查询集 QuerySet进行排序

    Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')
    

    练习5:对每个作者参与写作的书籍数量从大到小排序

    author_list=Author.objects.annotate(books_num=Count('book__id')).order_by('-books_num')
    

    练习6:对每个出版社出版的书籍数量从大到小排序

    publish_list = Publish.objects.annotate(pubs_num=Count('book__id')).order_by('-pubs_num')
    

    练习7:查询各个作者出的书的总价格

    # 按author表的所有字段 "group by"
    queryResult=Author.objects.annotate(SumPrice=Sum("book__price")).values_list("name","SumPrice")
    print(queryResult)
      
        
    # 按authors__name "group by"
    queryResult2=Book.objects.values("authors__name").annotate(SumPrice=Sum("price")).values_list("authors__name","SumPrice")
    print(queryResult2)
    

    四、F查询与Q查询

    4.1 F查询

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

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

    例如:查询评论数大于收藏数的书籍

    # 先导入F
    from django.db.models import F
    Book.objects.filter(commnetNum__lt=F('keepNum'))
    

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

    例如:查询评论数大于收藏数2倍的书籍

    Book.objects.filter(commnetNum__lt=F('keepNum')*2)
    

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

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

    4.2 Q查询

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

    Q 对象可以使用操作符:

    &    表示逻辑与
    |    表示逻辑或
    ~    表示逻辑非(波浪号)
    

    Q 对象可以使用& 和| 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。

    例如:查询作者是Yuan或Egon的所有书籍

    # 需要先导入Q
    from django.db.models import Q
    bookList=Book.objects.filter(Q(authors__name="yuan")|Q(authors__name="egon"))
    

    等同于下面的SQL WHERE 子句:

    WHERE name ="yuan" OR name ="egon"
    

    你可以组合& 和| 操作符以及使用括号进行分组来编写任意复杂的Q 对象。同时,Q 对象可以使用~ 操作符取反,这允许组合正常的查询和取反(NOT) 查询:

    bookList=Book.objects.filter(Q(authors__name="yuan") & ~Q(publishDate__year=2017)).values_list("title")
    

    查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。例如:

    bookList=Book.objects.filter(Q(publishDate__year=2016) | Q(publishDate__year=2017),title__icontains="python")        
    

    五、修改表记录

    # 方式一:
    author=Author.objects.get(id=5)
    author.name="Tenglan"
    author.save()
    
    # 方式二:
    Author.objects.filter(id=5).update(name='Tenglan')
        # 过滤时不可用get(id=5)
    

    注意:

    1. 方式二修改不能用get()过滤的原因是:update()是QuerySet对象的方法,而get()返回的是一个model对象,它没有update()方法;filter()方法返回的是一个QuerySet对象,因为filter的过滤条件,可能查询出多条数据,如年龄为25岁的员工:filter(age=25),就有多个对象。
    1. 方式一中的save()方法,会更新一行数据的所有字段,不管那些字段值是否改变过,都更新一遍。而某些时候,我们只需要更新行里的某几个字段,就需要用update()方法。所以update()的效率要比save()的效率更高

    此外,update()方法对于任何结果集(QuerySet)均有效,这意味着你可以同时更新多条记录,update()方法会返回一个整型数值,表示受影响的记录条数。

    注意,这里因为update返回的是一个整形,所以没法用query属性;对于每次创建一个对象,想显示对应的raw sql,需要在settings加上日志记录部分。

    六、删除表记录

    删除方法就是 delete()。它运行时立即删除对象而不返回任何值。例如:e.delete()

    你也可以一次性删除多个对象。每个 QuerySet 都有一个 delete() 方法,它一次性删除 QuerySet 中所有的对象。例如:

    # 下面的代码将删除 pub_date 是2005年的 Entry 对象
    Entry.objects.filter(pub_date__year=2005).delete()
    

    要牢记这一点:无论在什么情况下,QuerySet 中的 delete() 方法都只使用一条 SQL 语句一次性删除所有对象,而并不是分别删除每个对象。如果你想使用在 model 中自定义的 delete() 方法,就要自行调用每个对象的delete 方法。(例如,遍历 QuerySet,在每个对象上调用 delete()方法),而不是使用 QuerySet 中的 delete()方法。

    在 Django 删除对象时,会模仿 SQL 约束 ON DELETE CASCADE 的行为,换句话说,删除一个对象时也会删除与它相关联的外键对象。例如:

    b = Blog.objects.get(pk=1)
    # This will delete the Blog and all of its Entry objects.
    b.delete()
    

    要注意的是: delete() 方法是 QuerySet 上的方法,但并不适用于 Manager 本身。这是一种保护机制,是为了避免意外地调用 Entry.objects.delete() 方法导致 所有的 记录被误删除。如果你确认要删除所有的对象,那么你必须显式地调用:

    Entry.objects.all().delete()
    

    如果不想级联删除,可以定义外键字段时设置为:

    pubHouse = models.ForeignKey(to='Publisher', on_delete=models.SET_NULL, blank=True, null=True)
    

    七、Django models中的meta选项

    通过一个内嵌类 "class Meta" 给你的 model 定义元数据
    ,具体请点击访问 Django models中的meta选项

    相关文章

      网友评论

        本文标题:第19天,Django之ORM进阶续

        本文链接:https://www.haomeiwen.com/subject/trwxbxtx.html