Django中的ORM

1. 数据库的配置

Django可以配置使用sqlite3,mysql,oracle,postgresql等数据库

在一个Django项目中,默认使用的是sqlite3数据库

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',#默认使用的数据库引擎是sqlite3,项目自动创建
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),#指定数据库所在的路径
    }
}

如果想在一个Django项目中配置使用mysql数据库,可以使用如下配置:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',#表示使用的是mysql数据库的引擎
        'NAME': 'db1',      #数据库的名字,可以在mysql的提示符下先创建好
        'USER':'root',      #数据库用户名
        'PASSWORD':'',      #数据库密码
        'HOST':'',          #数据库主机,留空默认为"localhost"
        'PORT':'3306',      #数据库使用的端口
    }
}

配置好数据库的信息后还必须安装数据库的驱动程序

Django默认导入的mysql的驱动程序是MySQLdb,然而MySQLdb对于py3支持不全,所以这里使用PyMySQL

在项目名文件下的__init__.py文件中写入如下配置:

import pymysql
pymysql.install_as_MySQLdb()

2. ORM表模型

人的模型:每个人都有只属性自己的身份信息,包含生日,性别,身份证号等,每个人和他的信息是一对一的关系,

因此可以把一个人的所有身份信息汇总到一张数据表中,没必要拆分成两张表,这是一对一(one-to-one)的概念

书的模型:一本书有书名,出版日期,价格,所属的出版社等信息.一本书可以有多个作者来编写,一个作者也可以写作多本书,

书与作者是多对多的关联关系,这是多对多(many-to-many)的概念

同时,一本书只能由一个出版社出版,但是一个出版社可以出版多本书,所以出版社与书是一对多的关系,这是一对多(one-to-many)的概念

每个数据模型都是Django.db.models.Model的子类,它的父类Model包含了所有必要的和数据库交互的方法,并提供了一个简单的定义数据库字段的语法

每个模型相当于单个数据库表(多对多关系例外,会多生成一张关系表),每个属性都是数据表中的字段.

属性名就是字段名,其类型(例如CharField)相当于数据库的字段类型(例如varchar).

因此在Django的数据库中:

表名对应为python中的类名
字段对应python中的类属性
表中的每一条记录对应python中的类实例对象

数据模型的三种关系:

一对一模型:实质就是在主外键(foreign key)的关系基础上,给外键加了一个unique=True的属性
一对多模型:就是主外键关系(foreign key)
多对多模型:(ManyToManyField)自动创建第三张表,也可以手动创建第三张表:两个foreign key 

例子,创建一个包含一对一,一对多和多对多关系的数据表:

#创建一个书的类,继承models类
class Book(models.Model):

    #用models类创建书的名字,类型为字符串,CharField相当于mysql语句中的varchar,字段最长为32
    title = models.CharField(max_length=32)
	
    #创建书的价格,类型为浮点型,小数点前最长4位,小数点后最长2位
    price = models.DecimalField(max_digits=6, decimal_places=2)
	
    #创建书的出版社信息,其与出版社的外键关系为一对多,所以用外键
    publish = models.ForeignKey(Publish)
	
    #创建书的出版日期,类型为日期
    publication_date = models.DateField()
	
    #创建书的类型信息,为字符串类型,最长为20
    classification=models.CharField(max_length=20)
	
    #创建书的作者信息,书籍与作者的关系为多对多,所以使用many-to-many
    authors = models.ManyToManyField("Author")

3. ORM之增(create,save)

例如,为数据库中插入书的信息,有两种方式

3.1 使用create方式

方式一:

Publish.objects.create("name"="人民出版社",city="北京"}

方式二:

Publish.objects.create(**{"name":"文艺出版社","city":"上海"}}

3.2 使用save方式

方式一:

book1=Book(title="python",price="88",publish_id="1",publication_date="2017-06-18")
book1.save()

方式二:

author1=Author(name="jerry")
author1.save()

上面创建的都是一对一的信息

3.3 一对多的信息的创建(Foreignkey)

方式一:

#获取出版社对象
publish_obj=Publish.objects.get(id=4)   

