Diango基础五之diango模型层多表操作

Django基础五之django模型层(二)多表操作

一 创建模型

  表和表之间的关系

    一对一、多对一、多对多 

  如何实现一对一
      关联表的关系属性字段设置唯一约束,被关联表的主键是惟一的
  拆表
    可以加快查询速度,拆表的原则,常用的放一张表,不常用的再放一张表。

  建表需求分析

作者模型:
  作者模型:
      常用的:姓名和年龄。author    不用的:生日,手机号,家庭住址等信息。authordetial
      author 与authortial(one
-to-one)   出版商模型:publish
       名称,城市、email。   书籍模型:
     书名、出版日期,
     一本书可能会有多个作者,一个作者也可以写多本书,
     作者和书籍的关系(many
-to-many)
publish ----book 一本书可以有多个出版社出版(one-to-many)

  ·参数:

一对一:
  to  设置要关联的表
  to_field  设置要关联的字段。  
  on_delete  同ForeignKey字段。
一对多:
  to         设置要关联的表
  to_field   设置要关联的表的字段
  related_name 反向操作时,使用的字段名,用于代替原反向查询时的'表名_set'。
  related_query_name    反向查询操作时,使用的连接前缀,用于替换表名。
  on_delete     当删除关联表中的数据时,当前表与其关联的行的行为。
多对多
    to 设置要关联的表
    related_name         同ForeignKey字段。
    related_query_name   同ForeignKey字段。
    through 在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过        
    through来指定第三张表的表名。
    through_fields 设置关联的字段。
    db_table默认创建第三张表时,数据库中表的名称。   

 ondelelte 参数

on_delete
当删除关联表中的数据时,当前表与其关联的行的行为。
models.CASCADE
删除关联数据,与之关联也删除
models.DO_NOTHING
删除关联数据,引发错误IntegrityError
models.PROTECT
删除关联数据,引发错误ProtectedError
models.SET_NULL
删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
models.SET_DEFAULT
删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)
models.SET
删除关联数据,
a. 与之关联的值设置为指定值,设置:models.SET(值)
b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)

 

 创建表的原信息设置

元信息
    ORM对应的类里面包含另一个Meta类,而Meta类封装了一些数据库的信息。主要字段如下:
class Author2Book(models.Model):
    author = models.ForeignKey(to="Author")
    book = models.ForeignKey(to="Book")
    class Meta:
        unique_together = ("author", "book")

db_tableORM在数据库中的表名默认是 app_类名,可以通过db_table可以重写表名。db_table = 'book_model'
index_together联合索引。
unique_together联合唯一索引。

ordering指定默认按什么字段排序。ordering = ['pub_date',]
    只有设置了该属性,我们查询到的结果才可以被reverse(),否则是能对排序了的结果进行反转(order_by()方法排序过的数据)

 

  用代码建立模型及第三张表的创建方法:


from django.db import models
  class Author(models.Model): #比较常用的信息放到这个表里面
  nid = models.AutoField(primary_key=True)
  name=models.CharField( max_length=32)
  age=models.IntegerField() authorDetail=models.OneToOneField(to="AuthorDetail",to_field="nid",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=models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE)
  authors=models.ManyToManyField(to='Author',)
  #foreignkey里面可以加很多的参数,to指向表,to_field指向你关联的字段,不写这个,默认会自动关联主键字段,on_delete级联删除
  #字段名称不需要写成publish_id,orm在翻译foreignkey的时候会自动给你这个字段拼上一个_id,这个字段名称在数据库里面就自动变成了publish_id
  # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表,并且注意一点,你查看book表的时候,你看不到这个字段,因为这个字段就是创建第三张表的意思,不是创建字段的意思,所以只能说这个book类里面有authors这个字段属性

 ***************************************************************************************

第三张表创建的方法总结:

方式一:自行创建第三张表

class Book(models.Model):
    title = models.CharField(max_length=32, verbose_name="书名")


class Author(models.Model):
    name = models.CharField(max_length=32, verbose_name="作者姓名")


# 自己创建第三张表,分别通过外键关联书和作者
class Author2Book(models.Model):
    author = models.ForeignKey(to="Author")
    book = models.ForeignKey(to="Book")

    class Meta:
        unique_together = ("author", "book") #联合唯一

方式二:通过ManyToManyField自动创建第三张表
class Book(models.Model):
    title = models.CharField(max_length=32, verbose_name="书名")


