06 django模型之多表操作
一、创建表结构
作者与作者详细信息是一对一的关系(onetoone)
出版社与书的关系是一对多(onetomany)
书与作者的关系是多对多(manytomany)
建立的表结构如下:
from django.db import models # Create your models here. class Author(models.Model): #比较常用的信息放到这个表里面 nid = models.AutoField(primary_key=True) name=models.CharField( max_length=32) age=models.IntegerField() # 与AuthorDetail建立一对一的关系,一对一的这个关系字段写在两个表的任意一个表里面都可以 authorDetail=models.OneToOneField(to="AuthorDetail",to_field="nid",on_delete=models.CASCADE) #就是foreignkey+unique,只不过不需要我们自己来写参数了,并且orm会自动帮你给这个字段名字拼上一个_id,数据库中字段名称为authorDetail_id class AuthorDetail(models.Model):#不常用的放到这个表里面 nid = models.AutoField(primary_key=True) birthday=models.DateField() telephone=models.BigIntegerField() addr=models.CharField( max_length=64) class Publish(models.Model): nid = models.AutoField(primary_key=True) name=models.CharField( max_length=32) city=models.CharField( max_length=32) email=models.EmailField() #多对多的表关系,我们学mysql的时候是怎么建立的,是不是手动创建一个第三张表,然后写上两个字段,每个字段外键关联到另外两张多对多关系的表,orm的manytomany自动帮我们创建第三张表,两种方式建立关系都可以,以后的学习我们暂时用orm自动创建的第三张表,因为手动创建的第三张表我们进行orm操作的时候,很多关于多对多关系的表之间的orm语句方法无法使用#如果你想删除某张表,你只需要将这个表注销掉,然后执行那两个数据库同步指令就可以了,自动就删除了。 class Book(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField( max_length=32) publishDate=models.DateField() price=models.DecimalField(max_digits=5,decimal_places=2) # 与Publish建立一对多的关系,外键字段建立在多的一方,字段publish如果是外键字段,那么它自动是int类型 publish=models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE) #foreignkey里面可以加很多的参数,都是需要咱们学习的,慢慢来,to指向表,to_field指向你关联的字段,不写这个,默认会自动关联主键字段,on_delete级联删除 字段名称不需要写成publish_id,orm在翻译foreignkey的时候会自动给你这个字段拼上一个_id,这个字段名称在数据库里面就自动变成了publish_id # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表,并且注意一点,你查看book表的时候,你看不到这个字段,因为这个字段就是创建第三张表的意思,不是创建字段的意思,所以只能说这个book类里面有authors这个字段属性 authors=models.ManyToManyField(to='Author',) #注意不管是一对多还是多对多,写to这个参数的时候,最后后面的值是个字符串,不然你就需要将你要关联的那个表放到这个表的上面
1、创建多对多关系第三张表方式
方式一:自己创建第三张表
class Book(models.Model): title = models.CharField(max_length=32, verbose_name="书名") class Author(models.Model): name = models.CharField(max_length=32, verbose_name="作者姓名") # 自己创建第三张表,分别通过外键关联书和作者 class Author2Book(models.Model): author = models.ForeignKey(to="Author") book = models.ForeignKey(to="Book") class Meta: unique_together = ("author", "book")
方式二:通过ManyToManyField创建表
class Book(models.Model): title = models.CharField(max_length=32, verbose_name="书名") # 通过ORM自带的ManyToManyField自动创建第三张表 class Author(models.Model): name = models.CharField(max_length=32, verbose_name="作者姓名") books = models.ManyToManyField(to="Book", related_name="authors") #自动生成的第三张表我们是没有办法添加其他字段的
方式三:设置ManyToManyField并指定自己创建第三张表(中介模式)
class Book(models.Model): title = models.CharField(max_length=32, verbose_name="书名") # 自己创建第三张表,并通过ManyToManyField指定关联 class Author(models.Model): name = models.CharField(max_length=32, verbose_name="作者姓名") books = models.ManyToManyField(to="Book", through="Author2Book", through_fields=("author", "book")) # through_fields接受一个2元组('field1','field2'): # 其中field1是定义ManyToManyField的模型外键的名(author),field2是关联目标模型(book)的外键名。 class Author2Book(models.Model): author = models.ForeignKey(to="Author") book = models.ForeignKey(to="Book") #可以扩展其他的字段了 class Meta: unique_together = ("author", "book")
注意:
当我们需要在第三张关系表中存储额外的字段时,就要使用第三种方式,第三种方式还是可以使用多对多关联关系操作的接口(all、add、clear等等)
当我们使用第一种方式创建多对多关联关系时,就无法使用orm提供的set、add、remove、clear方法来管理多对多的关系了。
2、参数
2.1、创建一对一关系字段的一些参数
to
设置要关联的表。
to_field
设置要关联的字段。
on_delete
同ForeignKey字段。
2.2、创建一对多关系字段的一些参数
to 设置要关联的表 to_field 设置要关联的表的字段 related_name 反向操作时,使用的字段名,用于代替原反向查询时的'表名_set'。 related_query_name 反向查询操作时,使用的连接前缀,用于替换表名。 on_delete 当删除关联表中的数据时,当前表与其关联的行的行为。
2.3、创建多对多关系字段的一些参数
多对多的参数:
to
设置要关联的表
related_name
同ForeignKey字段。
related_query_name
同ForeignKey字段。
through
在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。
但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过
through来指定第三张表的表名。
through_fields
设置关联的字段。
db_table
默认创建第三张表时,数据库中表的名称。
2.4、创建表时的一些元信息设置
元信息 ORM对应的类里面包含另一个Meta类,而Meta类封装了一些数据库的信息。主要字段如下: class Author2Book(models.Model): author = models.ForeignKey(to="Author") book = models.ForeignKey(to="Book") class Meta: unique_together = ("author", "book") db_table ORM在数据库中的表名默认是 app_类名,可以通过db_table可以重写表名。db_table = 'book_model' index_together 联合索引。 unique_together 联合唯一索引。 ordering 指定默认按什么字段排序。 ordering = ['pub_date',] 只有设置了该属性,我们查询到的结果才可以被reverse(),否则是能对排序了的结果进行反转(order_by()方法排序过的数据)
2.5、关于on_delete
on_delete
当删除关联表中的数据时,当前表与其关联的行的行为。
models.CASCADE
删除关联数据,与之关联也删除
models.DO_NOTHING
删除关联数据,引发错误IntegrityError
models.PROTECT
删除关联数据,引发错误ProtectedError
models.SET_NULL
删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
models.SET_DEFAULT
删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)
models.SET
删除关联数据,
a. 与之关联的值设置为指定值,设置:models.SET(值)
b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)
2.6、ForeignKey的db_contraint
ForeignKey的db_contraint参数 关系和约束大家要搞清楚,我不加外键能不能表示两个表之间的关系啊,当然可以 但是我们就不能使用ORM外键相关的方法了,所以我们单纯的将外键换成一个其他字段类型,只是单纯的存着另外一个关联表的主键值是不能使用ORM外键方法的。 #db_constraint=False只加两者的关系,没有强制约束的效果,并且ORM外键相关的接口(方法)还能使用,所以如果将来公司让你建立外键,并且不能有强制的约束关系,那么就可以将这个参数改为False customer = models.ForeignKey(verbose_name='关联客户', to='Customer',db_constraint=False)
二、添加表记录
1、一对多
方式1: publish_obj=Publish.objects.get(nid=1) #拿到nid为1的出版社对象 book_obj=Book.objects.create(title="西游记",publishDate="2012-12-12",price=100,publish=publish_obj) 方式2: book_obj=Book.objects.create(title="金瓶眉",publishDate="2012-12-12",price=100,publish_id=1) #直接可以写id值,注意字段属性的写法和上面不同,这个是publish_id=xxx,上面是publish=xxx。
2、多对多
方式1: # 当前生成的书籍对象 book_obj=Book.objects.create(title="追风筝的人",price=200,publishDate="2012-11-12",publish_id=1) # 为书籍绑定的做作者对象 ee=Author.objects.filter(name="ee").first() rr=Author.objects.filter(name="alex").first() book_obj.authors.add(ee,rr) # 将某些特定的 model 对象添加到被关联对象集合中。 book_obj.authors.add(*[]) #book_obj是书籍对象,authors是book表里面那个多对多的关系字段名称。 #其实orm就是先通过book_obj的authors属性找到第三张表,然后将book_obj的id值和两个作者对象的id值组合成两条记录添加到第三张表里面去 方式2: book_obj.authors.add(3,4) book_obj.authors.add(*[3,4]) #这种方式用的最多,因为一般是给用户来选择,用户选择是多选的,选完给你发送过来的就是一堆的id值
多对多关系其他常用API:
book_obj.authors.remove() # 将某个特定的对象从被关联对象集合中去除。 # ====== book_obj.authors.remove(*[1,2]),将多对多的关系数据删除 book_obj.authors.clear() #清空被关联对象集合 book_obj.authors.set() #先清空再设置 =====
删除示例:
book_obj = models.Book.objects.filter(nid=4)[0] # book_obj.authors.remove(2) #将第三张表中的这个book_obj对象对应的那个作者id为2的那条记录删除 # book_obj.authors.clear() # book_obj.authors.set('2') #先清除掉所有的关系数据,然后只给这个书对象绑定这个id为2的作者,所以只剩下一条记录 3---2,比如用户编辑数据的时候,选择作者发生了变化,那么需要重新选择,所以我们就可以先清空,然后再重新绑定关系数据,注意这里写的是字符串,数字类型不可以 book_obj.authors.set(['1',]) #这么写也可以,但是注意列表中的元素是字符串,列表前面没有*,之前我测试有*,感觉是版本的问题,没事,能够用哪个用哪个
更新: book_obj = models.Book.objects.get(id=1) #获取一个书籍对象 data = {'title':'xxx','price':100} #这个书籍对象更新后的数据 models.Book.objects.filter(id=n).update(**data) #将新数据更新到原来的记录中 book_obj.authors.set(author_list) #将数据和作者的多对多关系加上 删除: models.Book.objects.filter(id=1).delete()
二、基于对象的跨表查询
1、一对多查询(Publish与Book)
关联字段在哪个类里从那张表开始查就是正向查询,反之反向查询
正向查询(字段publish)
# 查询主键为1的书籍的出版社所在的城市 book_obj=Book.objects.filter(pk=1).first() # book_obj.publish 是主键为1的书籍对象关联的出版社对象,book对象.外键字段名称 print(book_obj.publish.city)
反向查询(表名:book_set):加上_set是因为反向查询的时候,你查询出来的可能是多条记录的集合
publish=Publish.objects.get(name="苹果出版社") #publish.book_set.all() : 与苹果出版社关联的所有书籍对象集合,写法:小写的表名_set.all(),得到queryset类型数据 book_list=publish.book_set.all() for book_obj in book_list: print(book_obj.title)
2、一对一查询(Author和AuthorDetail)
正向查询(authorDetail):
qq=Author.objects.filter(name="qq").first() print(qq.authorDetail.telephone) qq.authorDeail就拿到了这个对象,因为一对一找到的就是一条记录,注意写法:作者对象.字段名,就拿到了那个关联对象
反向查询(按表名:author)不需要_set,因为一对一正向反向都是一条记录
# 查询所有住址在 斤斤计较 的作者的姓名 authorDet=AuthorDetail.objects.filter(addr="斤斤计较")[0] authorDet.author.name
3、多对多查询(Author与Book)
正向查询(字段:authors):
# 西游记所有作者的名字以及手机号 book_obj=Book.objects.filter(title="西游记").first() authors=book_obj.authors.all() for author_obj in authors: print(author_obj.name,author_obj.authorDetail.telephone
反向查询(表名:book_set):
# 查询qq出过的所有书籍的名字 author_obj=Author.objects.get(name="qq") book_list=author_obj.book_set.all() #与qq作者相关的所有书籍 for book_obj in book_list: print(book_obj.title)
可以通过在 ForeignKey() 和ManyToManyField的定义中设置 related_name 的值来覆写 FOO_set 的名称。例如,如果 Article model 中做一下更改
publish = ForeignKey(Book, related_name='bookList')
# 查询 北京出版社出版过的所有书籍 publish=Publish.objects.get(name="北京出版社") book_list=publish.bookList.all() # 与北京出版社关联的所有书籍对象集合
三、基于双下划线的跨表查询(基于join实现的)
# 正向查询按字段,反向查询按表名小写用来告诉ORM引擎join哪张表,
# 一对一、一对多、多对多都是一个写法,注意,我们写orm查询的时候,哪个表在前哪个表在后都没问题,因为走的是join连表操作。
1、一对多查询
# 练习: 查询北京出版社出版过的所有书籍的名字与价格(一对多) # 正向查询 按字段:publish queryResult=Book.objects .filter(publish__name="北京出版社") #通过__告诉orm将book表和publish表进行join,然后找到所有记录中publish.name='北京出版社'的记录(注意publish是属性名称),然后select book.title,book.price的字段值 .values_list("title","price") #values或者values_list # 反向查询 按表名:book queryResult=Publish.objects .filter(name="北京出版社") .values_list("book__title","book__price")
2、多对多查询
# 练习: 查询qq出过的所有书籍的名字(多对多) # 正向查询 按字段:authors: queryResult=Book.objects .filter(authors__name="qq") .values_list("title") # 反向查询 按表名:book queryResult=Author.objects .filter(name="qq") .values_list("book__title","book__price")
3、一对一查询
# 查询aa的手机号 # 正向查询 ret=Author.objects.filter(name="aa").values("authordetail__telephone") # 反向查询 ret=AuthorDetail.objects.filter(author__name="aa").values("telephone")
4、连续跨表练习
# 练习: 查询北京出版社出版过的所有书籍的名字以及作者的姓名 # 正向查询 queryResult=Book.objects .filter(publish__name="北京出版社") .values_list("title","authors__name") # 反向查询 queryResult=Publish.objects .filter(name="北京出版社") .values_list("book__title","book__authors__age","book__authors__name") # 练习: 手机号以178开头的作者出版过的所有书籍名称以及出版社名称 # 方式1: queryResult=Book.objects .filter(authors__authorDetail__telephone__regex="178") .values_list("title","publish__name") # 方式2: ret=Author.objects .filter(authordetail__telephone__startswith="178") .values("book__title","book__publish__name")
5、related_name
反向查询时,如果定义了related_name ,则用related_name替换 表名,例如:
publish = ForeignKey(Blog, related_name='bookList')
# 练习: 查询北京出版社出版过的所有书籍的名字与价格(一对多) # 反向查询 不再按表名:book,而是related_name:bookList queryResult=Publish.objects .filter(name="北京出版社") .values_list("bookList__title","bookList__price")
四、聚合查询、分组查询、F查询和Q查询
1、聚合查询
# 计算所有图书的平均价格 from django.db.models import Avg,Max,Min,Count,Sum Book.objects.all().aggregate(Avg('price')) #或者给它起名字:aggretate(a=Avg('price')) {'price__avg': 34.35}
如果你希望生成不止一个聚合,你可以向aggregate()子句中添加另一个参数。所以,如果你也想知道所有图书价格的最大值和最小值,可以这样查询:
from django.db.models import Avg,Max,Min,Count,Sum Book.objects.aggregate(Avg('price'), Max('price'), Min('price')) #count('id'),count(1)也可以统计个数,Book.objects.all().aggregete和Book.objects.aggregate(),都可以 {'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
2、分组查询
# 统计一下每个出版社出版书的平均价格 原生sql 根据出版社id分组 select publishs_id,avg(price) from app01_book group by publishs_id; 根据出版社名分组 select avg(app01_book.price) from app01_book inner join app01_publish on app01_book.publishs_id = app01_publish.id group by app01_publish.name; orm ret = models.Book.objects.values('publishs_id').annotate(a=Avg('price')) print(ret) ret = models.Publish.objects.annotate(a=Avg('book__price')) print(ret.values('a','name'))
3、F查询
在上面所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?我们在book表里面加上两个字段:评论数:comment,点赞数:dianzan
Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值
from django.db.models import F # 点赞数大于评论数的 # ret = models.Book.objects.filter(dianzan__gt=F('comment')) # print(ret) # 所有书籍上调10块 models.Book.objects.all().update(price=F('price')+10) #支持四则运算
4、Q查询
filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR 语句),你可以使用Q 对象。
from django.db.models import Q Q(title__startswith='Py')
Q 对象可以使用&(与) 、|(或)、~(非) 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象
ret = models.Book.objects.filter(Q(comment__gt=30)|Q(dianzan__gt=50)) ret = models.Book.objects.filter(Q(comment__gt=30)&Q(dianzan__gt=50)) 等同于# ret = models.Book.objects.filter(comment__gt=30,dianzan__gt=50) ret = models.Book.objects.filter(Q(comment__gt=30) | Q(dianzan__gt=50),publishDate__year='2018') # 注意没有Q包裹的条件,写在Q包裹的条件后面.
# 多层嵌套 # ret = models.Book.objects.filter(Q(Q(comment__gt=30) | Q(dianzan__gt=50))&Q(xx=11),publishDate__year='2018') # 条件取反 # 取评论数小于等于30 的,或者点赞数大于50的 # ret = models.Book.objects.filter(~Q(comment__gt=30)|Q(dianzan__gt=50)) # print(ret)
五、ORM执行原生sql语句
执行原生sql
Django 提供两种方法使用原始SQL进行查询:一种是使用raw()方法,进行原始SQL查询并返回模型实例;另一种是完全避开模型层,直接执行自定义的SQL语句。
raw()管理器方法用于原始的SQL查询,并返回模型的实例:
注意:raw()语法查询必须包含主键
这个方法执行原始的SQL查询,并返回一个django.db.models.query.RawQuerySet 实例。 这个RawQuerySet 实例可以像一般的QuerySet那样,通过迭代来提供对象实例。
例:
class Person(models.Model): first_name = models.CharField(...) last_name = models.CharField(...) birth_date = models.DateField(...)
可以像下面这样执行原生SQL语句
for p in Person.objects.raw('SELECT * FROM myapp_person'): print(p)
raw()方法自动将查询字段映射到模型字段。还可以通过translations参数指定一个把查询的字段名和ORM对象实例的字段名互相对应的字典
d = {'tname': 'haha'} ret = models.Student.objects.raw('select * from app02_teacher', translations=d) for i in ret: print(i.id, i.sname, i.haha)
原生SQL还可以使用参数,注意不要自己使用字符串格式化拼接SQL语句,防止SQL注入!
d = {'tname': 'haha'} ret = models.Student.objects.raw('select * from app02_teacher where id > %s', translations=d, params=[1,]) for i in ret: print(i.id, i.sname, i.haha)
执行自定义sql
有时候raw()方法并不十分好用,很多情况下我们不需要将查询结果映射成模型,或者我们需要执行DELETE、 INSERT以及UPDATE操作。在这些情况下,我们可以直接访问数据库,完全避开模型层。
我们可以直接从django提供的接口中获取数据库连接,然后像使用pymysql模块一样操作数据库。
from django.db import connection, connections cursor = connection.cursor() # cursor = connections['default'].cursor() cursor.execute("""SELECT * from auth_user where id = %s""", [1]) ret = cursor.fetchone()