在讲解MySQL
时,咱们提到,把应用程序的全部数据都放在一张表里是极不合理的 。python
好比咱们开发一个员工管理系统,在数据库里只建立一张员工信息表,该表有四个字段:工号、姓名、部门名、部门职能描述,此时若公司有1万名员工,但只有3个部门,由于每一名员工后都须要跟着部门信息(部门名、部门职能),因此将会致使部门信息出现大量重复、浪费空间。mysql
解决方法就是将数据存放于不一样的表中,而后基于foreign key
创建表之间的关联关系。git
细说的话,表之间存在三种关系:多对1、一对1、多对多,那如何肯定两张表之间的关系呢?按照下述步骤操做便可sql
左表<---------------------------------------------->右表 # 步骤一:先分析 # 分析一、先站在左表的角度 是否左表的多条记录能够对应右表的一条记录 # 分析2:再站在右表的角度去找 是否右表的多条记录能够对应左表的一条记录 # 步骤二:后肯定关系 # 多对一 若是只有"分析1"成立,那么能够肯定两张表的关系是:左表多对一右表,关联字段应该建立在左表中,而后foreign key 右表一个字段(一般是id) 若是只有"分析2"成立,那么能够肯定两张表的关系是:右表多对一左表,关联字段应该建立在右表中,而后foreign key 左表一个字段(一般是id) # 一对一 若是"分析1"和"分析2"都不成立,而是左表的一条记录惟一对应右表的一条记录,反之亦然。这种状况很简单,就是在左表foreign key右表的基础上,将左表的关联字段设置成unique便可 # 多对多 若是"分析1"和"分析2"同时成立,则证实这两张表是一个双向的多对一,即多对多,须要建立一张单独的新表来专门存放两者的关系,关联字段应该建立在新表中,而后在新表中分别foreign key两张表的id字段
咱们以一个图书管理系统为背景,设计了下述四张表,让咱们来找一找它们之间的关系数据库
书籍表:app01_book
django
出版社表:app01_publish
app
做者表:app01_author
设计
做者详细信息表:app01_authordetail
code
app01_book
与app01_publish
orm
找关系
左表(app01_book)<------------------------------->右表(app01_publish) # 步骤一: # 分析一、先站在左表的角度 左表的多条记录表明多版本书籍,右表的一条记录表明一个出版社,多本书籍对应同一个出版社 ✔️ #分析二、再站在右表的角度去找 右表的多条记录表明多个出版社,左表的一条记录表明一本书,多个出版社不能出版同一本书 ✘ # 步骤二:后肯定关系 # 多对一 只有"分析1"成立,那么能够肯定两张表的关系是:左表(app01_book)多对一右表(app01_publish),关联字段应该建立在左表(app01_book)中,而后foreign key 右表(app01_publish)的id字段
sql
语句
# 一、因为foreign key的影响,必须先建立被关联表 CREATE TABLE app01_publish ( id INT PRIMARY KEY auto_increment, name VARCHAR (20) ); # 二、才能建立出关联表 CREATE TABLE app01_book ( id INT PRIMARY KEY auto_increment, title VARCHAR (20), price DECIMAL (8, 2), pub_date DATE, publish_id INT, # 新增关联字段 FOREIGN KEY (publish_id) REFERENCES app01_publish (id) ON UPDATE CASCADE ON DELETE CASCADE );
app01_author
与app01_authordetail
找关系
左表(app01_author)<------------------------------->右表(app01_authordetail) 一个做者惟一对应一条本身的详情信息,反之亦然,因此两张表是一对一的关系。在左表中新增关联字段并添加unique约束,而后foreign key右表
sql
语句
# 一、因为foreign key的影响,必须先建立被关联表 CREATE TABLE app01_authordetail ( id INT PRIMARY KEY auto_increment, tel VARCHAR (20) ); # 二、才能建立出关联表 CREATE TABLE app01_author ( id INT PRIMARY KEY auto_increment, name VARCHAR (20), age INT, authordetail_id INT UNIQUE, # 新增关联字段,并添加惟一性约束unique FOREIGN KEY (authordetail_id) REFERENCES app01_authordetail (id) ON UPDATE CASCADE ON DELETE CASCADE );
app01_book
与app01_author
找关系
左表(app01_book)<------------------------------->右表(app01_author) # 步骤一: # 分析一、先站在左表的角度 左表的多条记录表明多版本书籍,右表的一条记录表明一个做者,多本书籍能够由同一个做者编写 ✔️ # 分析二、再站在右表的角度去找 右表的多条记录表明多个做者,左表的一条记录表明一本书,多个做者能够合做编写同一本书 ✔️ # 步骤二:后肯定关系 # 多对多 "分析1"和"分析2"同时成立,证实这两张表是多对多的关系,须要建立一张单独的新表来专门存放两者的关系,关联字段应该建立在新表中,而后在新表中分别foreign key两张表的id字段
sql
语句
# 一、建立被关联表一:app01_book,例1中已建立 # 二、建立被关联表二:app01_author,例2中已建立 # 三、建立新表,存放app01_book于app01_author的关联关系 CREATE TABLE app01_book_authors ( id INT PRIMARY KEY auto_increment, book_id INT, # 新增关联字段,用来关联表app01_book author_id INT, # 新增关联字段,用来关联表app01_author FOREIGN KEY (book_id) REFERENCES app01_book (id) ON UPDATE CASCADE ON DELETE CASCADE, FOREIGN KEY (author_id) REFERENCES app01_author (id) ON UPDATE CASCADE ON DELETE CASCADE );
上述三个例子中生成的表以下
模型类以下
from django.db import models # 表app01_publish class Publish(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=20) # 表app01_book class Book(models.Model): nid = models.AutoField(primary_key=True) title = models.CharField(max_length=20) price = models.DecimalField(max_digits=8, decimal_places=2) pub_date = models.DateField() # 表app01_book多对一表app01_publish,参数to指定模型名,参数to_field指定要关联的那个字段 publish = models.ForeignKey(to='Publish',to_field='nid',on_delete=models.CASCADE) # 咱们本身写sql时,针对书籍表与做者表的多对关系,须要本身建立新表,而基于django的orm,下面这一行代码能够帮咱们自动建立那张关系表 authors=models.ManyToManyField(to='Author') # 变量名为authors,则新表名为app01_book_authors,若变量名为xxx,则新表名为app01_book_xxx # 表app01_author class Author(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=20) age = models.IntegerField() # 表app01_author一对一表app01_authordetail author_detail = models.OneToOneField(to='AuthorDetail',to_field='nid',unique=True,on_delete=models.CASCADE) # 表app01_authordetail class AuthorDetail(models.Model): nid = models.AutoField(primary_key=True) tel = models.CharField(max_length=20)
强调
在建立关联时,针对参数to,若是传入的是字符串(to="模型名"),则模型类的定义不区分前后顺序,若是传入的是模型名(to=Author),则Author类必须事先定义
!!强调!!:上图所示的表名、字段名都是mysql
中的真实表/物理表,而咱们下述所示全部操做,都是经过模型类来操做物理表,例如不管增删改查,所使用的字段名都模型类中的字段
按照上图所示,因为foreign key
的关系,咱们须要事先往app01_publish
与app01_authordetail
里插入记录
# 一、需求:经过模型Publish往表app01_publish里插入三家出版社 Publish.objects.create(name='北京出版社') Publish.objects.create(name='长春出版社') Publish.objects.create(name='大连出版社') # 二、需求:经过模型AuthorDetail往表app01_authordetail里插入三条做者详情 AuthorDetail.objects.create(tel='18611312331') AuthorDetail.objects.create(tel='15033413881') AuthorDetail.objects.create(tel='13011453220')
按照上图所示,插入时会涉及到多张表,咱们一样分三种状况来介绍
app01_book
与app01_publish
# 需求:书籍(葵花宝典、菊花宝典、桃花宝典)都是在北京出版社出版的 # 一、先经过模型Publish从出版社表app01_publish查出北京出版社 publish_obj=Publish.objects.filter(name='北京出版社').first() # 上述代码也能够简写为:publish_obj=Publish.objects.get(name='北京出版社') # 二、再经过模型Book往书籍表app01_book里插入三本书籍与出版社的对应关系 # 方式一:使用publish参数指定关联 book_obj1=Book.objects.create(title='葵花宝典',price=2000,pub_date='1985-3-11',publish=publish_obj) book_obj2=Book.objects.create(title='菊花宝典',price=3000,pub_date='1990-1-21',publish=publish_obj) book_obj3=Book.objects.create(title='桃花宝典',price=4000,pub_date='1991-1-23',publish=publish_obj) # 方式二:使用publish_id参数指定关联 book_obj1=Book.objects.create(title='葵花宝典',price=2000,pub_date='1985-3-11',publish_id=publish_obj.nid) book_obj2=Book.objects.create(title='菊花宝典',price=3000,pub_date='1990-1-21',publish_id=1) # 在已经出版社id的状况下,能够直接指定 book_obj3=Book.objects.create(title='桃花宝典',price=4000,pub_date='1991-1-23',publish_id=1) # 注意:不管方式一仍是方式二获得的书籍对象book_obj一、book_obj二、book_obj3 # 均可以调用publish字段来访问关联的那一个出版社对象 # 均可以调用publish_id来访问关联的那一个出版社对象的nid print(book_obj1.publish,book_obj1.publish_id) print(book_obj2.publish,book_obj2.publish_id) print(book_obj3.publish,book_obj3.publish_id) # 三本书关联的是同一个出版社,因此输出结果均相同
参照上述步骤,把剩余三本书与出版社的对应关系也插入
book_obj1 = Book.objects.create(title='玉女心经', price=5000, pub_date='1988-3-24', publish_id=2) book_obj2 = Book.objects.create(title='玉男心经', price=3000, pub_date='1985-6-17', publish_id=2) book_obj3 = Book.objects.create(title='九阴真经', price=6000, pub_date='1983-8-17', publish_id=3)
app01_author
与app01_authordetail
# 需求:插入三个做者,并与做者详情表一一对应 # 因为做者详情表咱们已经事先建立好记录,因此只须要往做者表插入记录便可 # 方式一:须要事先过滤出做者详情的对象,而后经过模型Author的字段author来指定要关联的做者详情对象(略) # 方式二:肯定做者详情对象的id,而后经过模型Author经过字段author_id来指定关联关系, Author.objects.create(name='egon',age=18,author_detail_id=1) Author.objects.create(name='kevin',age=38,author_detail_id=2) Author.objects.create(name='rose',age=28,author_detail_id=3)
app01_book
与app01_author
# 咱们参照物理表app01_book_authors制定需求,须要建立以下关系 # 一、葵花宝典的做者为:egon、kevin # 二、菊花宝典的做者为:egon、kevin、rose # 三、桃花宝典的做者为:egon、kevin # 四、玉女心经的做者为:kevin、rose # 五、玉男心经的做者为:kevin # 六、九阴真经的做者为:egon、rose # 须要建立出上述关系,具体作法以下 # 一、先获取书籍对象 book_obj1=Book.objects.get(title='葵花宝典') book_obj2=Book.objects.get(title='菊花宝典') book_obj3=Book.objects.get(title='桃花宝典') book_obj4=Book.objects.get(title='玉女心经') book_obj5=Book.objects.get(title='玉男心经') book_obj6=Book.objects.get(title='九阴真经') # 二、而后获取做者对象 egon=Author.objects.get(name='egon') kevin=Author.objects.get(name='kevin') rose=Author.objects.get(name='rose') # 三、最后依次建立上述关系:在原生SQL中多对多关系涉及到操做第三张关系表,可是在ORM中咱们只须要操做模型类Book下的字段author便可 book_obj1.authors.add(egon,kevin) book_obj2.authors.add(egon,kevin,rose) book_obj3.authors.add(egon,kevin) book_obj4.authors.add(kevin,rose) book_obj5.authors.add(kevin) book_obj6.authors.add(egon,rose)
能够经过书籍对象下的authors字段获取其所关联的全部做者对象
book_obj1.authors.all() # 返回一个存有多个做者的queryset
# 一、book_obj.authors.remove() :将某个特定的对象从被关联对象集合中去除 # 从菊花宝典的做者集合中去掉做者rose rose = Author.objects.get(name='rose') book_obj2 = Book.objects.get(title='菊花宝典') book_obj2.authors.remove(rose) # 二、book_obj.authors.clear():清空被关联对象集合 # 清空菊花宝典所关联的全部做者 book_obj2 = Book.objects.get(title='菊花宝典') book_obj2.authors.clear() # 三、book_obj.authors.set():先清空再从新设置 # 玉男心经的做者原来为kevin,要求设置为egon、rose egon=Author.objects.get(name='egon') rose=Author.objects.get(name='rose') book_obj5 = Book.objects.get(title='玉男心经') book_obj5.authors.set([egon,rose]) # 多个做者对象放到列表里
数据库操做最经常使用的仍是查询操做,在介绍ORM
下多表关联查询时,须要事先记住关于ORM
模型的一个很是重要的概念:在使用模型类进行多表关联查询时,若是肯定两张表存在关联关系,那么在选取一个表做为起始(为了后续描述方便,咱们将其简称为“基表”)后,能够跨表引用来自另一张中的字段值,这存在正向与反向之分
若是关联字段存在于基表中,称之为正向查询,不然,称之为反向查询
例如表模Book
与Publish
,关联字段存在于Book
中
# 当以Book为基表时,称之为正向查询 Book(基表)-------正向---------->Publish # 当以Publish为基表时,称之为反向查询 Book<-------反向----------Publish(基表)
使用原生sql
进行多表关联查询时无非两种方式:子查询、join
连表查询,ORM
里一样有两种查询方式(严格依赖正向、反向的概念)
Author
与AuthorDetail
)正向查询,按关联字段:author_detail
# 需求:查询做者egon的手机号 # 一、先取出做者对象 egon=Author.objects.filter(name='egon').first() # 二、正向查询:根据做者对象下的关联字段author_detail取到做者详情 print(egon.author_detail.tel) # 输出:18611312331
反向查询,按模型名(小写):author
# 需求:查询手机号为'18611312331'的做者名 # 一、先取出做者的详情对象 tel=AuthorDetail.objects.filter(tel='18611312331').first() # 二、反向查询:根据小写的模型名author取到做者对象 print(tel.author.name) # 输出:egon
Book
与Publish
)正向查询,按关联字段:publish
# 需求:查询葵花宝典的出版社名字 # 一、先取书籍对象 book_obj=Book.objects.filter(title='葵花宝典').first() # 二、正向查询:根据书籍对象下的关联字段publish取到出版社 print(book_obj.publish.name) # 输出:北京出版社
反向查询,按模型名(小写)_set
:book_set
# 需求:查询北京出版社都出版的全部书籍名字 # 一、先取出出版社对象 publish_obj=Publish.objects.filter(name='北京出版社').first() # 二、反向查询:根据book_set取到全部的书籍 book_objs=publish_obj.book_set.all() print([book_obj.title for book_obj in book_objs]) # 输出:['葵花宝典', '菊花宝典', '桃花宝典']
Book
与Author
)正向查询,按关联字段,如authors
# 需求:查询葵花宝典的全部做者 # 一、先取出书籍对象 book_obj=Book.objects.filter(title='葵花宝典').first() # 二、正向查询:根据书籍对象下的关联字段authors取到全部做者 author_objs=book_obj.authors.all() print([obj.name for obj in author_objs]) # 输出:['egon', 'kevin']
反向查询,按模型名(小写)_set
:如author_set
# 需求:查询做者rose出版的全部书籍 # 一、先取出做者对象 egon=Author.objects.filter(name='rose').first() # 二、反向查询:根据book_set取到做者对象 book_objs=egon.book_set.all() print([book_obj.title for book_obj in book_objs]) # 输出:['玉女心经', '九阴真经', '玉男心经']
> 2
张表查询连续跨 > 2
张表的操做的套路与上面的案例都是同样的
# 需求:查询葵花宝典的做者们的手机号 book_obj=Book.objects.filter(title='葵花宝典').first() author_objs=book_obj.authors.all() print([author_obj.author_detail.tel for author_obj in author_objs]) # 输出:['18611312331', '15033413881']
Author
与AuthorDetail
)正向查询,按关联字段+双下划线:author_detail__
# 需求:查询做者egon的手机号 # 注意values()中的参数是:关联字段名__要取的那张被关联表中的字段 res = Author.objects.filter(name='egon').values('author_detail__tel').first() print(res['author_detail__tel']) # {'author_detail__tel': '18611312331'} # 注意:基于双下划线的跨表查询会被django的orm识别为join操做,因此上述代码至关于以下sql,后续案例均是相同原理,咱们再也不累述 select app01_authordetail.tel from app01_author inner join app01_authordetail on app01_author.author_detail_id = app01_authordetail.nid where app01_author.name = 'egon';
反向查询,按模型名(小写)+双下划线
:author__
# 需求:查询手机号为'18611312331'的做者名 # 注意values()中的参数是:小写的模型名__要取的那张被关联表中的字段 res=AuthorDetail.objects.filter(tel='18611312331').values('author__name').first() print(res) # {'author__name': 'egon'}
补充:基表决定了正向仍是反向
# 一、针对上例中正向查询的需求:查询做者egon的手机号,若是咱们选取的基表是AuthorDetail,那么就成了反向查询,应该用反向查询的语法 res = AuthorDetail.objects.filter(author__name='egon').values('tel').first() print(res) # {'tel': '18611312331'} # 二、针对上例中反向查询的需求:查询手机号为'18611312331'的做者名,若是咱们选取的基表是Author,那么就成了正向查询,应该用正向查询的语法 res=Author.objects.filter(author_detail__tel='18611312331').values('name').first() print(res) # {'name': 'egon'}
Book
与Publish
)正向查询,按关联字段+双下划线:publish__
# 需求:查询葵花宝典的出版社名字 # 注意values()中的参数是:关联字段名__要取的那张被关联表中的字段 res=Book.objects.filter(title='葵花宝典').values('publish__name').first() print(res['publish__name']) # {'publish__name': '北京出版社'}
反向查询,按模型名(小写)+双下划线:book__
# 需求:查询北京出版社都出版的全部书籍名字 # 注意values()中的参数是:小写的模型名__要取的那张被关联表中的字段 res = Publish.objects.filter(name='北京出版社').values('book__title') print(res) # <QuerySet [{'book__title': '葵花宝典'}, {'book__title': '菊花宝典'}, {'book__title': '桃花宝典'}]>
补充:基表决定了正向仍是反向
# 一、针对上例中正向查询的需求:查询葵花宝典的出版社名字,若是咱们选取的基表是Publish,那么就成了反向查询,应该用反向查询的语法 res = Publish.objects.filter(book__title='葵花宝典').values('name').first() print(res) # {'name': '北京出版社'} # 二、针对上例中反向查询的需求:查询北京出版社都出版的全部书籍名字,若是咱们选取的基表是Book,那么就成了正向查询,应该用正向查询的语法 res=Book.objects.filter(publish__name='北京出版社').values('title') print(res) # <QuerySet [{'title': '葵花宝典'}, {'title': '菊花宝典'}, {'title': '桃花宝典'}]>
Book
与Author
)正向查询,按关联字段+双下划线:authors__
# 需求:查询葵花宝典的全部做者 # 注意values()中的参数是:关联字段名__要取的那张被关联表中的字段 res=Book.objects.filter(title='葵花宝典').values('authors__name') print(res) # <QuerySet [{'authors__name': 'egon'}, {'authors__name': 'kevin'}]>
反向查询,按模型名(小写)+双下划线
:如book__
# 需求:查询做者rose出版的全部书籍 # 注意values()中的参数是:小写的模型名__要取的那张被关联表中的字段 res = Author.objects.filter(name='rose').values('book__title') print(res) # <QuerySet [{'book__title': '玉女心经'}, {'book__title': '九阴真经'}, {'book__title': '玉男心经'}]>
补充:基表决定了正向仍是反向
# 一、针对上例中正向查询的需求:查询葵花宝典的全部做者,若是咱们选取的基表是authors,那么就成了反向查询,应该用反向查询的语法 res=Author.objects.filter(book__title='葵花宝典').values('name') print(res) # <QuerySet [{'name': 'egon'}, {'name': 'kevin'}]> # 二、针对上例中反向查询的需求:查询做者rose出版的全部书籍,若是咱们选取的基表是Book,那么就成了正向查询,应该用正向查询的语法 res=Book.objects.filter(authors__name='rose').values('title') print(res) # <QuerySet [{'title': '玉女心经'}, {'title': '九阴真经'}, {'title': '玉男心经'}]>
> 2
张表查询连续跨> 2
张表的操做的套路与上面的案例都是同样的,能够连续接n
个双下划线,只须要在每次连双下划线时,肯定是正向仍是反向便可
# 需求1:查询北京出版社出版过的全部书籍的名字以及做者的姓名、手机号 # 方式一:基表为Publish res=Publish.objects.filter(name='北京出版社').values_list('book__title','book__authors__name','book__authors__author_detail__tel') # 方式二:基表为Book res=Book.objects.filter(publish__name='北京出版社').values_list('title','authors__name','authors__author_detail__tel') # 循环打印结果均为 for obj in res: print(obj) ''' 输出: ('葵花宝典', 'egon', '18611312331') ('菊花宝典', 'egon', '18611312331') ('桃花宝典', 'egon', '18611312331') ('葵花宝典', 'kevin', '15033413881') ('菊花宝典', 'kevin', '15033413881') ('桃花宝典', 'kevin', '15033413881') ('菊花宝典', 'rose', '13011453220') ''' # 需求2:查询手机号以186开头的做者出版过的全部书籍名称以及出版社名称 # 方式一:基表为AuthorDetail res=AuthorDetail.objects.filter(tel__startswith='186').values_list('author__book__title','author__book__publish__name') # 方式二:基表为Book res=Book.objects.filter(authors__author_detail__tel__startswith='186').values_list('title','publish__name') # 方式三:基表为Publish res=Publish.objects.filter(book__authors__author_detail__tel__startswith='186').values_list('book__title','name') # 循环打印结果均为 for obj in res: print(obj) ''' 输出: ('葵花宝典', '北京出版社') ('菊花宝典', '北京出版社') ('桃花宝典', '北京出版社') ('九阴真经', '大连出版社') '''