# 通过ORM自带的ManyToManyField自动创建第三张表
class Author(models.Model):
    name = models.CharField(max_length=32, verbose_name="作者姓名")
    books = models.ManyToManyField(to="Book", related_name="authors")

方式三:设置ManyTomanyField并指定自行创建的第三张表
  
class Book(models.Model):
    title = models.CharField(max_length=32, verbose_name="书名")


# 自己创建第三张表,并通过ManyToManyField指定关联
class Author(models.Model):
    name = models.CharField(max_length=32, verbose_name="作者姓名")
    books = models.ManyToManyField(to="Book", through="Author2Book", through_fields=("author", "book"))
    # through_fields接受一个2元组('field1','field2'):
    # 其中field1是定义ManyToManyField的模型外键的名(author),field2是关联目标模型(book)的外键名。


class Author2Book(models.Model):
    author = models.ForeignKey(to="Author")
    book = models.ForeignKey(to="Book")

    class Meta:
        unique_together = ("author", "book")


*******************************************************************************
注意事项:
  1、一对一的这个关系字段写在两个表的任意一个表里面都可以
  2、多对多的表的建立,
    mysql:手动创建一个第三张表,然后写上两个字段,每个字段外键关联到另外两张多对多关系的表,
    orm的manytomany自动帮我们创建第三张表,两种方式建立关系都可以,但是手动创建的第三张表我们进行orm操作的时候,很多关于多对多关系的表之间的orm语句方法无法使用(set、add、remove、clear)
  3、如果你想删除某张表,你只需要将这个表注销掉,然后执行那两个数据库同步指令就可以了,自动就删除了。
  4、 与Publish建立一对多的关系,外键字段建立在多的一方,外键字段的int类型跟随被关联表的被关联字段。
  5、 一对多还是多对多,写to这个参数的时候,如果是字符串,就不需要考虑建表顺序。

  6、 定义好模型之后,你需要告诉Django _使用_这些模型。你要做的就是修改配置文件中的INSTALL_APPSZ中设置,在其中添加models.py所在应用的名称。

  7、外键字段 ForeignKey 有一个 null=True 的设置(它允许外键接受空值 NULL),你可以赋给它空值 None 。

    生成表的样式:

 

二 表的增删改查

  表的增加

  主要是一对一,一对多,多对多表数据的添加

一对多
方式1:
   publish_obj=Publish.objects.get(nid=1) #拿到nid为1的出版社对象
   book_obj=Book.objects.create(title="金瓶眉",publishDate="2012-12-12",price=100,publish=publish_obj) 
    #出版社对象作为值给publish,过程就是自动将publish字段变成publish_id,然后将publish_obj的id给取出来赋值给publish_id字段,如果不是publish类的对象肯定会报错的
方式2:
   book_obj=Book.objects.create(title="金瓶眉",publishDate="2012-12-12",price=100,publish_id=1)  
    #直接可以写id值,注意字段属性的写法和上面不同,这个是publish_id=xxx,上面是publish=xxx。
    
多对多:
   方式一:
  # 生成的书籍对象
    book_obj=Book.objects.create(title="追风筝的人",price=200,publishDate="2012-11-12",publish_id=1)
    # 作者对象 module对象
    yuan=Author.objects.filter(name="yuan").first() 
    egon=Author.objects.filter(name="alex").first()
    #添加两条数据
    book_obj.authors.add(yuan,egon)
    或book_obj.authors.add(*[])
    #book_obj是书籍对象,authors是book表里面那个多对多的关系字段名称。
    #执行过程:orm就是先通过book_obj的authors属性找到第三张表,然后将book_obj的id值和两个作者对象的id值组合成两条记录添加到第三张表里面去
  方式二
    book_obj.authors.add(1,2)
    book_obj.authors.add(*[1,2]) 
    #这种方式用的最多,因为一般是给用户来选择,用户选择是多选的,选完给你发送过来的就是一堆的id值
 注意事项:
    1、多对多一般在前端页面上使用的时候是多选下拉框的样子来给用户选择多个数据,这里可以让用户选择多个书籍,多个作者
    2、通过orm间接的给第三张表添加数据,也可以通过语句直接给第三张表添加数据

 

    表的删除

1、一对一
    models.Author.objects.filter(id=3).delete()
2、一对多
    models.Book.objects.filter(id=1).delete()