#将出版社的对象绑定到书籍的记录中
Book.objects.create(
    title="python",
    price=48.00,
    publication_date="2017-07-12",
    publish=publish_obj,
)	

方式二:

#直接把出版社的id号插入到书籍的记录中
Book.objects.create(
    title="python",
    price=48.00,
    publish_id=2,
    publication_date="2017-06-18",
)

3.4 多对多信息的创建(ManyToManyField())

3.4.1 为一本书添加多个作者

author1=Author.objects.get(id=1)#获取id号为1的作者对象
author2=Author.objects.filter(name="tom")#获取名字为"tom"的作者对象
book1=Book.objects.get(id=2)#获取id号为2的书籍对象
book1.authors.add(author1,author2)#为书籍对象添加多个作者对象

也可以用这种方式:

book1.authors.add(*[author1,author2])#为书籍对象添加作者对象的列表
book1.authors.remove(*[author1,author2])#删除指定书籍的所有作者

3.4.2 为一个作者添加多本书

author_obj = Author.objects.filter(name="jerry")#获取名字为"jerry"的作者对象
book_obj=Book.objects.filter(id__gt=3)#获取id大于3的书籍对象集合
author_obj.book_set.add(*book_obj)#为作者对象添加书籍对象集合
author_obj.book_set.remove(*book_obj)#删除指定作者对象所有的书籍

使用models.ManyToManyField()会自动创建第三张表

3.5 手动创建多对多的作者与书籍信息表

class Book2Author(models.Models):
    author=models.ForeignKey("Author")#为作者指定Author这张表做为外键
    book=models.ForeignKey("Book")#为书籍指定Book这张表做为外键

author_obj=models.Author.objects.filter(id=3)[0]#获取Author表中id为3的作者对象
book_obj=models.Book.objects.filter(id=4)[0]#获取Book表中id为4的书籍对象

3.5.1 方式一:

obj1=Book2Author.objects.create(author=author_obj,book=book_obj)
obj1.save()

3.5.2 方式二:

obj2=Book2Author(author=author_obj,book=book_obj)
obj2.save()

4. ORM之删(delete)

语句格式:

Book.objects.filter(id=1).delete()

这个操作不仅会删除Book表中的一条记录,同时也会删除书籍与作者表中与Book相关联的记录,这种删除方式是Django默认的级联删除

5. ORM之查(filter,value)

5.1 方法

filter(**kwargs)            # 包含了与所给筛选条件相匹配的对象
all()                       # 查询所有结果
get(**kwargs)               # 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都是报错
values(*field)              # 返回一个ValueQuerySet,运行后得到的并不是一系列model的实例化对象,而是一个可迭代的字典序列
exclude(**kwargs)           # 包含了与所给的筛选条件不匹配的对象
order by(*field)            # 对查询结果排序
reverse()                   # 对查询结果反向排序
distinct()                  # 从返回结果中剔除重复记录
values_list(*field)         # 与values()非常相似,返回一个元组序列,values返回一个字典序列
count()                     # 返回数据库中匹配的记录的数量
first()                     # 返回数据库中匹配的对象的第一个对象
last()                      # 返回数据库中匹配的对象的最后一个对象
exists()                    # 判断一个对象集合中是否包含指定对象,包含返回True,不包含返回False

5.2 QuerySet与惰性机制

所谓惰性机制,Publisher.objects.all()或者.filter()等都只是返回一个QuerySet(查询结果集合对象)

其不会立即执行sql查询,而是当调用QuerySet的时候才会执行sq语句

5.2.1 QuerySet的特点

可迭代的
可切片

例子:

obj=Book.objects.all()#得到一个对象集合

for item in obj:#对QuerySet进行迭代,每一个item就是一个行对象
    print("item":,item)
	
    #对QuerySet进行切片
    print(obj[1])
    print(obj[1:4])
    print(obj[::-1])

5.2.2 Django的QuerySet是惰性的

Django的QuerySet对应于数据库的记录,通过设定的条件进行过滤.

一个简单的查询并不会运行任何的数据库查询.只有遍历QuerySet或者使用if语句的时候,才会执行sql语句

例子:

