表字段及表关系

(1)作者模型:一个作者有姓名和年龄。
(2)作者详细模型:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息。
   # 作者详情模型和作者模型之间是一对一的关系(one-to-one)。
(3)出版社模型:出版社有名称,所在城市以及email。
(4)书籍模型: 书籍有书名和出版日期.
   # 一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many);
   # 一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)。
(5)书跟作者是多对多关系,利用Django的建表语句我们可以新生成一张“关系表”——book2author

在app的models.py文件中创建模型类

需要记住下面这几个结论

(1)一旦确立表关系是一对多:建立一对多关系————在多对应的表中创建关联字段。
(2)一旦确立表关系是多对多:建立多对多关系————创建第三张关系表————id和两个关联字段。
(3)一旦确定表关系是一对一:建立一对一关系————在两张表的任意一张表中建立关联字段+Unique。
(4)其实上面说的“关联字段”就是外键——foreign key。

对应的模型类的创建如下:

from django.db import models

#出版社
class Publish(models.Model):
    #主键
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=55)
    city = models.CharField(max_length=55)
    #email有特定的格式!
    email = models.EmailField()

#作者详细
class AuthorDetail(models.Model):
    nid = models.AutoField(primary_key=True)
    #日期的格式
    birthday = models.DateField()
    #手机号
    telephone = models.BigIntegerField()
    addr = models.CharField(max_length=55)

#作者表
class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=55)
    #年龄,int类型的小数字就可以
    age = models.IntegerField()
    #由于作者与作者详细表是一对一的关系:所以选择在作者表中这样建立外键
    #注意这里还是只写authordetail就可以了,_id 程序会自动给加的!
    #注意这里on_delete一定要加!而且to后面的表的名字要习惯性的加上1
    #一对一!
    authordetail = models.OneToOneField(to='AuthorDetail',to_field='nid',on_delete=models.CASCADE)


#书籍
class Book(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=55)
    #出版日期,日期格式
    pub_date = models.DateField()
    #价格,最大位数5位,小数后保留两位
    price = models.DecimalField(max_digits=5,decimal_places=2)
    #与出版社表关联的字段——publish_id
    #注意自己写的时候只写publish就可以了!Django会自动补上_id
    #注意:on_delete必须要加上!!!而且to后面的表的名字要习惯性的加上1
    #注意 null=true表示允许为空值
    # 一对多!
    publish = models.ForeignKey(to='Publish',to_field='nid',on_delete=models.CASCADE,null=True)
    #书跟作者是多对多的关系。理论上需要新建一张关系表。
    #但是利用Django下面的语句既可以新建一张表,又可以分别将其与书籍表与作者表关联起来!
    #多对多!
    authors = models.ManyToManyField(to='Author')
    """
    create table book2author(
        id int primary_key auto_increment,
        book_id int,0
        author_id int,
        foreign_key (book_id) references Book(id),
        foreign_key (author_id) references Author(id),   
    );
    """

Django2的一个问题

在Django2中,外键关联的那个属性必须加上on_delete=models.CASCADE,否则数据迁移时会报错!

数据迁移

在terminal中执行数据库的迁移指令:

python manage.py makemigrations
python manage.py migrate

查看一下MySQL中是否生成了“5”张我们需要的表。

注意事项:

(1)表的名称myapp_modelName,是根据 模型中的元数据自动生成的,也可以覆写为别的名称  
(2)id字段是自动添加的
(3)对于外键字段,Django 会在字段名上添加"_id" 来创建数据库中的列名
(4)这个例子中的CREATE TABLE SQL 语句使用PostgreSQL 语法格式,要注意的是Django 会根据settings 中指定的数据库类型来使用相应的SQL语句。
(5)定义好模型之后,你需要告诉Django _使用_这些模型。你要做的就是修改配置文件中的INSTALL_APPSZ中设置,在其中添加models.py所在应用的名称。
(6)外键字段 ForeignKey 有一个 null=True 的设置(它允许外键接受空值 NULL),你可以赋给它空值 None 