3、多对多
    book_obj = models.Book.objects.filter(nid=4)[0]
    book_obj.authors.remove(2) 
    # 将第三张表中的这个book_obj对象对应的那个作者id为2的那条记录删除
    book_obj.authors.clear()
    #先清除掉所有的关系数据,然后只给这个书对象绑定这个id为2的作者,所以只剩下一条记录 
    book_obj.authors.set('2')
    book_obj.authors.set(['1',])

  表的修改

1、一对一
models.Author.objects.filter(id=3).update(age=66)
2、一对多
models.Book.objects.filter(id=2).update(price=998)
3、多对多
方式一:
data = {'title':'xxx','price':100} #这个书籍对象更新后的数据
models.Book.objects.filter(id=n).update(**data) #将新数据更新到原来的记录中
方式二:
book_obj = models.Book.objects.get(id=1) #获取一个书籍对象
bai = author('name':'xiaobai''age':18)
hei = author('name':'xiaobai''age':18)
author_list =[bai,hei]
book_obj.authors.set(author_list) #将数据和作者的多对多关系加上

三、基于对象的跨表查询

一对一查询

正向查询
egon=Author.objects.filter(name="egon").first()
print(egon.authorDetail.telephone) 
egon.authorDeail
#就拿到了这个对象,因为一对一找到的就是一条记录,
#注意写法:作者对象.字段名,就拿到了那个关联对象

方向查询
# 查询所有住址在北京的作者的姓名
authorDet=AuthorDetail.objects.filter(addr="beijing")[0]
authorDet.author.name

一对多查询:
正向查询:
# ----查询主键为1的书籍的出版社所在的城市
book_obj=Book.objects.filter(pk=1).first()
print(book_obj.publish.city) 
# book_obj.publish 是主键为1的书籍对象关联的出版社对象, 
# 方法:book对象.外键字段名称
方向查询:
publish=Publish.objects.get(name="苹果出版社")
book_list=publish.book_set.all()    
for book_obj in book_list:
    print(book_obj.title)
#publish.book_set.all() : 与苹果出版社关联的所有书籍对象集合,
#写法:小写的表名_set.all(),得到queryset类型数据

多对多查询
正向查询
# 金瓶眉所有作者的名字以及手机号
book_obj=Book.objects.filter(title="金瓶眉").first()
authors=book_obj.authors.all()
for author_obj in authors:
     print(author_obj.name,author_obj.authorDetail.telephone)
方向查询
# 查询egon出过的所有书籍的名字
author_obj=Author.objects.get(name="egon")
book_list=author_obj.book_set.all()        #与egon作者相关的所有书籍
for book_obj in book_list:
    print(book_obj.title)

补充related_name:
    ForeignKey() 和ManyToManyField的定义中设置 related_name 的值来覆写 FOO_set 的名称。
    例如,如果 Article model 中做一下更改:
    publish = ForeignKey(Blog, related_name='bookList')
    #  查询人民出版社出版过的所有书籍的名字与价格(一对多)
    # 反向查询 不再按表名:book,而是related_name:bookList
    queryResult=Publish.objects
              .filter(name="人民出版社")
              .values_list("bookList__title","bookList__price") 

  进阶查询连续跨表

复制代码
# 练习: 查询人民出版社出版过的所有书籍的名字以及作者的姓名

    # 正向查询
    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")

# 练习: 手机号以151开头的作者出版过的所有书籍名称以及出版社名称

# 方式1: queryResult=Book.objects             .filter(authors__authorDetail__telephone__regex="151")             .values_list("title","publish__name") # 方式2: ret=Author.objects .filter(authordetail__telephone__startswith="151") .values("book__title","book__publish__name")
复制代码

 四、基于对象的双下划线查询

基于双下划线的跨表查询

一对查询
    # 查询yuan的手机号
    # 正向查询
    ret=Author.objects.filter(name="yuan").values("authordetail__telephone")
    # 反向查询
    ret=AuthorDetail.objects.filter(author__name="yuan").values("telephone")
一对多查询
# 练习:  查询苹果出版社出版过的所有书籍的名字与价格(一对多) 
    # 正向查询 按字段:publish
    queryResult=Book.objects
            .filter(publish__name="苹果出版社") 
    #通过__告诉orm将book表和publish表进行join,然后找到所有记录中publish.name='苹果出版社'的记录(注意publish是属性名称),然后select book.title,book.price的字段值
            .values_list("title","price") #values或者values_list
    # 反向查询 按表名:book
    queryResult=Publish.objects
              .filter(name="苹果出版社")
              .values_list("book__title","book__price")