book_obj=Book.objects.filter(id=3)
for i in book_ojb:#到这一步才会真正执行sql查询
    print(i)
	
if book_obj:#到这一步才会真正执行sql查询
    print("ok")

5.2.3 QuerySet是具有cache的

当遍历QuerySet时,会从数据库中获取匹配的记录,然后转换成Django的model,此时执行sql语句

这些model会保存在QuerySet内置的cache中,这样如果你再次遍历整个QuerySet,就不会再次执行sql查询

例子:

book_obj=Book.objects.filter(id=3)

for i in book_obj:
    print(i)
	
for i in book_obj:#执行两次遍历,结果只会打印一次结果
    print(i)	

5.2.4 简单的使用if语句进行判断也会完全执行整个QuerySet并且把数据放入cache,可以使用exists()方法来判断是否有数据

例子:

book_obj=Book.objects.filter(id=4)#获取Book表中id为4的对象

if book_obj.exists():#exists()的检查可以避免数据放入QuerySet的cache中
    print("hello world")

5.2.5 当处理的记录数量很大时,cache会占用很多内存

巨大的QuerySet可能会锁住系统进程,使用程序崩溃.避免在遍历数据的同时产生QuerySet的cache,可以使用iterator()方法来获取数据,等到数据迭代并处理完就会被丢弃

例子:

book_obj=Book.objects.all().iterator()#iterator()每次只从数据库中取出少量数据,以节省内存

for obj in book_obj:#第一次遍历,,打印每本书的名字
    print(obj.name)
	
for obj in book_obj:#打印第二次,因为迭代器已经在第一次遍历到最后了,此次遍历不会打印
    print(obj.name)

使用iterator()方法来防止生成cache,意味着遍历同一个QuerySet时会重复执行查询.

所以使用iterator()时,要确保代码在操作一个大的QuerySet时没有执行重复的迭代

5.2.6 QuerySet的cache是用于减少程序对数据库的查询,在通常情况下会保证在需要的时候才会查询数据库.

使用exists()和iterator()方法可以优化程序对内存的使用,但是exists()和iterator()不会生成cache,可能会造成额外的数据库查询

5.3 对象查询

5.3.1 正向查找

res1=Book.objects.first()
print(res1.title)
print(res1.price)
print(res1.publish)
print(res1.publisher.name)#因为一对多的关系,所以res1.publisher是一个对象,不是一个QuerySet集合

5.3.2 反向查找

res2=Publish.objects.last()
print(res2.name)
print(res2.city)
print(res2.book_set.all())#res2.book_set是一个QuerySet集合,所以会打印集合中的所有对象元素

5.4 双下划线(__)查询

5.4.1 双下划线(__)之单表条件查询

例子:

table1.objects.filter(id__lt=10,id__gt=1)#获取id小于10,且大于1的记录
table1.objects.filter(id__in=[11,22,33,44])#获取id在[11,22,33,44]中的记录
table1.objects.exclude(id__in=[11,22,33,44])#获取id不在[11,22,33,44]中的记录
table1.objects.filter(name__contains="content1")#获取name中包含有"contents"的记录(区分大小写)
table1.objects.filter(name__icontains="content1")#获取name中包含有"content1"的记录(不区分大小写)

table1.objects.filter(id__range=[1,4])#获取id在1到4(不包含4)之间的的记录

可使用的条件:

startswith			# 指定开头的匹配条件
istartswith			# 指定开头的匹配条件(忽略大小写)
endswith			# 指定结束的匹配条件
iendswith			# 指定结束的匹配条件(忽略大小写)

5.4.2 双下划线(__)之多表条件查询

正向查找(条件)之一对一查询

#查询书名为"python"的书的id号
res3=Book.objects.filter(title="python").values("id")
print(res3)

正向查找(条件)之一对多查询

#查询书名为"python"的书对应的出版社的地址
res4=Book.objects.filter(title="python").values("publisher__city")
print(res4)

#查询"aaa"作者所写的所有的书的名字
res5=Book.objects.filter(author__name="aaa").values("title")
print(res5)

#查询"aaa"作者所写的所有的书的名字(与上面的用法没区别)
res6=Book.objects.filter(author__name="aaa").values("title")
print(res6)