添加记录

先使用sql语句在publish、author、authordetail表中加几条数据。

批量创建数据库数据

book_list = []
for i in range(100):
    book = Book(title='book_%s'%i,price=i*i)
    book_list.append(book)
#批量创建:bulk_create()
Book.objects.bulk_create(book_list)

1、一对多添加记录

在book表中新增一条记录,由于publish表与book表是一对多的关系,关联字段为publish_id,因此我们先得找到对应的出版社, 这里有两种方法:

方法一:

publish_obj = Publish.objects.get(nid=1)
Book.objects.create(title='金瓶',price=100,pub_date='1922-2-3',publish=publish_obj)

方法二:

Book.objects.create(title='金瓶',price=100,pub_date='1922-2-3',publish_id=1)

注意理解publish_obj:

# 查看水浒传书籍的出版社对应的邮箱——注意这里的first(),把QuerySet对象转化为Object对象###
book_obj = Book.objects.filter(title='水浒传').first()
# 注意先.publish再.emali
print(book_obj.publish.email)

2、多对多添加记录

书籍与作者是多对多的关系。 创建一个book记录,将两个作者关联到这个记录中,这两个作者写同一本书。

##注意这里的first()得加!
book_obj = Book.objects.create(title='三国群英传2',price=200,pub_date='1900-3-4',publish_id=2)
whw = Author.objects.filter(nid=1).first()
www = Author.objects.filter(nid=2).first()
##绑定多对多关系的API。
##这个authors就是Book类里建多对多关系的时候的那个:authors = models.ManyToManyField(to='Author')
book_obj.authors.add(whw,www)

##注意如果从既有的数据里找得这样写,authors字段在Book类中,因此必须是Book的对象去add Author对象
##后面加上first()将QuerySet对象转换成model对象
author1 = Author.objects.filter(name='whw').first()
author2 = Author.objects.filter(name='www').first()
book1 = Book.objects.filter(title='金瓶').first()
book1.authors.add(author1,author2)

##解除多对多关系,注意first得加
book = Book.objects.filter(nid=4).first()   
##注意这里的2代表author_id
book.authors.remove(2)
##也可以移除所有的关系:
book.authors.clear()

##重点来了!————all()
##与这本书关联的所有作者对象集合——QuerySet对象。[obj1,obj2,......]
ret = book.authors.all()
print(ret)
###查询主键为4的书籍的所有作者的名字###
book_o = Book.objects.filter(nid=4).first()
rets = book_o.authors.all().values('name')
print(rets)
###注意上面的结果是:<QuerySet [{'name': 'whw'}, {'name': 'www'}]>

多对多修改与删除

# 将某个特定的对象从被关联对象集合中去除。 book_obj.authors.remove(*[1,2]),将多对多的关系数据删除
book_obj.authors.remove()      
# 清空被关联对象集合
book_obj.authors.clear()
# 先清空再设置
book_obj.authors.set()        

### 删除实例
book_obj = models.Book.objects.filter(nid=4)[0]
#将第三张表中的这个book_obj对象对应的那个作者id为2的那条记录删除
# book_obj.authors.remove(2) 

# book_obj.authors.clear()

#先清除掉所有的关系数据,然后只给这个书对象绑定这个id为2的作者,所以只剩下一条记录  
# 比如用户编辑数据的时候,选择作者发生了变化,那么需要重新选择,所以我们就可以先清空,然后再重新绑定关系数据,
# 注意这里写的是字符串,数字类型不可以
# book_obj.authors.set('2') 

#这么写也可以,但是注意列表中的元素是字符串,列表前面没有*,之前我测试有*,可能是版本的问题
book_obj.authors.set(['1',]) 

多表操作之基于对象的跨表查询

基于对象的跨表查询最终翻译成SQL语句都是子查询。 数据库中的一个表记录其实就是筛选出来的“对象”的一个对象可以利用.操作符操作。