多对多查询
# 练习: 查询yuan出过的所有书籍的名字(多对多)
# 正向查询 按字段:authors:
    queryResult=Book.objects
            .filter(authors__name="yuan")
            .values_list("title")
    # 反向查询 按表名:book
    queryResult=Author.objects
              .filter(name="yuan")
              .values_list("book__title","book__price")
​

五 聚合查询、分组查询、F查询和Q查询

  聚合

aggregate(*args, **kwargs)
# 计算所有图书的平均价格
first:一个聚合
 from django.db.models import Avg
 Book.objects.all().aggregate(Avg('price')) #或者给它起名字:aggretate(a=Avg('price'))
 {'price__avg': 34.35}
  aggregate()是一个字典QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。
secodnd:多个聚合
from django.db.models import Avg, Max, Min
Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))  
#count('id'),count(1)也可以统计个数,
{'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}

   分组    

单表分组

查询每一个部门名称以及对应的员工数
emp:
id  name age   salary    dep
1   alex  12   2000     销售部
2   egon  22   3000     人事部
3   wen   22   5000     人事部

原生sql语句:
select dep,Count(*) from emp group by dep;
ORM: emp.objects.values(
"dep").annotate(c=Count("id")
翻译成原生sql语句 select dep,count(
'id') as c from emp grouby dep; #原生sql语句中的as c,不是必须有的 #多表分组查询 多表分组查询: #查询每一个部门名称以及对应的员工数 emp: id name age salary dep_id 1 alex 12 2000 1 2 egon 22 3000 2 3 wen 22 5000 2 dep id name 1 销售部 2 人事部 emp-dep: id name age salary dep_id id name 1 alex 12 2000 1 1 销售部 2 egon 22 3000 2 2 人事部 3 wen 22 5000 2 2 人事部 sql语句: select dep.name,Count(*) from emp left join dep on emp.dep_id=dep.id group by dep.id ORM: dep.objetcs.values("id").annotate(c=Count("emp")).values("name","c") ret = models.Emp.objects.values('dep_id','name').annotate(a=Count(1)) 原生sql语句 ''' SELECT `app01_emp`.`dep_id`, `app01_emp`.`name`, COUNT(1) AS `a` FROM `app01_emp` GROUP BY `app01_emp`.`dep_id`, `app01_emp`.`name` '''
 

   相关例题

    1、查询每一个部门的id以及对应员工的平均薪水
    ret = models.Emp.objects.values('dep_id').annotate(s=Avg('salary'))
                                   
    2、查询每个部门的id以及对对应的员工的最大年龄
    ret = models.Emp.objects.values('dep_id').annotate(a=Max('age'))

    3、查询每个部门的名称以及对应的员工个数和员工最大年龄
    ret = models.Emp.objects.values('dep__name').annotate(a=Count('id'),b=Max('age'))
    #values里面的两个字段进行分组,两个字段同时相同才算是一组,看下面的sql语句
    ret = models.Emp.objects.values('dep__name','age').annotate(a=Count('id'),b=Max('age'))
     
    4、如果不想遍历每一个对象,在values或valuelist
    ret =models.Emp.objects.values('dep__name','age').annotate(a=Count('id'),b=Max('age')) 
    5、 统计每一个出版社的最便宜的书
    publishList=Publish.objects.annotate(MinPrice=Min("book__price")) 
    for publish_obj in publishList:
        print(publish_obj.name,publish_obj.MinPrice)
    6、统计每一本书的作者个数

    ret=Book.objects.annotate(authorsNum=Count('authors__name'))
    ret=models.Book.objects.annotate(authorsNum=Count('authors__name')).values('title','authorsNum') 
  7、 统计每一本以py开头的书籍的作者个数:
    queryResult=Book.objects
     .filter(title__startswith="Py")
     .annotate(num_authors=Count('authors')) 
#执行过程:连接第三张表再连接author表,where title regexp '^Py' 然后按照连表后的大表中的book表title字段进行分组,并且统计对应作者的个数
  8、统计不止一个作者的图书:
    queryResult=Book.objects
       .annotate(num_authors=Count('authors'))
       .filter(num_authors__gt=1)
  9、根据一本图书作者数量的多少对查询集 QuerySet进行排序:
    Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')
  10、查询各个作者出的书的总价格:
    queryResult=Author.objects
         .annotate(SumPrice=Sum("book__price"))
         .values_list("name","SumPrice")
View Code

  总结:
  1、annotate里面必须写个聚合函数,并且必须有个别名=,用哪个字段分组,values里面就写哪个段,annotate其实就是对分组结果的统计,统计你需要什么。
  2、 Emp表示表,values中的字段表示按照哪个字段group by,annotate里面是显示分组统计的是什么
  3、正向与反向的结果可能不同,如果反向查的时候,有的部门还没有员工,那么他的数据也会被统计出来,只不过值为0,但是正向查的话只能统计出来有员工的部门的相关数据,因为通过你是员工找部门,而不是通过部门找员工,结果集里面的数据个数不同,但是你想要的统计结果是一样的

  4、使用分组时,values里面写的个数的别名,

六 F查询 和Q查询

  F查询

    功能:批量修改数据或批量查询数据。   
 查询评论数大于收藏数的书籍
   from django.db.models import F
   Book.objects.filter(commentNum__lt=F('keepNum'))

 查询评论数大于收藏数2倍的书籍
 Book.objects.filter(commentNum__lt=F('keepNum')*2)
 Book.objects.all().update(price=F("price")+30)

  Q查询


from
django.db.models import Q Q(title__startswith='Py') bookList=Book.objects.filter(Q(authors__name="yuan")|Q(authors__name="egon"))
Q(authors__name
="yuan")|Q(authors__name="egon") 等同于WHERE name ="yuan" OR name ="egon" bookList=Book.objects.filter(Q(authors__name="yuan") & ~Q(publishDate__year=2017)).values_list("title")
bookList
=Book.objects.filter(Q(Q(authors__name="yuan") & ~Q(publishDate__year=2017))&Q(id__gt=6)).values_list("title")
bookList
=Book.objects.filter(Q(publishDate__year=2016) | Q(publishDate__year=2017), title__icontains="python")
为什么要使用filter:
filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR 语句),你可以使用Q 对象。
使用方法:
  Q 对象可以使用&(与) 、|(或)、~(非) 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。
注意事项:
  1、可以进行Q嵌套,多层Q嵌套等,加括号嵌套,在生产中比较常用
  2、filter用逗号隔开,表示and关系

 七、ORM执行原生sql语句(了解)

      在django无法满足需求的情况下需要些原生sql语句,在django中提供了两种使用sql的方法。
  first:使用raw()方法,进行原始SQL查询并返回模型实例;
    注意事项1、raw()语法查询必须包含主键。
        2、返回对象:django.db.models.query.RawQuerySet
        3、缺点:很多情况下我们不需要将查询结果映射成模型,或者我们需要执行DELETE、 INSERT以及UPDATE操作。在这些情况下,我们可以直接访问数据库,完全避开模型层。
  second:完全避开模型层,直接执行自定义的SQL语句。
    注意事项:原生SQL还可以使用参数,注意不要自己使用字符串格式化拼接SQL语句,防止SQL注入

class Person(models.Model):
    first_name = models.CharField(...)
    last_name = models.CharField(...)
    birth_date = models.DateField(...)
  可以像下面这样执行原生SQL语句

>>> for p in Person.objects.raw('SELECT * FROM myapp_person'):
...     print(p)
  raw()
   #person可以随意写,使用原生sql的目的主要把sql语句扔给mysql

first:通过row(0)方法
方式一;没有指定查询字段 ret
= models.Student.objects.raw('select id, tname as hehe from app02_teacher') for i in ret: print(i.id, i.hehe)

#方式二:指定查询字段 #raw()方法自动将查询字段映射到模型字段。还可以通过translations参数指定一个把查询的字段名和ORM对象实例的字段名互相对应的字典 d
= {'tname': 'haha'} ret = models.Student.objects.raw('select * from app02_teacher', translations=d) for i in ret: print(i.id, i.sname, i.haha)    #方式三:指定查询字段和传参参 d = {'tname': 'haha'} ret = models.Student.objects.raw('select * from app02_teacher where id > %s', translations=d, params=[1,]) for i in ret: print(i.id, i.sname, i.haha) second: 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()

 八Python脚本中调用Django环境(django外部脚本使用models)

import os

if __name__ == '__main__':
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "BMS.settings") #环境
    import django
    django.setup()
from app01 import models #引入也要写在上面三句之后 #上面的格式是固定的 books = models.Book.objects.all() print(books)
注意:
  1、脚本文件必须写在项目文件夹下

 

 

 

 

posted @ 2019-05-15 20:28  与凯学习  阅读(263)  评论(0编辑  收藏  举报