Day 51 Django_模型层_多表操作
多表操作
创建模型
实例:我们来假定下面这些概念,字段和关系
作者模型:一个作者有姓名和年龄。
作者详细模型:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息。作者详情模型和作者模型之间是一对一的关系(one-to-one)
出版商模型:出版商有名称,所在城市以及email。
书籍模型: 书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many);一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)。
模型建立如下:
from django.db import models # Create your models here. class Author(models.Model): nid = models.AutoField(primary_key=True) name=models.CharField( max_length=32) age=models.IntegerField() # 与AuthorDetail建立一对一的关系 authorDetail=models.OneToOneField(to="AuthorDetail",on_delete=models.CASCADE) class AuthorDetail(models.Model): nid = models.AutoField(primary_key=True) birthday=models.DateField() telephone=models.BigIntegerField() addr=models.CharField( max_length=64) class Publish(models.Model): nid = models.AutoField(primary_key=True) name=models.CharField( max_length=32) city=models.CharField( max_length=32) email=models.EmailField() 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建立一对多的关系,外键字段建立在多的一方 publish=models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE) # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表 authors=models.ManyToManyField(to='Author',)
生成表如下:
注意事项:
- 表的名称myapp_modelName,是根据 模型中的元数据自动生成的,也可以覆写为别的名称
- id 字段是自动添加的
- 对于外键字段,Django 会在字段名上添加"_id" 来创建数据库中的列名
- 这个例子中的CREATE TABLE SQL 语句使用PostgreSQL 语法格式,要注意的是Django 会根据settings 中指定的数据库类型来使用相应的SQL 语句。
- 定义好模型之后,你需要告诉Django _使用_这些模型。你要做的就是修改配置文件中的INSTALL_APPSZ中设置,在其中添加models.py所在应用的名称。
- 外键字段 ForeignKey 有一个 null=True 的设置(它允许外键接受空值 NULL),你可以赋给它空值 None
补充下面示例中用到的两张表:
注意:如果在一个 models.py 中,加入了以下这两张表但同时把上面已经 5 张表的 class 注释掉了,那么ORM会先把上面已经生产的 5 张表删除,然后在进行创建,所以这时要做的就是加入下面两个 calss,上面已存在的保持不变即可。当对已有的 class 进行增加字段时,原有的字段也是保持不变,需要再新加的字段中输入默认值:default=0 即可。
class Emp(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=6) age = models.IntegerField() salary = models.IntegerField() province = models.CharField(max_length=12) dep = models.ForeignKey(to="Dep",to_field='nid',on_delete=models.CASCADE) class Dep(models.Model): nid = models.AutoField(primary_key=True) name = models.CharField(max_length=12)
添加表纪录
def add(requset): ####### 添加 一对一 的关系数据 ####### # 出版社表 pub = Publish.objects.create(name='天空出版社',city='大理',email='dl@qq.com') # 作者 作者详情表 aud = AuthorDetail.objects.create(birthday='1992-1-1', telephone=130000, addr='中国') Author.objects.create(name='egon', age=1, authorDetail_id=5) ####### 添加 一对多 的关系数据 ####### # 为book表绑定出版社 # 方法1 book = Book.objects.create(title='少有人的路',publishDate='2018-12-23',price=68,publish_id=1) # 方法2 pub_obj = Publish.objects.filter(name='天空出版社').first() book = Book.objects.create(title='python',publishDate='2008-4-7',price=38,publish=pub_obj) print(book.title) print(book.price) print(book.publish) # 这本书关联的出版社对象 print(book.publish_id) # 查询 book 所对应的出版社 email 信息 book_obj = Book.objects.filter(nid=1).first() book_email = book.publish.email ####### 添加 一对多 的关系数据 ####### book = Book.objects.filter(title='python').first() alex = Author.objects.filter(name='alex').first() egon = Author.objects.filter(name='egon').first() book.authors.add(alex,egon) book.authors.add(5,6) # 作者表的主键 book.authors.add(*[5,6]) # 作者表的主键 # 解除多对多的关系 book = Book.objects.filter(title='python').first() book.authors.remove(5) book.authors.remove(5,6) book.authors.remove(*[5,6]) # 清空所有 book.authors.clear() # 查看与本书相关的所有作者 book = Book.objects.filter(title='python').first() print(book.authors.all().values('name')) # <QuerySet [{'name': 'alex'}, {'name': 'egon'}]> return HttpResponse("b_a")
基于对象的跨表查询
基于对象查询
一对多
def query(requset): ########## 基于基于对象查询 ########## # 一对多正向查询:查询书本的出版社名称 book = Book.objects.filter(title='谈美').first() # b_p = book.publish.name # 与本书相关的出版社对象 b_p = book.publish.name # 一对多反向查询:查询出版社出版的书籍 publish = Publish.objects.filter(name='商务出版社').first() pub = publish.book_set.all() print(pub) return HttpResponse(pub) ''' A---B 关联属性在A表 正向查询:A---B 反向查询:B---A #一对多查询 正向查询:看字段 方向查询:表名小写_set book.publish Book(关联属性) 《-----------------------------------》Publish publish.book_set.all() # 返回值:queryset '''
多对多
# 多对多 # 多对多正向查询:查询书本的作者名称 book = Book.objects.filter(title='python').first() author_list = book.authors.all() for list in author_list: print(list.name) # 多对多反向查询:查询作者出版的书籍 aut = Author.objects.filter(name='alex').first() book_list = aut.book_set.all() for list in book_list: print(list) return HttpResponse(list) ''' A---B 关联属性在A表 正向查询:A---B 反向查询:B---A #一对多查询 正向查询:看字段 方向查询:表名小写_set book.authors.all() Book(关联属性) 《-----------------------------------》Publish Author.book_set.all() # 返回值:queryset '''
一对一
# 一对一 # 一对一正向查询:查询 alex 的手机号 alex = Author.objects.filter(name='alex').first() alex_t = alex.authorDetail.telephone print(alex_t) # 一对一反向查询:通过手机号得到作者信息 ad = AuthorDetail.objects.filter(telephone='120000').first() alex_name = ad.author.name alex_age = ad.author.age print(alex_age,alex_name) ''' A---B 关联属性在A表 正向查询:A---B 反向查询:B---A #一对一查询 正向查询:看字段 方向查询:表名小写 author.authorDetail Author(关联属性:authorDetail) 《-----------------------------------》AuthorDetail authorDetail.author '''
基于双下划綫查询
基于双下划綫的跨表查询查询(join查询)
正向查询按字段,反向查询按表名小写,用来告诉 ORM 引擎 join 哪张表
一对多
# 一对多 # 一对多的正向查询:查询书本出版社的名称 b_p_name = Book.objects.filter(title='python').values("publish__name") print(b_p_name) # <QuerySet [{'publish__name': '天空出版社'}]># 一对多的反向查询:查询出版社出版的书名 p_b_name = Publish.objects.filter(book__title='python').values("name") print(p_b_name) # <QuerySet [{'name': '天空出版社'}]> return HttpResponse(p_b_name)
多对多
# 多对多 # 多对多的正向查询:查询 python 这书本的作者名称 # 通过 book 表 join 与其关联的 Author 表 autname = Book.objects.filter(title='python').values("authors__name") print(autname) # <QuerySet [{'authors__name': 'alex'}, {'authors__name': 'egon'}]># 多对多的正向查询:查询出版过 python 的作者 # 通过 Author 表 join 与其关联的 book 表 name = Author.objects.filter(book__title='python').values("name") print(name) # <QuerySet [{'name': 'alex'}, {'name': 'egon'}]> return HttpResponse(name)
一对一
# 一对一 # 一对一的正向查询:查询作者的手机号 phone = Author.objects.filter(name='alex').values("authorDetail__telephone") print(phone) # <QuerySet [{'authorDetail__telephone': 120000}]># 一对一的反向查询:查询作者的手机号 phone = AuthorDetail.objects.filter(author__name='alex').values('telephone') print(phone) # <QuerySet [{'telephone': 120000}]> return HttpResponse(phone)
连续跨表
# 连续跨表 # 手机号以 12 开头的作者出版过的书籍名称和出版社名称 # 正向查询 name = Book.objects.filter(authors__authorDetail__telephone__regex="12").values('title', 'publish__name') print(name) # <QuerySet [{'title': 'python', 'publish__name': '天空出版社'}]> # 反向查询 name = Author.objects.filter(authorDetail__telephone__startswith="12").values('book__title','book__publish__name') print(name) # <QuerySet [{'book__title': 'python', 'book__publish__name': '天空出版社'}]> return HttpResponse('OK') # 查询人民出版社出版过的所有书籍的名字以及作者的姓名 # 正向查询 queryResult=Book.objects .filter(publish__name="人民出版社") .values_list("title","authors__name") # 反向查询 queryResult=Publish.objects .filter(name="人民出版社") .values_list("book__title","book__authors__age","book__authors__name")
聚合和分组查询
聚合:aggregate 表示使用聚合
from django.db.models import Avg, Min, Max, Count def query(requset): # 聚合 # 返回值:字典 avg_p = Book.objects.all().aggregate(Avg("price")) print(avg_p) # {'price__avg': Decimal('46.400000')} 存在默认名称:字段 + 聚合函数名 p1 = Book.objects.all().aggregate(price_avg=Avg("price")) # 自定义名称 print(p1) # {'price_avg': Decimal('46.400000')} 存在默认名称:字段 + 聚合函数名 # 注意:当输出多个值时,其中一个使用聚合函数重命名,其它的也必须使用重名,即要用都用,要不用都不用 p2 = Book.objects.all().aggregate(price_avg=Avg("price"), price_cou=Count("price")) # 输出多个值 print(p2) # {'price_avg': Decimal('46.400000'), 'price_cou': 5} 存在默认名称:字段 + 聚合函数名 return HttpResponse('OK')
分组查询:annotate 返回值:QuerySet
单表分组
# 单表分组 # 查询每一个部门的名称以及员工的平均薪水 avg = Emp.objects.all().values('dep_id').annotate(Avg("salary")) print(avg) # <QuerySet [{'dep_id': 1, 'salary__avg': 2000.0}, {'dep_id': 2, 'salary__avg': 4000.0}]> return HttpResponse('OK') # 单表分组查询的ORM语法:单表模型.objects.values("group by 的字段").annotate(聚合函数("统计字段")) # 注意:在单表分组中,按照主键分组是毫无意义的 # Emp.objects.all() select * from Emp # Emp.objects.values('name') select t.name form Emp t
多表分组
# 一对多 # 查询每一个出版社的名称以及出版的书籍个数 info = Book.objects.values('publish__name').annotate(Count("title")) info = Book.objects.values('publish__nid').annotate(c=Count("title")).values("publish__name", 'c') print(info) # <QuerySet [{'publish__name': '天空出版社', 'c': 2}, {'publish__name': '海洋出版社', 'c': 2}]> # 多对多 # 查询每一个作者的名字以及出版过书籍的最高价值 res = Author.objects.values('pk').annotate(max=Max("book__price")).values('name','max') print(res) # 多表分组查询的ORM语法:表模型.objects.values("pk").annotate(聚合函数("统计字段"))
练习
# 练习 # 查询每一个书籍名称以及对应的作者个数 book = Book.objects.values('pk').annotate(c=Count("authors__name")).values('title',"c") print(book) # 统计每一个以字母开头书籍的作者个数 num =Book.objects.filter(authors__name__regex="^[a-z]").annotate(c=Count("authors__nid")).values("title","c") print(num) # <QuerySet [{'title': 'pyhton', 'c': 2}, {'title': 'java', 'c': 1}]> # 统计不止一个作者的书籍 num = Book.objects.values('pk').annotate(c=Count('authors__nid')).filter(c__gt=1).values('title',"c") print(num) return HttpResponse("OK")
F 与 Q 查询
F查询
在上面所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?
Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
# 查询评论数大于收藏数的书籍 from django.db.models import F Book.objects.filter(commnetNum__lt=F('keepNum'))
Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作。
# 查询评论数大于收藏数2倍的书籍 Book.objects.filter(commnetNum__lt=F('keepNum')*2)
修改操作也可以使用F函数,比如将每一本书的价格提高30元:
Book.objects.all().update(price=F("price")+30)
Q查询
filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR 语句),你可以使用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"
你可以组合& 和| 操作符以及使用括号进行分组来编写任意复杂的Q 对象。同时,Q 对象可以使用~ 操作符取反,这允许组合正常的查询和取反(NOT) 查询:
bookList=Book.objects.filter(Q(authors__name="yuan") & ~Q(publishDate__year=2017)).values_list("title")
查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。例如:
bookList=Book.objects.filter(Q(publishDate__year=2016) | Q(publishDate__year=2017), title__icontains="python" )