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,可能
会造成额外的数据库查询。
原文地址:https://www.cnblogs.com/idktp/p/6120052.html