values()等同于select。

filter()等同于where。

一对多查询(Book与Publish)

以Book表为基准,由于我们将关联的字段定义在了Book表中,也就是说“关联字段”在Book表中,所以从Book开始查是“正向”,从Publish开始查是“反向”。 Book类中是这样写的:

publish = models.ForeignKey(to='Publish',to_field='nid',on_delete=models.CASCADE,null=True)
Book         book_obj.publish         >>   Publish
          ———————————————————————————————————— 
            <<<  publish_obj.book_set.all()#QuerySet author_boj1,author_obj2,......]

正向查询按字段——publish

#查询主键为1的书籍的出版社的城市
book_obj = Book.objects.filter(pk=1).first()
#正向查询按字段
##注意这里的book_obj.publish————是主键为1的书籍对象关联的出版社对象
ret = book_obj.publish.city
print(ret)

#同样的:“西游记”书籍的出版社的名字:
book_o = Book.objects.filter(title='水浒传').first()
ret = book_o.publish.name
print(ret)

反向查询按表名小写_set.all():book_set.all()

#查询“苹果出版社”出版过的所有书籍的书名
publish_obj = Publish.objects.filter(name='苹果出版社').first()
#反向查询按表名小写_set
##注意 publish.book_set.all() ————是苹果出版社关联的所有“书籍对象”的集合
book_list = publish.book_set.all()
for book_obj in book_list:
    print(book_obj.title)

多对多查询(Book与Author,外加book_authors表)

以Book表为基准,由于我们将关联的字段定义在了Book表中,也就是说“关联字段”在Book表中,所以从Book开始查是“正向”,从Author开始查是“反向”。 Book类中是这样写的:

authors = models.ManyToManyField(to='Author')
Book        book_obj.authors.all()  >>  Author #QuerySet[author_boj1,author_obj2,...]
         ———————————————————————————————————— 
         <<<  author_obj.book_set.all()  

正向查询按字段—authors

#“三国群英”所有的作者及手机号
##先“过滤”
book_obj = Book.objects.filter(title='三国群英').first()
##正向查询按字段
##注意这里得到的 authors 是与这本书关联的所有 作者对象的集合
authors = book_obj.authors.all()
for author_obj in authors:
    name = author_obj.name
    ##手机号在“作者详细表”中,而且“作者表”相对于“作者详细表”是正向,关联字段为authordetail
    telephone = author_obj.authordetail.telephone
    print('作者:%s,手机号:%s'%(name,telephone))

反向查询按表名_set.all()—book_set.all()

#查询作者whw出版过的所有书籍的名字
##先“过滤”
author_obj = Author.objects.filter(name='whw').first()
##反向查询按表名小写_set
##注意这里得到的是该作者写过的书的对象的集合
book_list = author_obj.book_set.all()
for book_obj in book_list:
    print(book_obj.title)

一对一查询(Author与AuthorDetail)

以Author表为基准,由于我们将关联的字段定义在了Author表中,也就是说“关联字段”在Author表中,所以从Author开始查是“正向”,从AuthorDetail开始查是“反向”。 Author类中是这样写的:

authordetail = models.OneToOneField(to='AuthorDetail',to_field='nid',on_delete=models.CASCADE)
Author        author_obj.authordetail  >>      AuthorDetail 
             ———————————————————————————————————— 
             <<<    author_detail_obj.author_set

正向查询按字段

#查询作者whw的电话
author = Author.objects.filter(name='whw').first()
##正向查询按字段
ret = author.authordetail.telephone
print(ret)

反向查询按表名小写

#查询电话是12312312的作者名字
(1)##注意这里的 authordetail_list 是一个QuerySet对象,后面没有用first
authordetail_list = AuthorDetail.objects.filter(telephone=12312312)
for obj in authordetail_list:
    #反向查询用表名小写
    print(obj.author.name)

(2)也可以在后面加first() 
add = AuthorDetail.objects.filter(telephone=12312312).first()
#反向查询按表名小写:
print(add.author.name)

