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出的书总价格                   

Django进阶(三)

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

Django进阶(三)

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

Django进阶(三)

--------------------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,可能
会造成额外的数据库查询。
上一篇:iOS 导航栏实现总结


下一篇:生产者与消费者+Queue(线程安全)