08 Django 多表操作

一、数据库表关系

在讲解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 注意

  1. 在创建模型表时,最后的表名,orm会默认给我们添加app应用名加下划线
  2. 在创建模型表时,我们如果自己不创建主键字段的话,orm会自动给我们创建一个名为id的主键
  3. 在django的orm中只用varchar类型,并且字符型字段必须要传最大长度参数max_length
  4. Datefield()可以传参数,参数有两个auto_now、auto_now_add。auto_now代表只要我一动这个字段,时间实时更新auto_now_add代表永远都是一个时间
  5. 在创建关联时,针对参数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') 

猜你喜欢

转载自www.cnblogs.com/xichenHome/p/11735833.html