多表操作之基于双下划线的跨表查询

1、基于双下划线的跨表查询最终翻译成SQL语句都是“join查询” 。

2、正向查询按字段,反向查询按表名小写:用来告诉ORM引擎join哪张表。

3、基于双下划线的跨表查询先join成一张表,再执行“单表查询”。

4、values()等同于select;filter()等同于where。

一对多关系

正向查询按字段,反向查询按表名小写用来告诉ORM引擎join哪张表。

查询水浒传这本书的出版社的名字:Book与Publish。

SQL语句这样写:

select publish.name from book inner join publish 
on book.publish_id = publish.nid 
where book.title = '水浒传'

正向查询

以Book为基准,因为“关联字段”在Book类中

Book里面这样定义的:

publish = models.ForeignKey(to='Publish',to_field='nid',on_delete=models.CASCADE,null=True)

因此这样进行正向查询

values()等同于select;filter()等同于where。  
ret = Book.objects.filter(title='水浒传').values('publish__name')
print(ret) #<QuerySet [{'publish__name': '333'}]>

反向查询

从Publish查Book

##反向查询按表名小写~
## values()等同于select;filter()等同于where
ret = Publish.objects.filter(book__title='水浒传').values('name')
print(ret) #<QuerySet [{'name': '333'}]>

多对多关系

查询三国群英这本书所有作者的名字 Book、Author、book_authors表。

SQL语句这样写

select author.name from book inner join book_authors
on book.nid = book_authors.book_id
inner join author
on book_authors.author_id = author.nid
where book.title = '三国群英'

正向查询

以Book为基准,因为“关联字段”在Book类中

##Book里面是这样定义的:
authors = models.ManyToManyField(to='Author')

##正向查询方法:
##通过Book表join与其关联的Author表————按字段authors通知ORM引擎去join 表book_authors与表author。
##正向查询按字段
ret = Book.objects.filter(title='三国群英').values('authors__name')
print(ret)  #<QuerySet [{'authors__name': 'whw'}, {'authors__name': 'www'}]>

反向查询

以AUthor为基准反查

##反向查询的方法:
##通过Author表join与其关联的Book表————按表名小写book通知ORM引擎去join 表book_authors与表book。
##反向查询按表名小写
ret = Author.objects.filter(book__title='三国群英').values('name')
print(ret) #<QuerySet [{'name': 'whw'}, {'name': 'www'}]>

一对一关系

Author与AuthorDetail

查询whw的手机号

正向查询

正向查询按字段——##通过Author表join与其关联的AUthorDetail表,按字段authordetail通知ORM引擎去join 表authordetail
ret = Author.objects.filter(name='whw').values('authordetail__telephone')
print(ret) #<QuerySet [{'authordetail__telephone': 123123123}]>

反向查询

反向查询按表名小写——##通过AuthorDetail表join与其关联的AUthor表,按表名小写author通知ORM引擎去join 表author
ret = AuthorDetail.objects.filter(author__name='whw').values('telephone')
print(ret) #<QuerySet [{'telephone': 123123123}]>

这里以几个例子来进行讲解。

例1

查询地址以'as'开头的作者出版过的所有书籍名称以及书籍的出版社名称。

(1)

通过Book表join表AuthorDetail,但Book与AuthorDetail无关联,因此必须以Author为桥梁连续跨表

##注意这里是 value_list
ret = Book.objects.filter(authors__authordetail__addr__startswith='as').values('title','publish__name')
print(ret) #<QuerySet [{'title': '三国群英', 'publish__name': '333'}]>

(2)

以Author表为基准,它与AuthorDetail表有关联,但是与Publish无关联,因此得以Book表为桥梁

ret = Author.objects.filter(authordetail__addr__startswith='as').values('book__title','book__publish__name')
print(ret) #<QuerySet [{'book__title': '三国群英', 'book__publish__name': '333'}]>

例2

