6.模型-多表

一、创建模型

作者模型:一个作者有姓名和年龄。

作者详细信息模型:作者的详细信息放到详情表,包含生日,手机号,家庭住址等信息。作者详情模型和作者模型之间是一对一的关系(one-to-one)

出版商模型:出版商有名称,所在城市以及email。

书籍模型: 书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many);一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)。

1、创建一对一关系

一对一的这个关系字段写在两个表的任意一个表里面都可以。OneToOneField方法的三个参数:

  • to="AuthorDetail",建立一对一关系的另一张表的表名
  • to_field="nid",建立一对一关系的另一张表的主键,如果是mysql默认生成的id可以不写这参数
  • on_delete=“ ”,可选值有两个,models.CASCADE是默认级联,models.SET_NULL是不级联。这在django2.0以上版本需要自己手动设置,以下默认级联。
# 作者表
class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()

    # authorDetail = models.OneToOneField(to="AuthorDetail", to_field="nid", on_delete=models.CASCADE)
    # authorDetail = models.OneToOneField(to="AuthorDetail", on_delete=models.CASCADE)
    authorDetail = models.OneToOneField(to="AuthorDetail", on_delete=models.SET_NULL)
    # 就是foreignkey+unique,不需要自己写参数,并且orm会自动给这个字段名字拼上一个_id,数据库中字段名称为authorDetail_id

class AuthorDetail(models.Model):
    birthday = models.DateField()
    telephone = models.CharField(max_length=32)
    addr = models.CharField(max_length=64)

2、创建一对多关系

一对多关系的相关参数:

  • to=“ ” 指向表,
  • to_field=“ ” 指向你关联的字段,不写这个,默认会自动关联主键字段,
  • related_name = " " 反向操作时,使用的字段名,用于代替原反向查询时的 '表名_set'
  • related_query_name = " " 反向查询操作时,使用的连接前缀,用于替换表名,
  • on_delete=“ ” 级联删除

建立一对多的关系,外键字段建立在多的一方,字段publish如果是外键字段,那么它自动是int类型

注意:多对多的表关系,orm的manytomany自动帮我们创建第三张表,手动创建的第三张表进行orm操作的时候,很多关于多对多关系的表之间的orm语句方法无法使用,如果你想删除某张表,你只需要将这个表注销掉,然后执行那两个数据库同步指令就可以了,自动就删除了。

# 出版社和书籍一对多
class Publish(models.Model):
    name = models.CharField(max_length=32)
    city = models.CharField(max_length=32)
    email = models.EmailField()    # charfield
    
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 = models.ForeignKey(to="Publish", to_field="nid", on_delete=models.CASCADE)
    
   # 多对多
    authors = models.ManyToManyField(to='Author',) 
    # 创建下面的第三张表 
    # class BookToAuthor(models.Model):
    # book_id = models.ForeignKey(to='Book')
    # author_id = models.ForeignKey(to='Author')

外键字段名称不需要写成publish_id,orm在翻译foreignkey的时候会自动给你这个字段拼上一个_id,这个字段名称在数据库里面就自动变成了publish_id。

3、创建多对多关系

多对多关系的相关参数:

  • to=" " 关联的表
  • related_name=“ ” 同Foreignkey字段
  • related_query_name=“ ” 同Foreignkey字段
  • through=“ ” 手动创建第三张表的时候指定第三张表的表名
  • through_fields=“ ” 设置关联的字段
  • db_table=" " 默认创建第三张表时,数据库中表的名称。

与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表,

注意:查看book表的时候,看不到这个字段,因为这个字段就是创建第三张表的意思,不是创建字段的意思,所以只能说这个book类里面有authors这个字段属性。

注意:不管是一对多还是多对多,写to这个参数的时候,最后后面的值是个字符串,不然你就需要将你要关联的那个表放到这个表的上面

4、创建管理数据表的超级用户

createsuperuser

输入创建的用户名和密码就可以,邮箱可以直接不用填直接回车

在admin.py文件中添加对应的模型,使用admin.site.register()方法,这样就能在浏览器中直接管理数据表了。

from django.contrib import admin 
from app01 import models

admin.site.register(models.Book)
admin.site.register(models.Publish)
admin.site.register(models.Author)
admin.site.register(models.AuthorDetail)

访问http://127.0.0.1:8000/admin/ 即可进入登陆界面

image-20200911204540131

查看已创建的超级用户

from django.contrib.auth.models import User
user = User.objects.filter(is_superuser = True)
print(user)

二、添加表记录

1、增

一对一增加

方式一:

先创建外键连接到那张表的数据,外键在Author表中,即先创建AuthorDetail表的数据再将其作为参数传给Author表的authorDetail。

new_author_detail = models.AuthorDetail.objects.create(
        birthday='1991-02-09',
        telephone='1245236',
        addr='浙江杭州'
    )
