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,可能 会造成额外的数据库查询。