查询人民出版社出版过的所有书籍的名字以及作者的姓名。

(1)正向查询

##Book相对于Publish与Author都是“正向的”
##注意用 values_list
ret = Book.objects.filter(publish__name='人民出版社').values_list('title','authors__name')   
print(ret)

(2)反向查询

过程说明

A_首先,filter等价于where,我们以Publish为基准查,因此filter里直接写name='人民出版社'即可;
B_然后,由于Publish跟Author没有直接联系,我们得借助Book作为桥梁:
(B1)要找书籍的名字,由于Publish查Book是反向查询,“用表明小写”————因此“书籍名字”是 book__title
(B2)而要找作者名字,由于Book找Author是正向查询,“用字段”————因此“在Book的基础上”我们用:book__authors__name。

查询语句

##注意用 values_list
ret = Publish.objects.filter(name='人民出版社').values__list('book__title','book__authors__name')
print(ret)

related_name

“反向查询”时,如果定义了related_name ,则用related_name替换表名

例如在Book中这样定义:

publish = models.ForeignKey(to='Publish',to_field='nid',on_delete=models.CASCADE,null=True,related_name='booklist')

那么:查询人民出版社出版过的所有书籍的名字与价格(一对多)

(1)正向查询“用不到”
ret = Book.objects.filter(publish__name='人民出版社').values('title','price')
print(ret)
(2)反向查询用到related_names
ret = Publish.objects.filter(name='人民出版社').values('booklist__title','booklist__price')

多表操作之聚合查询

用法

(1)导入django.db.models 中的Avg,Max,Min,Count等模块  
(2)找出来所有对象 Book.objects.all() 后 用aggregate()方法

举例

查询所有书籍的平均价格以及最高的价格

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

ret = Book.objects.all().aggregate(avg_price=Avg('price'),max_price=Max('price'))
print(ret) #{'avg_price': 160.0, 'max_price': Decimal('200.00')}

多表操作之F查询与Q查询

拓展既有的Book模型

在Book表中新增两个字段:read_num与content_num。由于之前已经加了数据了,想要在有数据的表中再新增字段,那么需要为之前的记录设置default值。

在Book类中新增:

class Book(models.Model):
    ##前面的字段略##
    ##新增两个字段
    ##加上 default=0 添加这两个字段后,之前的记录的这两个字段都默认为0
    read_num = models.IntegerField(default=0)
    content_num = models.IntegerField(default=0)

然后,在Terminal中运行:

python3 manage.py makemigrations
python3 manage.py migrate

就可以将上面两个字段添加到book表中。

F查询

说明

1、在上面所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?

2、Django提供F()来做这样的比较。F()的实例可以在查询中引用字段,来比较同一个model实例中两个不同字段的值。

3、需要先引入F。

from django.db.models import F

举例

1 字段间的比较

查询同一张表中评论数大于点赞数的书籍的名字:查询content_num大于read_num的书籍的名字

ret = Book.objects.filter(content_num__gt=F('read_num')).values('title')
print(ret)
#<QuerySet [{'title': '水浒传'}, {'title': '金瓶'}, {'title': '三国群英传'}, {'title': '三国群英'}]>

2 统一修改:与update结合

2-1 将每个书籍的价格加10元

Book.objects.all().update(price=F('price')+10)

2-2 给价格小于10元的书籍的价格增加10元

Book.objects.filter(price__lt=10).update(price=F('price')+10)

Q查询

说明

1、filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR语句),你可以使用Q对象。

2、需要先引入Q

from django.db.models import Q

举例

1 查找书名以“三国”开头或者价格等于100的书籍名称

ret = Book.objects.filter(Q(title__startswith='三国') | Q(price=100)).values('title')
print(ret) 
#<QuerySet [{'title': '三国群英传'}, {'title': '三国群英'}]>

2 查找书名不以“三国”开头的书籍名称

ret = Book.objects.filter(~Q(title__startswith='三')).values('title')
print(ret) 
#<QuerySet [{'title': '金瓶'}, {'title': '水浒传'}, {'title': '金瓶'}]>