models.Author.objects.create(
         name='小明',
         age='20',
         authorDetail=new_author_detail,
    )

方式二:

obj = models.AuthorDetail.objects.filter(addr='北京').first()
    models.Author.objects.create(
        name='王涛',
        age='28',
        # mysql字段
        authorDetail_id=obj.id,
    )

一对多增加

obj = models.Publish.objects.get(id=2)
    models.Book.objects.create(
        title='王兄和李兄的故事',
        publish='人民出版社',
        price=3,
        # publishs=models.Publish.objects.get(id=1),
        publishs_id=obj.id,
    )

多对多增加

# 方式一
    # 给nid为1的书籍添加id为1和2的作者
    book_obj = models.Book.objects.get(nid=1)
    # * 打散传参
    book_obj.authors.add(*[1, 2])
# 方式二
    nid为的这本书添加id为1和3的两个作者
    author1 = models.Author.objects.get(id=1)
    author2 = models.Author.objects.get(id=3)
    book_obj = models.Book.objects.get(nid=5)
    book_obj.authors.add(*[author1, author2])

2、删除

一对一、一对多删除和单表删除一样

一对一 表一外键关联到表二,表一删除,不影响表二,表2删除会影响表一

models.AuthorDetail.objects.get(id=2).delete()

一对多

models.Publish.objects.get(id=1).delete()
models.Book.objects.get(nid=1).delete()

多对多删除 删除第三张表

    book_obj = models.Book.objects.get(id=6)
    # book.objects.remove(6)
    # 5和6是字段author_id的值
    # book_obj.authors.remove(*[5, 6])
    # 先清除再重置
    book_obj.author.clear()
    book_obj.authors.add(*[1, ])
    book_obj.authors.set('1')
    book_obj.authors.set(['5', '6'])    # 删除后更新

3、更新

  models.Author.objects.filter(id=5).update(
        name='老张',
        age='16',
        # authorDetail=models.AuthorDetail.objects.get(id=3),
        authorDetail_id=4
    )

三、查询

1、基于表对象的跨表查询操作

类似于子查询,

正向查询:通过关系属性所在的表(类)去查关联的另一张表的数据

反向查询:与正向相反

image-20200910090610854

一对一跨表查询

正向查询:Authorobj.authorDetail,对象.关联属性名称。

# 查询老李的电话
author_obj = models.Author.objects.filter(name='老李').first()
print(author_obj.authorDetail.telephone)

反向查询:AuthorDetailobj.author,对象.小写类名。

# 查询电话为18764512的作者是谁
author_detail_obj = models.AuthorDetail.objects.get(telephone='18764512')
print(author_detail_obj.author.name)

一对多跨表查询

正向查询:book_obj.publishs 对象.属性

# 查询三国那些事这本书的出版社是哪个
book_obj = models.Book.objects.get(title='三国那些事')
print(book_obj.publishs.name)

反向查询:publish_obj.book_set.all() 对象.表名小写_set

# 三国出版社出版了哪些书
pub_obj = models.Publish.objects.get(name='三国出版社')
print(pub_obj.book_set.all())

2、基于双下划线的跨表操作

正向查询按字段,反向查询按表名小写用来告诉ORM引擎join哪张表,一对一、一对多、多对多都是一个写法,注意,写orm查询的时候,哪个表在前哪个表在后都没问题,因为走的是join连表操作。

总结:过滤条件,已知条件的在哪张表中,以那张表去查询就是正向

反向:表名小写__连表

正向:属性名__连表

一对一

# 方式一   正向
    obj1 = models.Author.objects.filter(name='老曹').values('authorDetail__telephone')
    print(obj1)
    # 方式二   反向
    obj2 = models.AuthorDetail.objects.filter(author__name='老曹').values('telephone', 'author__age')
    print(obj2)
# 2、查询电话为1452341526的人是谁
    # 正向
    obj3 = models.Author.objects.filter(authorDetail__telephone='1452341526').values('name')
    print(obj3)
    # 反向
    obj4 = models.AuthorDetail.objects.filter(telephone='1452341526').values('author__name')
    print(obj4)

一对多

# 查询三国那些事的出版社
    obj5 = models.Book.objects.filter(title='三国那些事').values('publishs__name')
    print('obj5', obj5)
    obj6 = models.Publish.objects.filter(book__title='三国那些事').values('name')
    print('obj6', obj6)

多对多

# 正向查询 按字段:authors:
queryResult=Book.objects
            .filter(authors__name="yuan")
            .values_list("title")

    # 反向查询 按表名:book
    queryResult=Author.objects
              .filter(name="yuan")
              .values_list("book__title","book__price")

进阶:连续跨表

