• Django进阶(三)


    ORM

    众所周知有很多不同的数据库系统,并且其中的大部分系统都包含Python接口,能够让我们更好的利用它们的功能,而这些系统唯一的缺点就是需要你了解SQL,如果你是一个更愿意操纵Python对象,而不是SQL查询的程序员,并且仍然希望使用关系数据库作为你的数据后端,那么我们可以使用ORM。

    这些ORM系统的作者将纯SQL语句进行了抽象化处理,将其实现为Python中的对象,这样我们只操作对象就能完成与生成SQL语句相同的任务。就是用面向对象的方式去操作数据库的创建表以及增删改查等操作。

    ORM优点:

    1 ORM使得我们的通用数据库交互变得简单易行,而且完全不用考虑该死的SQL语句。快速开发,由此而来。

     2 可以避免一些新手程序猿写sql语句带来的性能问题。

    ORM缺点:

    1  性能有所牺牲,不过现在的各种ORM框架都在尝试各种方法,比如缓存,延迟加载登来减轻这个问题。效果很显著。

    2  对于个别复杂查询,ORM仍然力不从心,为了解决这个问题,ORM一般也支持写raw sql。

    3  通过QuerySet的query属性查询对应操作的sql语句

    Model

    下面要开始学习Django ORM语法了

    单表的操作(增删改查)

    --------------------增

    from blog.models import *  # 首先导入应用里的models.py里的所有class(数据库里的各个表)
    
    
    def base(request): 
        # 增:
        # 方法一(推荐)
        # 固定结构: 类名(表名).objectes.create(**{"字段名1":"插入的字段内容","字段名2":"插入的字段内容"})
        Author.objects.create(**{"name": "liu"})
    
        # 方法二
        # 固定结构: 类名(表名).objectes.create(字段名1="插入的字段内容",字段名2="插入的字段内容")
        # 注意:字段名不加引号
        Author.objects.create(name="liu2")
    
        # 方法三
        author = Author(name="liu3")  # 实例化类(要操作的表)的对象并直接赋值
        author.save()  # 保存表的内容
    
        # 方法四
        author = Author()  # 实例化类(要操作的表)的对象
        author.name = "liu4"  # 逐个给对象的属性赋值
        author.save()  # 最后保存

    --------------------查

    # User2是blog.models中的一个类(表)
    
    
        # 方法一 filter
        # 固定结构: 类名(表名).objectes.filter(判断条件)
        user2_list = User2.objects.filter(name = "liu",sex="")
        # filter括号内添加查询条件,多个条件用逗号隔开User2.objects.filter(name = "liu2",sex = "男")
        # 将所有满足条件的对象集合成QuerySet对象返回:<QuerySet [<User2: User2 object>, <User2: User2 object>]>
        # 即使只有一个对象满足条件也会返回QuerySet对象<QuerySet [<User2: User2 object>]>
        # 可以将QuerySet对象理解成一个list 可以通过索引获取单个对象:user2_list[0]
        # 当没有满足条件的对象则会返回一个空的QuerySet对象:<QuerySet []>
        # 获取到单个对象后可以通过 .字段名 获取该字段对应的内容 user2_list[0].name
    
        # 方法二 get
        # 固定结构: 类名(表名).objectes.get(判断条件)
        user2 = User2.objects.get(name = "liu2")
        # 只能获取到单个满足条件的对象,返回结果有且只有一个
        # 如果符合筛选条件的对象超过一个,或者没有都会抛出错误
        # 直接通过 .字段名 获取该字段对应的内容
    
        # 方法三 all
        # 固定结构: 类名(表名).objectes.all()
        user2_list = User2.objects.all()
        # 将该表(User2)的所有数据集合成queryset对象返回
    
        # 方法四 exclude
        # 固定结构: 类名(表名).objectes.exclude(判断条件)
        user2_list = User2.objects.exclude(name = "liu2")
        # 与filter正好相反,返回的结果是所有不满足括号内条件的对象的集合,返回的是QuerySet对象
    
    
        # -----------下面的方法都是对查询的结果进行处理再返回
    
        # values("字段名")
        User2.objects.filter(name = "liu2").values("sex")
        # QuerySet对象中只是想要获取某个字段,而不是全部的字段,将该字段放入 values("字段名")
        # 获取到QuerySet对象 列表包含字典的形式 < QuerySet[{'sex': '男'}, {'sex': '男'}]>
    
        # order_by("字段名")
        # 对查询结果按照 括号内的字段 从大到小排序,如果想要从小到大排序,则在引号内的字段名前添加一个减号
        User2.objects.filter(name="liu").order_by("-sex")
    
        # .reverse()
        User2.objects.filter(name="liu").reverse()
        # 对查询结果进行倒序,可以配合order_by使用
    
        # distinct()
        User2.objects.filter(name="liu").values("sex").distinct()
        # 剔除查询结果中完全相同的数据
    
        # count()
        User2.objects.filter(name="liu").count()
        # 返回查询结果(QuerySet)中包含的对象数量
    
        # first():  返回查询结果(QuerySet)中第一个对象
    
        # last():   返回查询结果(QuerySet)中最后一个对象
    
        # exists():  如果QuerySet包含数据,就返回True,否则返回False。

    --------------------删

    基于查的基础上进行删除,先查找到要删除的数据,然后进行删除

    delete()
    Author.objects.filter(name = "liu").delete()

    删除一条数据,那么数据库中所有与该条数据相关的数据都会被删除,级联删除

    --------------------改

    基于查的基础上进行修改,先查找到要修改的数据,然后进行修改

        # update() 括号内添加要修改的字段及内容 sex = "aaa" 修改多个字段用逗号分隔开
        User2.objects.filter(name="liu2").update(sex = "")

     关联表操作

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

    作者模型:一个作者有姓名。

    作者详细模型:把作者的详情放到详情表,包含性别,email地址和出生日期,作者详情模型和作者模型之间是一对一的关系(one-to-one)(类似于每个人和他的身份证之间的关系),在大多数情况下我们没有必要将他们拆分成两张表,这里只是引出一对一的概念。

    出版商模型:出版商有名称,地址,所在城市,省,国家和网站。

    书籍模型:书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many),一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many),也被称作外键。

    书籍与作者:多对多关系,书籍与出版商:一对多关系,作者与出版商:无关系

    创建表

    from django.db import models
    
    
    
    class Publisher(models.Model):
        name = models.CharField(max_length=30, verbose_name="名称")
        address = models.CharField("地址", max_length=50)
        city = models.CharField('城市', max_length=60)
        state_province = models.CharField(max_length=30)
        country = models.CharField(max_length=50)
        website = models.URLField()
    
        class Meta:
            verbose_name = '出版商'
            verbose_name_plural = verbose_name
    
        def __str__(self):
            return self.name
    
    
    class Author(models.Model):
        name = models.CharField(max_length=30)
    
        def __str__(self):
            return self.name
    
    
    class AuthorDetail(models.Model):
        sex = models.BooleanField(max_length=1, choices=((0, ''), (1, ''),))
        email = models.EmailField()
        address = models.CharField(max_length=50)
        birthday = models.DateField()
        author = models.OneToOneField(Author)
        # 一对一的关系 当表中存在两个相同的author时会报错
    
    
    class Book(models.Model):
        title = models.CharField(max_length=100)
        authors = models.ManyToManyField("Author")
        # 创建多对多关系的第三章表
        # models.ManyToManyField(Author) 括号里放入那个与该表有“多对多”关系的表名
        # 规定是一本书可以由多个作者共同完成,而一个作者又可以完成多本书,所以书与作者的关系是“多对多”
        # “多对多”的关系只有依靠第三章表才会完美体现两个表中的关系,而ManyToManyField(Author)会自动帮我们创建第三张表
        # 也可以在Author表里写 models.ManyToManyField(Book) 自动创建第三张表
    
    
        # 外键中加引号则 外键相关联的Publisher表不一定非要建在Book表之前,不加引号则必须建在该表之前
        # 是根据反射找到的Publisher表
        publishersss = models.ForeignKey("Publisher")
        # models.ForeignKey(Publisher) 括号里放入你想建立主外键的另一个数据表的表名(对应的主键的表的名称)
        # 规定是一本书只能由一家出版社出版,而一家出版社可以出版多本书,所以书与出版社之间的关系就是“多对一”的关系
        # 多对一的关系中应该在“多”的那个表里创建外键,所以这里添加外键
        # 我们写的字段是publisher 实际上django帮我们存入数据库中时自动存储成了publisher_id字段, 这是models.ForeignKey()的特殊性
    
        publication_date = models.DateField()
        price = models.DecimalField(max_digits=5, decimal_places=2, default=10)
    
        def __str__(self):
            return self.title
    
    
    
    class User2(models.Model):
        name = models.CharField(max_length=30)
        sex = models.CharField(max_length=30)

     表中插入数据

    from blog.models import * # 首先导入应用里的models.py里的所有class(数据库里的各个表)

    # Author表 Author.objects.create(**{ "name":"zhangsan" }) # AuthorDetail表 # 字段:sex email address birthday author # 先获取外键author所要绑定的Author对象author3 author3 = Author.objects.filter(name = "gaoer")[0] AuthorDetail.objects.create(**{ "sex":True, "email":"WANGWU@qq.com", "address":"中国北京", "birthday":"1991-10-24", "author":author3 # 一对一关系中 author 赋值 Author对象author3 }) # Publisher表 # 字段:name address city state_province country website Publisher.objects.create(**{ "name": "出版社002", "address": "地址002", "city": "城市002", "state_province": "省份002", "country": "国家002", "website": "网站002", }) # Book表 # 字段:title authors publisher publication_date price # 先获取要建立关系的Publisher对象 publisher2 = Publisher.objects.filter(id = 3)[0] Book.objects.create(**{ "title":"书籍005", "publishersss":publisher2, # 一对多关系中 直接在外键publishersss 赋值 publisher对象 "publication_date":"2016-08-09", "price":50 }) #多对多 建立关系 先获取要绑定的Author对象 authors8 =Author.objects.filter(id = 5)[0] #先获取要绑定的Book对象 book001 = Book.objects.filter(title="书籍005")[0] # Book对象.关系字段.add(Author对象) book001.authors.add(authors8) # 由于我们是在Book表中与Author建立的ManyToManyField关系,所以是 Book对象.Book表中的关系字段名.add(Author对象) # 如果是在Author表中与Book建立的ManyToManyField关系,那么该是 Author对象.Author表中的关系字段名.add(Book对象)

     --------------------关联表查询

       #----------------关联查找之“一对多”的关系
        # 双下划线可以理解为只是一个判断条件,任何有关联的表都可以查到
        # 固定写法:外键名__外键关联表的字段名
        # Book.objects.filter(publishersss__name="出版社001")[0]
        # Publisher.objects.filter(book__title="书籍001")
    
        # 都是已知A表中的数据,查找与他相关的B表中的数据
    
        # 当A表中的某行数据只能绑定B表中的一行数据时(一个Book只能对应一个Publisher)
        # 直接 .外键名称
        # book = Book.objects.filter(id = 1)[0]
        # publisher = book.publishersss
        # 由于是“一对多”的关系 所以publisher是一个对象 而不是QuerySet对象
    
        # 当A表中的某行数据可以绑定多个B表中的数据(一个Publisher对应多个Book)
        # 需要用到 表名_set  固定写法:表名_set
        # publisher =Publisher.objects.filter(id=1)[0]
        # book=publisher.book_set.all() #  获取到QuerySit对象集合
        # .book_set 跳转到与Publisher对象关联的book表
        #  Publisher.objects.filter(id=1)[0].book_set   相当于  Book.objects 只不过都是与id=1的publisher绑定的书的对象
        #  之后可以根据查询语法查询了 .filter .all .get
    
        # ----------------关联查找之“多对多”的关系
    
        # 根据书的ID找到对应的作者 是个QuerySet对象,拿到第一个作者的作者信息.authordetail
        # author = Author.objects.filter(book__id=1)[0].authordetail
    
        # -----QuerySet对象.values("相关联的表外键字段名__相关联的表的字段名")
        # 取到与之相关联对象的字段,不加“__字段名” 默认取到与之相关联对象的主键
        # Book.objects.filter(title='书籍001').values('publishersss__city')[0]
        # -----QuerySet对象.values("表名__外键字段名")
        # A表和B表有关联,B表和C表有关联,A和C无关联,已知A,查到与A有关联的B再查到与B有关联的C
        # A对象点.values('共同关联B表名__B中关联C的外键字段名')  得到C表主键
        # Author.objects.filter(id=1).values('book__publishersss')

     --------------------聚合查询:

    通过对QuerySet进行计算,返回一个聚合值的字典。aggregate()中每一个参数都指定一个包含在字典中的返回值。即在查询集上生成聚合。

    首先导入模块 from django.db.models import aggregates, Avg, Sum, Max, Min

    # 从整个查询集生成统计值。比如,你想要计算所有在售书的平均价钱。Django的查询语法提供了一种方式描述所有图书的集合。
        # 得到字典形式结果
        # 默认:
        a = Book.objects.all().aggregate(Avg("price"))
        print(a)  # 执行结果{'price__avg': 60.0}
        # aggregate()子句的参数描述了我们想要计算的聚合值,在这个例子中,是Book模型中price字段的平均值
        # aggregate()是QuerySet的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。
       # 键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它:
    a = Book.objects.all().aggregate(pingjunjiage=Avg("price")) print(a) # 执行结果{'pingjunjiage': 60.0} # 如果你也想知道所有图书价格的最大值和最小值,可以这样查询: a = Book.objects.aggregate(Avg('price'), Max('price'), Min('price'), Sum("price")) print(a) # 执行结果{'price__max': Decimal('80.00'), 'price__avg': 60.0, 'price__sum': Decimal('240.00'), 'price__min': Decimal('50.00')}

     --------------------分组查询:

    可以通过计算查询结果中每一个对象所关联的对象集合,从而得出总计值(也可以是平均值或总和),即为查询集的每一项生成聚合。

        查询alex出的书总价格                   

           

            查询各个作者出的书的总价格,这里就涉及到分组了,分组条件是authors__name

               

             查询各个出版社最便宜的书价是多少

           

     --------------------F查询和Q查询

    
    
        仅仅靠单一的关键字参数查询已经很难满足查询要求。此时Django为我们提供了F和Q查询:
    
        # F 使用查询条件的值,专门取对象中某列值的操作
    
        # from django.db.models import F
        # models.Tb1.objects.update(num=F('num')+1)
    
    
        # Q 构建搜索条件
        from django.db.models import Q
    
        # 1 Q对象(django.db.models.Q)可以对关键字参数进行封装,从而更好地应用多个查询
        q1 = models.Book.objects.filter(Q(title__startswith='P')).all()
        print(q1)  # [<Book: Python>, <Book: Perl>]
    
        # 2、可以组合使用&,|操作符,当一个操作符是用于两个Q的对象,它产生一个新的Q对象。
        Q(title__startswith='P') | Q(title__startswith='J')
    
        # 3、Q对象可以用~操作符放在前面表示否定,也可允许否定与不否定形式的组合
        Q(title__startswith='P') | ~Q(pub_date__year=2005)
    
        # 4、应用范围:
    
        # Each lookup function that takes keyword-arguments (e.g. filter(),
        #  exclude(), get()) can also be passed one or more Q objects as
        # positional (not-named) arguments. If you provide multiple Q object
        # arguments to a lookup function, the arguments will be “AND”ed
        # together. For example:
    
        Book.objects.get(
            Q(title__startswith='P'),
            Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
        )
    
        # sql:
        # SELECT * from polls WHERE question LIKE 'P%'
        #     AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
    
        # import datetime
        # e=datetime.date(2005,5,6)  #2005-05-06
    
        # 5、Q对象可以与关键字参数查询一起使用,不过一定要把Q对象放在关键字参数查询的前面。
        # 正确:
        Book.objects.get(
            Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
            title__startswith='P')
        # 错误:
        Book.objects.get(
            question__startswith='P',
            Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)))
    
    
    
    

    QuerySet

    QuerySet对象特点:

    1. 可迭代的

    2.可切片

    #objs=models.Book.objects.all()#[obj1,obj2,ob3...]
    
        #QuerySet:   可迭代
    
        # for obj in objs:#每一obj就是一个行对象
        #     print("obj:",obj)
        # QuerySet:  可切片
    
        # print(objs[1])
        # print(objs[1:4])
        # print(objs[::-1])

     QuerySet对象的高效使用:

    <1>Django的queryset是惰性的
    
         Django的queryset对应于数据库的若干记录(row),通过可选的查询来过滤。例如,下面的代码会得
         到数据库中名字为‘Dave’的所有的人:person_set = Person.objects.filter(first_name="Dave")
         上面的代码并没有运行任何的数据库查询。你可以使用person_set,给它加上一些过滤条件,或者将它传给某个函数,
         这些操作都不会发送给数据库。这是对的,因为数据库查询是显著影响web应用性能的因素之一。
    
    <2>要真正从数据库获得数据,你可以遍历queryset或者使用if queryset,总之你用到数据时就会执行sql.
       为了验证这些,需要在settings里加入 LOGGING(验证方式)
            obj=models.Book.objects.filter(id=3)
            # for i in obj:
            #     print(i)
    
            # if obj:
            #     print("ok")
    
    <3>queryset是具有cache的
         当你遍历queryset时,所有匹配的记录会从数据库获取,然后转换成Django的model。这被称为执行
        (evaluation).这些model会保存在queryset内置的cache中,这样如果你再次遍历这个queryset,
         你不需要重复运行通用的查询。
            obj=models.Book.objects.filter(id=3)
    
            # for i in obj:
            #     print(i)
                              ## models.Book.objects.filter(id=3).update(title="GO")
                              ## obj_new=models.Book.objects.filter(id=3)
            # for i in obj:
            #     print(i)   #LOGGING只会打印一次
    
    <4>
         简单的使用if语句进行判断也会完全执行整个queryset并且把数据放入cache,虽然你并不需要这些
         数据!为了避免这个,可以用exists()方法来检查是否有数据:
    
                obj = Book.objects.filter(id=4)
                #  exists()的检查可以避免数据放入queryset的cache。
                if obj.exists():
                    print("hello world!")
    
    <5>当queryset非常巨大时,cache会成为问题
    
         处理成千上万的记录时,将它们一次装入内存是很浪费的。更糟糕的是,巨大的queryset可能会锁住系统
         进程,让你的程序濒临崩溃。要避免在遍历数据的同时产生queryset cache,可以使用iterator()方法
         来获取数据,处理完数据就将其丢弃。
            objs = Book.objects.all().iterator()
            # iterator()可以一次只从数据库获取少量数据,这样可以节省内存
            for obj in objs:
                print(obj.name)
            #BUT,再次遍历没有打印,因为迭代器已经在上一次遍历(next)到最后一次了,没得遍历了
            for obj in objs:
                print(obj.name)
    
         #当然,使用iterator()方法来防止生成cache,意味着遍历同一个queryset时会重复执行查询。所以使
         #用iterator()的时候要当心,确保你的代码在操作一个大的queryset时没有重复执行查询
    
    总结:
        queryset的cache是用于减少程序对数据库的查询,在通常的使用下会保证只有在需要的时候才会查询数据库。
    使用exists()和iterator()方法可以优化程序对内存的使用。不过,由于它们并不会生成queryset cache,可能
    会造成额外的数据库查询。
  • 相关阅读:
    【算法】CDQ分治 -- 三维偏序 & 动态逆序对
    【题解】ZJOI2007报表统计
    【乱七八糟】兰陵王
    【题解】NOI2017游戏
    【题解】JSOI2010满汉全席
    【题解】NOI2014动物园
    【题解】HNOI2010合唱队
    【题解】SDOI2010地精部落
    cf 843 D Dynamic Shortest Path [最短路+bfs]
    cf 843 B Interactive LowerBound [随机化]
  • 原文地址:https://www.cnblogs.com/idktp/p/6120052.html
Copyright © 2020-2023  润新知