反向查找之一对多查询

#查询出版了书名为"python"这本书的出版社的名字
res7=Publisher.objects.filter(book__title="python").values("name")
print(res7)

#查询写了书名为"python"的作者的名字
res8=Publisher.objects.filter(book__title="python").values("book__authors")
print(res8)

反向查找之多对多查询

#查询所写的书名为"python"的作者的名字
res9=Author.objects.filter(bool__title="python").values("name")
print(res9)

条件查询即与对象查询对应,是指filter,values等方法中的通过__来明确查询条件

5.4 聚合查询和分组查询

5.4.1 aggregate(*args,**kwargs)

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

例子:

from django.db.models import Avg,Max,Min,Sum

#计算所有书籍的平均价格,书籍最高的价格和最低价格
res1=Book.objects.all().aggregate(Avg("price"),Max("price"),Min("price"))
print(res1)#打印为"{'price__avg':xxx,'price__max':xxx,'price__min':xxx}"

Django的查询语句提供了一种方式描述所有图书的集合

aggregate()子句的参数可以指定想要计算的聚合值.
aggregate()是QuerySet的一个终止子句,返回一个包含一些键值对的字典.
	字典的键的名称是聚合值的标识符,是按照字段和聚合函数的名称自动生成出来的.
	字典的值是计算出来的聚合值.

可以为聚合值指定一个名称.

#计算所有书籍的平均价格,并给书籍的平均价格起一个别名
res2=Book.objects.all().aggregate(average__price=Avg("price"))
print(res2)#打印为"{'average_price':xxx}"

5.4.2 annotate(*args,**kwargs)

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

#查询作者"aaa"所写的所有的书的名字
res3=Book.objects.filter(authors__name="aaa").values("title")
print(res3)

#查询作者"bbb"所写的所有的书的总价格
res4=Book.objects.filter(authors__name="bbb").aggregate(Sum("price"))
print(res4)

查询各个作者所写的书的总价格,就要使用分组

#查询每个作者所写的所有书籍的总价格
res5=Book.objects.values("authors__name").annotate(Sum("price"))
print(res5)

#查询各个出版社所出版的书籍的总价格
res6=Book.objects.values("Publish__name").annotate(Min("price"))
print(res6)

5.5 F查询和Q查询

5.5.1 F查询专门取对象中某列值的操作

#导入F
from django.db.models import F
#把table1表中的num列中的每一个值在的基础上加10
table1.objects.all().update(num=F("num")+10)

5.5.2 Q构建搜索条件

#导入Q
from django.db.models import Q

Q对象可以对关键字参数进行封装,从而更好的应用多个查询
#查询table2表中以"aaa"开头的所有的title列
q1=table2.objects.filter(Q(title__startswith="aaa")).all()
print(q1)

Q对象可以组合使用&,|操作符,当一个操作符是用于两个Q对象时,会产生一个新的Q对象

#查找以"aaa"开头,或者以"bbb"结尾的所有title
Q(title__startswith="aaa") | Q(title__endswith="bbb")

Q对象可以用"~"操作符放在表达式前面表示否定,也可允许否定与不否定形式的组合

#查找以"aaa"开头,且不以"bbb"结尾的所有title
Q(title__startswith="aaa") & ~Q(title__endswith="bbb")

Q对象可以与关键字参数查询一起使用,Q对象放在关键字查询参数的前面

查询条件:

#查找以"aaa"开头,以"bbb"结尾的title且书的id号大于4的记录
Q(title__startswith="aaa") | Q(title__endswith="bbb"),book_id__gt=4

6. ORM之改(update,save)

6.1 使用save方法将所有属性重新设定一遍,效率低

author1=Author.objects.get(id=3)#获取id为3的作者对象
author1.name="jobs"#修改作者对象的名字
author1.save()#把更改写入数据库

6.2 使用update方法直接设置对就的属性

Publish.objects.filter(id=2).update(name="北京出版社")

6.3 需要注意的点

update()是QuerySet对象的一个方法,get返回的是一个model对象,其没有update方法.
filter返回的是一个QuerySet对象,filter里可以设定多个过滤条件

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