3 Q查询与键值对的关系:先写Q再写键值对,而且是“且”的关系:

ret = Book.objects.filter(~Q(title__startswith='三'),title__startswith='水').values('title')
print(ret) 
#<QuerySet [{'title': '水浒传'}]>

多表操作之多表下的分组查询

多表下的分组查询语法(注意是values不是filter):

# 注意以哪张表中的字段分组,哪一张表就是“后表”
每一个后表模型.objects.values('pk').annotate(聚合函数('关联表__统计字段')).values('表模型的所有字段以及统计字段')

另外一种写法:

每一个后表模型.objects.annotate(聚合函数('关联表__统计字段')).values('表模型的所有字段以及统计字段')

# 这种写法等价于
每一个后表模型.objects.all().annotate(聚合函数('关联表__统计字段')).values('表模型的所有字段以及统计字段')

一些多表分组查询的练习

1、查询每一个出版社出版的书籍的个数

##后面这引入段省略
from django.db.models import Avg,Max,Min,Count

##查询每一个出版社出版的书籍的个数
ret = Book.objects.values('publish_id').annotate(Cout('title'))
print(ret)

2、查询每一个出版社的名称以及出版书籍的个数

(1)SQL方法:

select publish.name,Count('title') from book inner join publish 
on book.publish_id = publish.nid
group by publish_nid

(2)annotate方法:

注意,Publish查Book是反向查询,按“表名”

A:
    ret = Publish.objects.values('name').annotate(count=Count('book__title'))
    print(ret) #<QuerySet [{'name': '苹果出版社', 'count': 1}, {'name': '333', 'count': 4}]>
###############################################################################
B:  
    ret = Publish.objects.values('name').annotate(count=Count('book__title')).values('name', 'count')
    print(ret) #<QuerySet [{'name': '苹果出版社', 'count': 1}, {'name': '333', 'count': 4}]>

3、查询每一个作者的名字以及出版过的书籍的最高价格

(1)SQL方法

select author.name,Max(book.price) from book inner join book_authors
on book.nid = book_authors.book_id
inner join author 
on autohr.nid = book_authors.author_id
group by author.nid

(2)annotate方法

注意主键可以用 pk 表示;Author找Book是“反向查询”按表名小写

ret = Author.objects.values('pk').annotate(max_price=Max('book__price')).values('name','max_price')
print(ret) 
#<QuerySet[{'name':'whw','max_price':Decimal('200.00')},{'name':'www','max_price': Decimal('200.00')}]>

4、查询每一个书籍的名称以及对应的作者的个数

“正向查询按字段”

ret = Book.objects.values('pk').annotate(c=Count('authors__name')).values('title','c')
print(ret) 
# <QuerySet [{'title': '三国群英传', 'c': 1}, {'title': '三国群英', 'c': 2}, {'title': '金瓶', 'c': 0}, {'title': '水浒传', 'c': 0}, {'title': '金瓶', 'c': 0}]>

5、统计每一本以“三”字开头的书籍的作者的个数:Book找Author是多对多,而且是“正向查询”按字段

ret = Book.objects.filter(title__startswith='三').annotate(c=Count('authors__name')).values('title','c')
print(ret) 
#<QuerySet [{'title': '三国群英传', 'c': 1}, {'title': '三国群英', 'c': 2}]>

6、统计不止一个作者的书籍的名字:Book找Author是多对多,而且是“正向查询”按字段

ret = Book.objects.values('pk').annotate(num_authors=Count('authors__name')).filter(num_authors__gt=1).values('title','num_authors')
print(ret)

7、根据一本图书作者数量的多少对查询集 QuerySet进行排序

Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')

8、查询各个作者出的书的总价格

#按author表的所有字段 group by
queryResult=Author.objects
                    .annotate(SumPrice=Sum("book__price"))
            .values_list("name","SumPrice")
print(queryResult)