一、数据库表关系
在讲解MySQL时,我们提到,把应用程序的所有数据都放在一张表里是极不合理的。
比如我们开发一个员工管理系统,在数据库里只创建一张员工信息表,该表有四 个字段:工号、姓名、部门名、部门职能描述,此时若公司有1万名员工,但只有 3个部门,因为每一名员工后都需要跟着部门信息(部门名、部门职能),所以将 会导致部门信息出现大量重复、浪费空间。
解决方法:是将数据存放于不同的表中,然后基于foreign key建立表之间的关联 关系。
表关系有三种:一对一、一对多、多对多
###################################分析表关系 左表--------------------------------右表 第一步:先分析 分析1:先站在左表的角度:判断是否左表的多条记录可以对应右表的一条记录 分析2:再站在右表的角度:判读是否右表的多条记录可以对应左表的一条关系 第二步:确定关系 第一种:一对多 1.如果只有分析1成立,那么可以确定的关系式:右表一对多左表,然后在左表中建立外键(foreign key)对应右表的一个字段(通常是逐渐id) 2.如果只有分析2成里,那么可以确定的关系:左表一对多右表,在右表中建立外键中(foreign key)对应左表的一个字段 第二种:一对一 如果分析1和分析2都不成立,二十左表的一条记录唯一对应右表的一条记录,反之,这种情况很简单,就是在左表建立外键(foreign key)对应右表的一条记录,并且将左表的外键字段设置唯一 第三种:多对多 如果分析1和分析2同时成立,说明者两张表是一个双向的多对一,即多对多,这是我们那张表中都不能建立外键字段,我们则必须重新创建一张新的表,这张表是左表和右表的关系表,左表和右表的关键字段都在新的关系表中,然后再信标中分别建立外键(foreign key)两张表的id字段
二、图书馆里系统案例
我们以一个图书馆里系统为背景,设计了下面四张表,让我们找一找他们之间的关系
书籍表:app_book
出版社表:app_publish
作者表:app_author
作者详细信息表:app_authordetail
2.1 找关系
左表出版社表app_publish----------右表书籍表app_book 第一步: 分析1:站在左表的角度 左表的多个多条记录代表对各出版社,右表一条记录代码一本书,一本书不能多家出版社出版(N) 分析2:站在右表的角度 右表的多条记录代表多本书,左表的一条记录代表一个出版社,多本书可以对应同一个出版社(Y) 第二步:确定关系 分析2成立,则是左表一对多右表,将外键设在右表app_book中foreign key 对应左表app_publish的一个字段,为id主键 sql语句 # 由于foreign key的影响,必须先创建被关联表(app_publish) create table app_publish( id int primary key auto_increment, name varchar(20), ); # 才能创建出关联表app_book create teble app_book( id int primary key auto_increment, name varchar(20), price decimal(5,2), up_date date, publish_id int, foreign key(publish_id) reference app_publish(id) on update cascade on delete cascade );
左表app01_author作者表----------------------------右表app01_authordetail作者详情表 一个作者唯一对应一条自己的详情信息,反之亦然,所以两张表是一对一的关系。在左表中新增关联字段并添加unique 约束,然后foreign key右表 # sql语句 # 1、由于foreign key的影响,必须先创建被关联表 CREATE TABLE app01_authordetail ( id INT PRIMARY KEY auto_increment, tel VARCHAR (20) ); # 2、才能创建出关联表 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 # 级联更新,级联删除 );
左表app_book书籍表-----------------右表app_author作者表 第一步: 分析1:左表的一条记录代表一本书,右表的多条记录代表多个作者,一本书可以被多个作者编写(Y) 分析2:右表的一条记录代表一个作者,左表的多天记录代表多本书,一个作者可以写多本书(Y) 第二步:确定关系 分析1和分析2同时成立,多对多的关系 新建第三张表,关系表 #sql语句:先建立被关联的两张表app_book表和app_author表已经建好了 create table app_book_author( 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 );
2.2 django中ORM创建模型表
模型类如下:
from django.db import models # 模型表app_publish 出版社表 # 此处注意:我们在创建模型表时,最后的表名,orm会默认给我们添加app应用名加下划线 class Publish(models.Model): nid = models.Autofield(primary_key=True) # 设置主键字段nid,必须Autofield() # 在创建模型表时,我们如果自己步创建主键字段的话,orm会自动给我们创建一个名为id的主键 # 如果自己创建了,就用自己的 name = models.CharField(max_length=20) # 在django的orm中只用varchar类型 # 字符型字段必须要传最大长度参数max_length=20 # 模型表app_book class Book(models.Models): 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() # Datefield()可以传参数,参数有两个auto_now、auto_now_add # 表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 # 模型表app_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.CASCA DE) # 模型表app_authordetail class AuthorDetail(models.Model): nid = models.AutoField(primary_key=True) tel = models.CharField(max_length=20)
2.3 注意
- 在创建模型表时,最后的表名,orm会默认给我们添加app应用名加下划线
- 在创建模型表时,我们如果自己不创建主键字段的话,orm会自动给我们创建一个名为id的主键
- 在django的orm中只用varchar类型,并且字符型字段必须要传最大长度参数max_length
- Datefield()可以传参数,参数有两个auto_now、auto_now_add。
auto_now代表只要我一动这个字段,时间实时更新
,auto_now_add代表永远都是一个时间
- 在创建关联时,针对参数to,如果传入的是字符串(to="模型名"),则模型类的定义不区分先后顺序,如果传入的是 模型名(to=Author),则Author类必须事先定义
三、添加、删除、修改记录
3.1 添加记录
此处强调1:创建完模型表后的两句数据库表迁移命令一定不能忘了,否则你没有表去操作
此处强调2:上述创建模型表上的app_表名就是在数据库中的真实表/物理表,而我们 下述所示所有操作,都是通过模型类来操作物理表,例如无论增删改查,所使用的字段名都模型类中的字段
按照表与表之间的关系,以及foreign key的关系,我们需要先往app_publish出版社表和app_authordetail表添加数据
1.通过模型Publish往表app_publish中添加三家出版社 models.Publish.objects.create(name='北京出版社') models.Publish.objects.create(name='上海出版社') models.Publish.objects.create(name='南京出版社') 2.通过模型AuthorDetail往表app_authordetail中添加三条作者信息 models.AuthorDetail.objects.create(tel='15025461234') models.AuthorDetail.objects.create(tel='18564597524') models.AuthorDetail.objects.create(tel='13456762134')
根据上面的表关系,插入时会涉及到多张表,我们同样分三种情况来看
1.一对多:app_publish与app_book
# 需求:书籍(葵花宝典、菊花宝典、桃花宝典)都是在北京出版社出版的 1、先通过模型Publish从出版社表app01_publish查出北京出版社 publish_obj = Publish.objects.filter(name='北京出版社').first() # 上述代码也可以简写为:publish_obj = Publish.objects.get(name='北京出版社') 2.再通过模型Book往书籍表app_book里插入三本书籍与出版社的对应关系 # 方式一:使用publish参数指定关联,不加id时,外键字段名是一个对象 book_obj1 = models.Book.objects.create(title='葵花宝典',price=2000,pub_date='1985-311',publish=publish_obj) book_obj2 = models.Book.objects.create(title='菊花宝典',price=2000,pub_date='1985-121',publish=publish_obj) book_obj3 =models.Book.objects.create(title='桃花宝典',price=4000,pub_date='1991-123',publish=publish_obj) # 方式二:使用publish_id参数指定关联,加id时,就是一个字段,然后 book_obj1 = models.Book.objects.create(title='葵花宝典',price=2000,pub_date='1985-311',publish_id=publish_obj.nid) book_obj2 = models.Book.objects.create(title='菊花宝典',price=3000,pub_date='1990-121',publish_id=1) # 在已经出版社id的情况下,可以直接指定 book_obj3 = models.Book.objects.create(title='桃花宝典',price=4000,pub_date='1991-123',publish_id=1) # 注意:无论方式一还是方式二得到的书籍对象book_obj1、book_obj2、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 = models.Book.objects.create(title='玉女心经',price=2000,pub_date='1985-311',publish_id=2) book_obj2 = models.Book.objects.create(title='九阴真经',price=3000,pub_date='1990-121',publish_id=2) # 在已经出版社id的情况下,可以直接指定 book_obj3 = models.Book.objects.create(title='python入门',price=4000,pub_date='1991-123',publish_id=3)
2.一对一:app_author与app_authordetail
# 需求:插入三个作者,并与作者详情表一一对应 # 由于作者详情表我们已经事先创建好记录,所以只需要往作者表插入记录即可 # 方式一:需要事先过滤出作者详情的对象,然后通过模型Author的字段author来指定要关联的作者详情对象 detail_obj1 = models.AuthorDetail.objects.filter(id=1).first() detail_obj2 = models.AuthorDetail.objects.filter(id=2).first() detail_obj3 = models.AuthorDetail.objects.filter(id=3).first() models.Author.objects.create(name='xichen',age=20,author_detail=detail_obj1) models.Author.objects.create(name='cecilia',age=21,author_detail=detail_obj2) models.Author.objects.create(name='xucheng',age=22,author_detail=detail_obj3) # 方式二:确定作者详情对象的id,然后通过模型Author通过字段author_id来指定关联关系 models.Author.objects.create(name='xichen',age=20,author_detail_id=1) models.Author.objects.create(name='cecilia',age=20,author_detail_id=2) models.Author.objects.create(name='xucheng',age=20,author_detail_id=3)
3.多对多:app_book与app_author
# 我们参照物理表app01_book_authors制定需求,需要创建如下关系 # 1、葵花宝典的作者为:xichen、cecilia # 2、菊花宝典的作者为:xichen、cecilia、xucheng # 3、桃花宝典的作者为:xichen、cecilia # 4、玉女心经的作者为:cecilia、xucheng # 5、九阴真经的作者为:cecilia # 6、python入门的作者为:xichen、xucheng # 需要创建出上述关系,具体做法如下 1.先获取书籍对象 book_obj1 = models.Book,objects.get(name='葵花宝典') book_obj2 = models.Book,objects.get(name='菊花宝典') book_obj3 = models.Book,objects.get(name='桃花宝典') book_obj4 = models.Book,objects.get(name='玉女心经') book_obj5 = models.Book,objects.get(name='九阴真经') book_obj6 = models.Book,objects.get(name='python入门') 2.获取作者对象 author_obj1 = models.Author.objects.get(name='xichen') author_obj2 = models.Author.objects.get(name='cecilia') author_obj3 = models.Author.objects.get(name='xucheng') 3.添加关系表中 book_obj1.authors.add('xichen','cecilia') book_obj2.authors.add('xichen','cecilia','xucheng') book_obj3.authors.add('xichen','cecilia') book_obj4.authors.add('cecilia','xucheng') book_obj5.authors.add('cecilia') book_obj6.authors.add('xichen','xucheng')
可以通过书籍对象下的authors字段获取其所关联的所有作者对象
book_obj1.authors.all() # 返回一个存有多个作者的queryset
3.2 删除、修改数据
# 1、book_obj.authors.remove() :将某个特定的对象从被关联对象集合中去除 # 从菊花宝典的作者集合中去掉作者rose rose = Author.objects.get(name='rose') book_obj2 = Book.objects.get(title='菊花宝典') book_obj2.authors.remove(rose) # 2、book_obj.authors.clear():清空被关联对象集合 # 清空菊花宝典所关联的所有作者 book_obj2 = Book.objects.get(title='菊花宝典') book_obj2.authors.clear() # 3、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里 同样有两种查询方式(严格依赖正向、反向的概念)
五、基于对象的查询
5.1 跨两张表查询
1.一对一查询(模型类Author与AuthorDetail)
正向查询,按关联字段:author_detail
需求:查询作者xichen的手机号 #先取出作者xichen对象 xichen_obj = models.Author.objects.get(name='xichen') #正向查询::根据作者对象下的关联字段author_detail取到作者详情 xichen_obj.author_detail.tel # 输出15025461234
反向查询,按模型名(小写):author
需求:查询手机号为'15025461234'的作者名 #先取出手机号为15025461234的对象 tel_obj = models.AuthorDetail.objects.filter(tel='15025461234').first() #反向查询:根据关联表的小写表名取到作者对象 tel_obj.author.name 输出:xichen
2.一对多查询(模型类Publish和Book)
正向查询,按关联字段:publish
需求:查询葵花宝典的出版社名字 # 先取出书名是葵花宝典的书籍对象 book_obj = models.Book.objects.get(name='葵花宝典') # 正向查询:根据外键字段 book_obj.Publisg.name # 输出北京出版社
反向查询,按模型名(小写)_set:book_set
需求:查询北京出版社都出版的所有书籍名字 # 先查出北京出版社对象 beijing_obj = models.Publish.objects.filter(name='北京出版社').first() # 反向查询,按照模型名小写 books_list = beijing_obj.book_set.all() print([books_obj.title for books_obj in books_list]) # 输出:['葵花宝典', '菊花宝典', '桃花宝 典']
3.多对多查询(模型类Book与Author)
正向查询,按关联字段,如authors
需求:查询葵花宝典的所有作者 # 查出书名为葵花宝典的书籍对象 book_obj = models.Book.objects.filter(name='葵花宝典').first() #正向查询,按照关键字段 authors_list = book_obj.authors_set.all() print([obj.name for obj in authors_list ]) # 输出:['xichen', 'cecila']
反向查询,按模型名(小写)_set:如author_set
需求:查询作者xucheng出版的所有书籍 #先获取名字为xucheng的作者对象 xucheng_obj = models.Author.objects.filter(name='xucheng').first() #反向查询,根据表名小写_set.all()取到所有的作者对象 book_list = xucheng_obj.book_set.all() print([book_obj.title for book_obj in book_list])
5.2 连续跨>2张表查询
连续跨>2张表的操作的套路与上面的案例都是一样的
需求:查询葵花宝典的作者们的手机号 # 查出书名为葵花宝典的书籍对象 book_obj = models.Book.objects.filter(name='葵花宝典').first() #正向查询,按照关键字段 authors_list = book_obj.authors_set.all() # 正向查询,按照关键字段 for authors_obj in authors_list: print(authors_obj.author_detail.tel)
六、基于下划线的跨表查询
6.1 跨两张表查询
1.一对一查询(模型类Author与AuthorDetail)
正向查询,按关联字段+双下划线:author_detail__
# 需求:查询作者xichen的手机号 # 注意values()中的参数是:关联字段名__要取的那张被关联表中的字段 res = models.Author.objects.filter(name='xichen').values('author_detail__tel').first() # 注意:基于双下划线的跨表查询会被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__
# 需求:查询手机号为'15025461234'的作者名 # 注意values()中的参数是:小写的模型名__要取的那张被关联表中的字段 res=models.AuthorDetail.objects.filter(tel='15025461234').values('author__name').first() print(res) # {'author__name': 'xichen'}
补充:基表决定了正向还是反向
# 1、针对上例中正向查询的需求:查询作者xichen的手机号,如果我们选取的基表是AuthorDetail,那么就成了反向 查询,应该用反向查询的语法 res = models.AuthorDetail.objects.filter(author__name='egon').values('tel').first() print(res) # {'tel': '15025461234'} # 2、针对上例中反向查询的需求:查询手机号为'15025461234'的作者名,如果我们选取的基表是Author,那么就 成了正向查询,应该用正向查询的语法 res=models.Author.objects.filter(author_detail__tel='15025461234').values('name').first() print(res) # {'name': 'egon'}
2. 一对多查询(模型类Book与Publish)
正向查询,按关联字段+双下划线:publish__
# 需求:查询葵花宝典的出版社名字 # 注意values()中的参数是:关联字段名__要取的那张被关联表中的字段 res=models.Book.objects.filter(title='葵花宝典').values('publish__name').first() print(res['publish__name']) # {'publish__name': '北京出版社'}
反向查询,按模型名(小写)+双下划线:book__
# 需求:查询北京出版社都出版的所有书籍名字 # 注意values()中的参数是:小写的模型名__要取的那张被关联表中的字段 res = models.Publish.objects.filter(name='北京出版社').values('book__title') print(res) # <QuerySet [{'book__title': '葵花宝典'}, {'book__title': '菊花宝典'}, {'book__title': '桃花宝典'}]>
补充:基表决定了正向还是反向
# 1、针对上例中正向查询的需求:查询葵花宝典的出版社名字,如果我们选取的基表是Publish,那么就成了反向查 询,应该用反向查询的语法 res = models.Publish.objects.filter(book__title='葵花宝典').values('name').first() print(res) # {'name': '北京出版社'} # 2、针对上例中反向查询的需求:查询北京出版社出版的所有书籍名字,如果我们选取的基表是Book,那么就成了 正向查询,应该用正向查询的语法 res=Book.objects.filter(publish__name='北京出版社').values('title') print(res) # <QuerySet [{'title': '葵花宝典'}, {'title': '菊花宝典'}, {'title': '桃花宝 典'}]>
3 多对多查询(模型类Book与Author)
正向查询,按关联字段+双下划线:authors__
# 需求:查询葵花宝典的所有作者 # 注意values()中的参数是:关联字段名__要取的那张被关联表中的字段 res=Book.objects.filter(title='葵花宝典').values('authors__name') print(res) # <QuerySet [{'authors__name': 'egon'}, {'authors__name': 'kevin'}]>
反向查询,按模型名(小写)+双下划线:如book__
# 需求:查询作者xucheng出版的所有书籍 # 注意values()中的参数是:小写的模型名__要取的那张被关联表中的字段 res = Author.objects.filter(name='rose').values('book__title') print(res) # <QuerySet [{'book__title': '玉女心经'}, {'book__title': '九阴真经'}, {'book__title': 'python入门'}]>
补充:基表决定了正向还是反向
# 1、针对上例中正向查询的需求:查询葵花宝典的所有作者,如果我们选取的基表是authors,那么就成了反向查 询,应该用反向查询的语法 res=Author.objects.filter(book__title='葵花宝典').values('name') print(res) # <QuerySet [{'name': 'xichen'}, {'name': 'cecilia'}]> # 2、针对上例中反向查询的需求:查询作者rose出版的所有书籍,如果我们选取的基表是Book,那么就成了正向查询,应该用正向查询的语法 res=Book.objects.filter(authors__name='xucheng').values('title') print(res) # <QuerySet [{'title': '玉女心经'}, {'title': '九阴真经'}, {'title': 'python入门'}]>
6.2 连续跨>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) ''' 输出: ('葵花宝典', 'xichen', '15025461234') ('菊花宝典', 'xichen', '15025461234') ('桃花宝典', 'xichen', '15025461234') ('葵花宝典', 'cecilia', '18564597524') ('菊花宝典', 'cecilia', '18564597524') ('桃花宝典', 'cecilia', '18564597524') ('菊花宝典', 'xucheng', '13456762134') ''' 需求2:查询手机号以185开头的作者出版过的所有书籍名称以及出版社名称 # 方式一:基表为AuthorDetail res=AuthorDetail.objects.filter(tel__startswith='185').values_list('author__book__title' ,'author__book__publish__name') # 方式二:基表为Book res=Book.objects.filter(authors__author_detail__tel__startswith='186').values_list('titl e','publish__name') # 方式三:基表为Publish res=Publish.objects.filter(book__authors__author_detail__tel__startswith='186').values_l ist('book__title','name')