# 人民出版社 出版的书的名字及作者名字
    obj1 = models.Book.objects.filter(publishs__name='人民出版社').values('title', 'authors__name')
    print('obj1', obj1)
    obj2 = models.Publish.objects.filter(name='人民出版社').values('book__title', 'book__authors__name')
    print('obj2', obj2)

    # 号码以1开头的作者出版过的书籍名称及出版社名称
    obj3 = models.AuthorDetail.objects.filter(telephone__startswith='1').\
        values('author__book__title', 'author__book__publishs__name')
    print('obj3', obj3)

3、聚合查询、分组查询

聚合

aggregate(*args, **kwargs)

from ajango.db.models import Avg,Max,Min,Sum,Count
# 计算所有书籍的平均价格和最大值
obj = models.Book.objects.all().aggregate(Avg('price'), m=Max('price'))
print(obj['m']-2)

aggregate()QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。

分组

annotate()为调用的QuerySet中每一个对象都生成一个独立的统计值(统计方法用聚合函数)。

# 统计每个出版社出版的书籍的平均价格
    ret1 = models.Book.objects.values('publishs_id').annotate(a=Avg('price'))
    # select avg(price) from app01_book group by publishs_id;
    print(ret1)
    ret2 = models.Publish.objects.annotate(a=Avg('book__price'))
    print('ret2', ret2)

4、F查询和Q查询

构造的过滤器都只是将字段值与某个常量做比较。Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作。

F查询

from django.db.models import F
# F查询
# good>comment+10的
ret1 = models.Book.objects.filter(good__gt=F('comment')+10)
print('ret1', ret1)     # <QuerySet [<Book: 老李的家事>, <Book: 三国那些事>, <Book: 老曹的床头故事>]>
# 书籍表中的所有书的价格都加上20
models.Book.objects.all().update(price=F('price')+20)

Q查询

from django.db.models import Q
Q(title__startswith='Py')

Q 对象可以使用&(与)|(或)、~(非) 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。

bookList``=``Book.objects.``filter``(Q(authors__name``=``"yuan"``)|Q(authors__name``=``"egon"``))

等同于下面的SQL WHERE 子句:

WHERE name ``=``"yuan"` `OR name ``=``"egon"

四、执行原生的sql语句

执行原始的sql查询

ret = models.Publish.objects.raw('select * from app01_publish;')

执行自定义sql

不需要将查询结果映射成模型,或者我们需要执行DELETE、 INSERT以及UPDATE操作。在这些情况下,我们可以直接访问数据库,完全避开模型层。

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()

五、练习

#1 查询每个作者的姓名以及出版的书的最高价格
    ret = models.Author.objects.values('name').annotate(max_price=Max('book__price'))
    print(ret) #注意:values写在annotate前面是作为分组依据用的,并且返回给你的值就是这个values里面的字段(name)和分组统计的结果字段数据(max_price)
    # ret = models.Author.objects.annotate(max_price=Max('book__price')).values('name','max_price')#这种写法是按照Author表的id字段进行分组,返回给你的是这个表的所有model对象,这个对象里面包含着max_price这个属性,后面写values方法是获取的这些对象的属性的值,当然,可以加双下划线来连表获取其他关联表的数据,但是获取的其他关联表数据是你的这些model对象对应的数据,而关联获取的数据可能不是你想要的最大值对应的那些数据
# 2 查询作者id大于2作者的姓名以及出版的书的最高价格
    ret = models.Author.objects.filter(id__gt=2).annotate(max_price=Max('book__price')).values('name','max_price')#记着,这个values取得是前面调用这个方法的表的所有字段值以及max_pirce的值,这也是为什么我们取关联数据的时候要加双划线的原因
    print(ret)

#3 查询作者id大于2或者作者年龄大于等于20岁的女作者的姓名以及出版的书的最高价格
    # ret = models.Author.objects.filter(Q(id__gt=2)|Q(age__gte=20),sex='female').annotate(max_price=Max('book__price')).values('name','max_price')
#4 查询每个作者出版的书的最高价格 的平均值
    # ret = models.Author.objects.values('id').annotate(max_price=Max('book__price')).aggregate(Avg('max_price')) #{'max_price__avg': 555.0} 注意,aggregate是queryset的终止句,得到的是字典
    # ret = models.Author.objects.annotate(max_price=Max('book__price')).aggregate(Avg('max_price')) #{'max_price__avg': 555.0} 注意,aggregate是queryset的终止句,得到的是字典

#5 每个作者出版的所有书的最高价格以及最高价格的那本书的名称(通过orm玩起来就是个死题,需要用原生sql)
    '''
    select title,price from (select app01_author.id,app01_book.title,app01_book.price from app01_author INNER JOIN app01_book_authors on app01_author.id=
app01_book_authors.author_id INNER JOIN app01_book on app01_book.id=
app01_book_authors.book_id ORDER BY app01_book.price desc) as b  GROUP BY id
'''

六、ORM的坑

posted @ 2020-09-17 19:21  journeyerxxx  阅读(126)  评论(0编辑  收藏  举报
返回顶部