Django-模型层(多表操作)

2023-10-27

1.创建模型

表和表之间的关系

一对一、多对一、多对多,用book表和publish表自己来想想关系,想想里面的操作,加外键约束和不加外键约束的区别,一对一的外键约束是在一对多的约束上加上唯一约束

实例:我们来假定下面这些概念,字段和关系

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

作者详细模型:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息。作者详情模型和作者模型之间是一对一的关系(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",to_field="nid",on_delete=models.CASCADE) #就是foreignkey+unique,只不过不需要我们自己来写参数了,并且orm会自动帮你给这个字段名字拼上一个_id,数据库中字段名称为authorDetail_id

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

#多对多的表关系,我们学mysql的时候是怎么建立的,是不是手动创建一个第三张表,然后写上两个字段,每个字段外键关联到另外两张多对多关系的表,orm的manytomany自动帮我们创建第三张表,两种方式建立关系都可以,以后的学习我们暂时用orm自动创建的第三张表,因为手动创建的第三张表我们进行orm操作的时候,很多关于多对多关系的表之间的orm语句方法无法使用#如果你想删除某张表,你只需要将这个表注销掉,然后执行那两个数据库同步指令就可以了,自动就删除了。

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如果是外键字段,那么它自动是int类型
    publish=models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE) #foreignkey里面可以加很多的参数,都是需要咱们学习的,慢慢来,to指向表,to_field指向你关联的字段,不写这个,默认会自动关联主键字段,on_delete级联删除   字段名称不需要写成publish_id,orm在翻译foreignkey的时候会自动给你这个字段拼上一个_id,这个字段名称在数据库里面就自动变成了publish_id
    # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表,并且注意一点,你查看book表的时候,你看不到这个字段,因为这个字段就是创建第三张表的意思,不是创建字段的意思,所以只能说这个book类里面有authors这个字段属性
    authors=models.ManyToManyField(to='Author',) #注意不管是一对多还是多对多,写to这个参数的时候,最后后面的值是个字符串,不然你就需要将你要关联的那个表放到这个表的上面

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

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") #联合键

1.2方式二:通过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")  #自动生成的第三张表我们是没有办法添加其他字段的

创建一对一关系字段时的一些参数

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
默认创建第三张表时,数据库中表的名称。            

创建表时的一些元信息设置

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

db_table
    ORM在数据库中的表名默认是 app_类名,可以通过db_table可以重写表名。db_table = 'book_model'

index_together
    联合索引。

unique_together
    联合唯一索引。

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

1.3关于db_column和verbose_name

1.指定字段名: 在定义字段的时候,增加参数db_column=’real_field’;

    2.指定表名: 在model的class中,添加Meta类,在Meta类中指定表名db_table

例如在某个models.py文件中,有一个类叫Info:

class Info(models.Model):  
    ''''' 
            信息统计 
    '''  
    app_id = models.ForeignKey(App)  
    app_name = models.CharField(verbose_name='应用名',  max_length=32, db_column='app_name2')  
  
    class Meta:  
        db_table = 'info'  
        verbose_name = '信息统计'  
        verbose_name_plural = '信息统计'  

其中db_column指定了对应的字段名,db_table指定了对应的表明;

如果不这样指定,字段名默认为app_name, 而表明默认为app名+类名: [app_name]_info.

verbose_name指定在admin管理界面中显示中文;verbose_name表示单数形式的显示,verbose_name_plural表示复数形式的显示;中文的单数和复数一般不作区别。

创建完这个表,我们自己可以通过navicat工具来看看数据库里面的那些表,出版社这个表里面没有任何的关系字段,这种单表的数据,我们可以先添加几条数据,在进行下面的增删改查的操作。

生成表如下: 

1730003-20190928171045726-1510330859.png

1730003-20190928171052033-2039547977.png

1730003-20190928171059786-257308096.png

1730003-20190928171106032-1258419959.png

1730003-20190928171111162-1046571284.png

注意事项:

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

1.4关于on_delete(了解)

关于on_delete参数

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(可执行对象)

ForeignKey的db_contraint参数

关系和约束大家要搞清楚,我不加外键能不能表示两个表之间的关系啊,当然可以

但是我们就不能使用ORM外键相关的方法了,所以我们单纯的将外键换成一个其他字段类型,只是单纯的存着另外一个关联表的主键值是不能使用ORM外键方法的。

#db_constraint=False只加两者的关系,没有强制约束的效果,并且ORM外键相关的接口(方法)还能使用,所以如果将来公司让你建立外键,并且不能有强制的约束关系,那么就可以将这个参数改为False
    customer = models.ForeignKey(verbose_name='关联客户', to='Customer',db_constraint=False)

2.添加表记录

操作前先简单的录入一些数据:还是create和save两个方法,和单表的区别就是看看怎么添加关联字段的数据

publish表:

1730003-20190928171125838-694736720.png

author表:

1730003-20190928171130766-1606031883.png

authordetail表:

1730003-20190928171136087-86947778.png

2.1一对多

方式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。

1730003-20190928171143288-1145134330.png

2.2多对多

   方式一: 多对多一般在前端页面上使用的时候是多选下拉框的样子来给用户选择多个数据,这里可以让用户选择多个书籍,多个作者  # 当前生成的书籍对象
    book_obj=Book.objects.create(title="追风筝的人",price=200,publishDate="2012-11-12",publish_id=1)
    # 为书籍绑定的做作者对象
    yuan=Author.objects.filter(name="yuan").first() # 在Author表中主键为2的纪录,注意取的是author的model对象
    egon=Author.objects.filter(name="alex").first() # 在Author表中主键为1的纪录
  #有人可能会说,我们可以直接给第三张表添加数据啊,这个自动生成的第三张表你能通过models获取到吗,是获取不到的,用不了的,当然如果你知道了这个表的名字,那么你通过原生sql语句可以进行书的添加,所以要通过orm间接的给第三张表添加数据,如果是你手动添加的第三张表你是可以直接给第三张表添加数据
    # 绑定多对多关系,即向关系表book_authors中添加纪录,给书添加两个作者,下面的语法就是告诉orm给第三张表添加两条数据
    book_obj.authors.add(yuan,egon)    #  将某些特定的 model 对象添加到被关联对象集合中。   =======    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值

book表

1730003-20190928171150725-1106342801.png

book_authors表

1730003-20190928171156327-1515447430.png

多对多关系其它常用API:

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

删除示例:

   book_obj = models.Book.objects.filter(nid=4)[0]
    # book_obj.authors.remove(2) #将第三张表中的这个book_obj对象对应的那个作者id为2的那条记录删除
    # book_obj.authors.clear()
    # book_obj.authors.set('2') #先清除掉所有的关系数据,然后只给这个书对象绑定这个id为2的作者,所以只剩下一条记录  3---2,比如用户编辑数据的时候,选择作者发生了变化,那么需要重新选择,所以我们就可以先清空,然后再重新绑定关系数据,注意这里写的是字符串,数字类型不可以
    book_obj.authors.set(['1',]) #这么写也可以,但是注意列表中的元素是字符串,列表前面没有*,之前我测试有*,感觉是版本的问题,没事,能够用哪个用哪个

一对一和一对多的删改和单表的删改是一样的,别忘了删除表的时候,咱们是做了级联删除的

更新:
book_obj = models.Book.objects.get(id=1) #获取一个书籍对象
data = {'title':'xxx','price':100} #这个书籍对象更新后的数据
models.Book.objects.filter(id=n).update(**data) #将新数据更新到原来的记录中
book_obj.authors.set(author_list) #将数据和作者的多对多关系加上

删除:
models.Book.objects.filter(id=1).delete()

3.基于对象的跨表查询

  一对多查询(Publish 与 Book)

1730003-20190928171205569-1097195162.png

正向查询(按字段:publish):关联属性字段所在的表查询被关联表的记录就是正向查询,反之就是反向查询

# 查询主键为1的书籍的出版社所在的城市
book_obj=Book.objects.filter(pk=1).first()
# book_obj.publish 是主键为1的书籍对象关联的出版社对象,book对象.外键字段名称
print(book_obj.publish.city)  

反向查询(按表名:book_set,因为加上_set是因为反向查询的时候,你查询出来的可能是多条记录的集合):

publish=Publish.objects.get(name="苹果出版社")
#publish.book_set.all() : 与苹果出版社关联的所有书籍对象集合,写法:小写的表名_set.all(),得到queryset类型数据
book_list=publish.book_set.all()    
for book_obj in book_list:
       print(book_obj.title)

3.2一对一查询(Author与AuthorDetail)

1730003-20190928171212859-162073064.png

正向查询(按字段:authorDetail):

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

反向查询(按表名:author):不需要_set,因为一对一正向反向都是找到一条记录

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

3.3 多对多查询(Author与Book)

1730003-20190928171218545-1090685525.png

正向查询(按字段:authors):

反向查询(按表名:book_set):

注意:

你可以通过在 ForeignKey() 和ManyToManyField的定义中设置 related_name 的值来覆写 FOO_set 的名称。例如,如果 Article model 中做一下更改:

那么接下来就会如我们看到这般:

在这里我们补充一点,因为你很快就要接触到了,那就是form表单里面的button按钮和form表单外面的button按钮的区别,form表单里面的button按钮其实和input type='submit'的标签是有同样的效果的,都能够提交form表单的数据,但是如果放在form表单外面的button按钮,那就只是个普通的按钮了。提交,还有一点,input type='submit'按钮放到form表单外面那就成了一个普通的按钮。

4.基于双下划线的跨表查询(基于join实现的)

Django 还提供了一种直观而高效的方式在查询(lookups)中表示关联关系,它能自动确认 SQL JOIN 联系。要做跨关系查询,就使用两个下划线来链接模型(model)间关联字段的名称,直到最终链接到你想要的model 为止。

'''
    基于双下划线的查询就一句话:正向查询按字段,反向查询按表名小写用来告诉ORM引擎join哪张表,一对一、一对多、多对多都是一个写法,注意,我们写orm查询的时候,哪个表在前哪个表在后都没问题,因为走的是join连表操作。
'''

4.1一对多查询

# 练习:  查询苹果出版社出版过的所有书籍的名字与价格(一对多) 

    # 正向查询 按字段: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")

4.2多对多查询  

# 练习: 查询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")

4.3一对一查询

    # 查询yuan的手机号
    
    # 正向查询
    ret=Author.objects.filter(name="yuan").values("authordetail__telephone")

    # 反向查询
    ret=AuthorDetail.objects.filter(author__name="yuan").values("telephone")

4.4进阶练习(连续跨表)

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


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

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

# 练习: 查询人民出版社出版过的所有书籍的名字与价格(一对多)# 反向查询 不再按表名:book,而是related_name:bookList
    queryResult=Publish.objects
              .filter(name="人民出版社")
              .values_list("bookList__title","bookList__price") 

5.聚合查询、分组查询、F查询和Q查询

5.1聚合

**aggregate(*args, kwargs)

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

如果你希望生成不止一个聚合,你可以向aggregate()子句中添加另一个参数。所以,如果你也想知道所有图书价格的最大值和最小值,可以这样查询:

5.2分组

###################################--单表分组查询--#######################################################

查询每一个部门名称以及对应的员工数

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") #注意:annotate里面必须写个聚合函数,不然没有意义,并且必须有个别名=,别名随便写,但是必须有,用哪个字段分组,values里面就写哪个字段,annotate其实就是对分组结果的统计,统计你需要什么。'''  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)) '''  SELECT `app01_emp`.`dep_id`, `app01_emp`.`name`, COUNT(1) AS `a` FROM `app01_emp` GROUP BY `app01_emp`.`dep_id`, `app01_emp`.`name`'''#<QuerySet [{'dep_id': 1, 'name': 'alex', 'a': 1}, {'dep_id': 2, 'name': 'egon', 'a': 1}, {'dep_id': 2, 'name': 'wen', 'a': 1}]>,注意,这里如果你写了其他字段,那么只有这两个字段重复,才算一组,合并到一起来统计个数
 
class Emp(models.Model):
    name=models.CharField(max_length=32)
    age=models.IntegerField()
    salary=models.DecimalField(max_digits=8,decimal_places=2)
    dep=models.CharField(max_length=32)
    province=models.CharField(max_length=32)

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

总结 :跨表分组查询本质就是将关联表join成一张表,再按单表的思路进行分组查询,,既然是join连表,就可以使用咱们的双下划线进行连表了。 

#单表:
    #查询每一个部门的id以及对应员工的平均薪水
    ret = models.Emp.objects.values('dep_id').annotate(s=Avg('salary'))
    #查询每个部门的id以及对对应的员工的最大年龄
    ret = models.Emp.objects.values('dep_id').annotate(a=Max('age'))
    #Emp表示表,values中的字段表示按照哪个字段group by,annotate里面是显示分组统计的是什么

#连表:
    # 查询每个部门的名称以及对应的员工个数和员工最大年龄
    ret = models.Emp.objects.values('dep__name').annotate(a=Count('id'),b=Max('age')) #注意,正向与反向的结果可能不同,如果反向查的时候,有的部门还没有员工,那么他的数据也会被统计出来,只不过值为0,但是正向查的话只能统计出来有员工的部门的相关数据,因为通过你是员工找部门,而不是通过部门找员工,结果集里面的数据个数不同,但是你想要的统计结果是一样的
    #<QuerySet [{'a': 1, 'dep__name': '销售部', 'b': 12}, {'a': 3, 'dep__name': '人事部', 'b': 22}]>
    #使用双下划线进行连表,然后按照部门名称进行分组,然后统计员工个数和最大年龄,最后结果里面显示的是部门名称、个数、最大年龄。
#注意:如果values里面有多个字段的情况:ret = models.Emp.objects.values('dep__name','age').annotate(a=Count('id'),b=Max('age')) #是按照values里面的两个字段进行分组,两个字段同时相同才算是一组,看下面的sql语句'''     SELECT `app01_dep`.`name`, `app01_emp`.`age`, COUNT(`app01_emp`.`id`) AS `a`, MAX(`app01_emp`.`age`) AS `b` FROM `app01_emp` INNER JOIN `app01_dep` ON (`app01_emp`.`dep_id` = `app01_dep`.`id`) GROUP BY `app01_dep`.`name`, `app01_emp`.`age`;'''

下面是书籍表和出版社表的一个连表分组的sql语句写法:

1730003-20190928171242522-134459139.png

5.3查询练习

5.3.1练习:统计每一个出版社的最便宜的书

annotate的返回值是querySet,如果不想遍历对象,可以用上valuelist:

queryResult= Publish.objects
            .annotate(MinPrice=Min("book__price"))
            .values_list("name","MinPrice")
print(queryResult)
'''
SELECT "app01_publish"."name", MIN("app01_book"."price")  AS "MinPrice" FROM "app01_publish" 
LEFT  JOIN "app01_book" ON ("app01_publish"."nid" = "app01_book"."publish_id") 
GROUP BY "app01_publish"."nid", "app01_publish"."name", "app01_publish"."city", "app01_publish"."email" 
'''

5.3.2练习:统计每一本书的作者个数

ret=Book.objects.annotate(authorsNum=Count('authors__name'))
ret=models.Book.objects.annotate(authorsNum=Count('authors__name')).values('title','authorsNum') #注意写法,values里面写的个数的别名ret=models.Book.objects.annotate(a=Count('author__name')).filter(a__gt=2).values('title','a') #还有这种写法,看看你能不能明白这是在做什么

5.3.3统计每一本以py开头的书籍的作者个数:

queryResult=Book.objects
           .filter(title__startswith="Py")
           .annotate(num_authors=Count('authors')) #连接第三张表再连接author表,where title regexp '^Py' 然后按照连表后的大表中的book表的title字段进行分组,并且统计对应作者的个数

5.3.4统计不止一个作者的图书:

queryResult=Book.objects
          .annotate(num_authors=Count('authors'))
          .filter(num_authors__gt=1) #filter也是也可以是querset来调用

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

5.3.6查询各个作者出的书的总价格:

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

6.F查询与Q查询

6.1F查询

在上面所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?我们在book表里面加上两个字段:评论数:commentNum,收藏数:KeepNum

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

  Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作。

  修改操作也可以使用F函数,比如将每一本书的价格提高30元:

6.2Q查询

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

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

等同于下面的SQL WHERE 子句:

你可以组合&| 操作符以及使用括号进行分组来编写任意复杂的Q 对象。同时,Q 对象可以使用~ 操作符取反,这允许组合正常的查询和取反(NOT) 查询:

查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。例如:

综合查询练习题

#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
'''

    print(ret)

7.ORM执行原生sql语句(了解)

在模型查询API不够用的情况下,我们还可以使用原始的SQL语句进行查询。

Django 提供两种方法使用原始SQL进行查询:一种是使用raw()方法,进行原始SQL查询并返回模型实例;另一种是完全避开模型层,直接执行自定义的SQL语句。

7.1执行原生查询

raw()管理器方法用于原始的SQL查询,并返回模型的实例:

注意:raw()语法查询必须包含主键

这个方法执行原始的SQL查询,并返回一个django.db.models.query.RawQuerySet 实例。 这个RawQuerySet 实例可以像一般的QuerySet那样,通过迭代来提供对象实例。

举个例子:

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()查询可以查询其他表的数据。

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)

原生SQL还可以使用参数,注意不要自己使用字符串格式化拼接SQL语句,防止SQL注入!

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)

7.2直接执行自定义SQL

有时候raw()方法并不十分好用,很多情况下我们不需要将查询结果映射成模型,或者我们需要执行DELETE、 INSERT以及UPDATE操作。在这些情况下,我们可以直接访问数据库,完全避开模型层。

我们可以直接从django提供的接口中获取数据库连接,然后像使用pymysql模块一样操作数据库。

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

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

如果你想通过自己创建的python文件在django项目中使用django的models,那么就需要调用django的环境:

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)

8.1补充多个app配置models

app01的models文件内容

from django.db import models

# Create your models here.

class UserInfo(models.Model):

    name = models.CharField(max_length=12) 

app02的models文件内容

from django.db import models

# Create your models here.

class Class(models.Model):

    title = models.CharField(max_length=32)
    user = models.ForeignKey('app01.Userinfo')  #如果需要两个app之间的models进行关联,直接这样写就可以,或者直接将那个被关联的表,通过import的方法引入进行进行关联。

转载于:https://www.cnblogs.com/fengqiang626/p/11603923.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Django-模型层(多表操作) 的相关文章

  • 如何有效地从连续字符串中提取文字单词? [复制]

    这个问题在这里已经有答案了 可能的重复 如何将没有空格的文本拆分为单词列表 https stackoverflow com questions 8870261 how to split text without spaces into li
  • 如何在 Windows 10 上使用 python 3.7 的 anaconda 包?

    我使用的是 Windows 10 Pro 64 位版本 我从此页面下载Anaconda3 2019 03 Windows x86 64 exe https www anaconda com distribution https www an
  • 如何忽略传递给函数的意外关键字参数?

    假设我有一些功能 f def f a None print a 现在 如果我有一本字典 比如dct a Foo 我可以打电话f dct 并得到结果Foo打印 但是 假设我有一本字典dct2 a Foo b Bar 如果我打电话f dct2
  • 重新索引错误没有意义

    I have DataFrames大小在 100k 到 2m 之间 我正在处理这个问题的框架是如此之大 但请注意 我必须对其他框架执行相同的操作 gt gt gt len data 357451 现在这个文件是通过编译许多文件创建的 所以它
  • pip 安装失败,SSL 证书验证失败 (_ssl.c:833)

    我无法通过 pip install 安装任何外部 python 模块 我已经正确安装了 python 但如果我使用 pip install 它会显示此错误 这是我运行后的代码pip install pytesseract C Users 1
  • Python grpc protobuf 存根生成问题:--grpc_out: protoc-gen-grpc: 插件失败,状态代码 1

    正如问题所说 我从源代码编译了 grpc 并且也做了sudo pip install grpcio 但是 那which grpc python plugin不返回任何内容 这是一个问题 因为route guide的grpc python示例
  • 在 Python 中绘制分类数据的三个维度

    我的数据包含三个我试图可视化的分类变量 城市 五个之一 职业 四种之一 血型 四种之一 到目前为止 我已经成功地以一种我认为易于使用的方式对数据进行了分组 import numpy as np pandas as pd Make data
  • 使用 Pymongo 从 Windows 连接到 AWS 实例上的 MongoDB

    此行反复抛出错误 client MongoClient ec2 12 345 67 89 us east 2 compute amazonaws com 27017 ssl True ssl keyfile C mongo pem 由于显而
  • 检测/删除 Python 2 + GTK 中不成对的代理字符

    在Python 2 7中我可以成功转换Unicode字符串 abc udc34xyz 转换为 UTF 8 结果是 abc xed xb0 xb4xyz 但是当我将 UTF 8 字符串传递给例如时 pango parse markup or
  • 从主机名中提取域名

    是否有一种编程方式可以从给定的主机名查找域名 给出 gt www yahoo co jp 返回 gt yahoo co jp 有效但非常慢的方法是 拆分为 并从左侧删除 1 个组 使用 dnspython 加入并查询 SOA 记录 当返回有
  • python:是否有用于对输入流进行分块的库函数?

    我想对输入流进行分块以进行批处理 给定一个输入列表或生成器 x in 1 2 3 4 5 6 我想要一个能够返回该输入块的函数 说 如果chunk size 4 then x chunked 1 2 3 4 5 6 这是我一遍又一遍地做的事
  • 根据给定列表中的值替换列中的值[重复]

    这个问题在这里已经有答案了 我在数据框中有一列 仅允许定义列表中存在的值 例如 给定列表 l1 1 2 5 6 如果列表中不存在列中的值 我需要将每个值替换为 0 column Expected column 1 1 5 5 2 2 3 0
  • PyCharm - 如何挂起所有线程

    我们使用 PyCharm 5 0 1 进行多线程调试 当它在断点处停止时 只有特定线程停止 而所有其他线程继续 这使得 冻结时刻 和检查参数值以及其他线程的当前状态变得困难 当其中一个线程在断点处停止时 是否可以挂起所有线程 这在最新的 P
  • 在python中安装scipy模块时出错

    我正在尝试使用 pip 在 python 中安装 scipy 模块 它显示以下错误 Command c users sony appdata local programs python python35 32 python exe u c
  • Python 中的十进制到二进制半精度 IEEE 754

    我只能使用以下命令将十进制转换为二进制单精度 IEEE754struct pack模块 或者使用相反的方法 float16 或 float32 numpy frombuffer 是否可以使用 Numpy 将十进制转换为二进制半精度浮点数 我
  • 如何使用 pygame.mixer 重复音乐?

    我创建了以下使用 pygame mixer 播放 mp3 音乐的代码 然而 音乐不会重复 有什么想法可以让音乐重复播放吗 这是代码 playlist list playlist append put music here mp3 playl
  • 如何限制scrapy请求对象?

    所以我有一个蜘蛛 我认为它正在泄漏内存 结果当我检查 telnet 控制台 gt gt gt prefs 时 它只是从链接丰富的页面中抓取了太多链接 有时它会超过 100 000 个 现在我已经一遍又一遍地浏览文档和谷歌 但我找不到一种方法
  • 如何保持 python 3 脚本 (Bot) 运行

    不是母语英语 抱歉 英语可能很蹩脚 我也是编程新手 您好 我正在尝试使用 QueryServer 连接到 TeamSpeak 服务器来创建机器人 经过几天的努力 它有效 只有 1 个问题 而我却被这个问题困扰了 如果您需要检查 这是我正在使
  • Matplotlib 中的 TwoSlopeNorm 未按预期工作

    我正在尝试创建一个具有发散颜色图的绘图 该颜色图在零附近不对称 In this https stackoverflow com a 20146989 6288682例如 DivergingNorm函数被使用并产生我想要的 然而 我使用的是更
  • 通过 ManyToManyField = Value 对 django 查询集进行排序

    如果有一些模型 例如 class Tag models Model name models CharField class Thing models Model title models CharField tags models Many

随机推荐

  • 解决display:none

    selenium 解决页面元素display none的方法 在UI自动化测试中 有时候会遇到页面元素无法定位的问题 包括xpath等方法都无法定位 是因为前端元素被设置为不可见导致 这篇博客 介绍下如何通过JavaScript修改页面元素
  • 2019CISCN华中赛区分区赛部分wp

    pwn1 64位程序 只开启了NX 栈不可执行 保护 试着运行发现是一个菜单题 选项二 三没用 拖到IDA中查看 发现在encrypt选项中存在gets造成的栈溢出漏洞 不过输进去的字符串被分段异或了 我们可以先进行异或一下 然后在输入程序
  • 嵌入式C惯用法

    1 cpp里的c代码按照c的方式来编译和调用 时常在cpp的代码之中看到这样的代码 ifdef cplusplus extern C endif 一段代码 ifdef cplusplus endif 这样的代码到底是什么意思呢 首先 cpl
  • git for Linux 详细安装步骤 及 详细设置 ----git源码编译安装

    前记 git svn sourcetree gitee github gitlab gitblit gitbucket gitolite gogs 版本控制 仓库管理 系列工程笔记 Platform Ubuntu18 04 LTS Git
  • gulp-rev 和 rev-collector 控制版本总是上一个旧版本的bug原因

    原因是执行顺序的问题 css处理 gulp task css function return gulp src css path pipe stylus config stylus pipe autoprefixer config auto
  • Acwing - 131. 直方图中最大的矩形

    131 直方图中最大的矩形 AcWing题库 题目描述 tag 单调栈 直方图是由在公共基线处对齐的一系列矩形组成的多边形 矩形具有相等的宽度 但可以具有不同的高度 例如 图例左侧显示了由高度为 2 1 4 5 1 3 3 的矩形组成的直方
  • 2021年防关联浏览器哪个好(防关联测评)

    之前文章我已经介绍了防关联浏览器行业的隔离效果比较 这次专门为大家分享一下各个软件的售后客服专业度 软件操作界面易上手程度 以及相应时间方面的比较 作为一个付费用户 尤其是像我这种花了几万学习的亚马逊测评基础知识的同学 本身就没有什么电脑网
  • wifi 小米pro 驱动 黑苹果_macOS安装教程兼小米Pro安装过程记录

    引言 一直以来就想写个macOS的安装教程 可惜没有TransMac以及BIOS设置的截图 正好小米PRO黑苹果高级群的班长 原味菠萝最近一直在写小米Pro安装黑苹果的教程 由于他工作繁忙 一直也没有完稿 我就在他写作的基础上将这篇教程完善
  • Day 6 - 集合及字符串习题

    输入一个字符串 打印所有奇数位上的字符 下标是1 3 5 7 位上的字符 例如 输入 abcd1234 输出 bd24 s1 adkfuyhiwuhtshd print s1 1 2 输入用户名 判断用户名是否合法 用户名长度6 10位 u
  • 小白教程!x2m和x3m音频格式转换.mp3格式

    如果你下载的文件是一个 x2m或 x3m格式文件的 这种格式是经过加密处理的 正常情况下是无法进行格式转换的 不过这里也不是完全没有办法 今天就教你一个比较简单的x2m和x3m格式转换方法 这个格式转换方法需要借助一个小工具来实现 不过它并
  • 初识puppet!

    什么是puppet puppet是一个IT基础设施自动化管理工具 它能够帮助系统管理员管理基础设施的整个生命周期 供应 provisioning 配置 configuration 联动 orchestration 及报告 reporting
  • 创建网页实现上传文件(简明教程)

    在菜鸟教程中有文件和详细教程 链接 菜鸟教程 其中想要实现上传任意文件只需要将upload file php文件中的内容替换为以下代码
  • vsCode 调试c代码时 undefined reference to `WinMain‘

    编辑好的文件没有进行保存 或者是没有定义main函数 保存后执行即可 保存前 保存后 另外注意主函数main 拼写问题
  • 关于Class.forName()

    Class forName xxx 采用类加载机制 传入类的全路径名可以在程序运行的过程中动态地加载一个类 返回这个类的Class对象 这个对象对于任何一个类来说都是单例唯一的 通过这个Class对象利用反射进而可以创建这个类的实例 得到这
  • 【06】Vue_集成mavon-editor编辑器

    06 Vue 集成mavon editor编辑器 官网 https www npmjs com package mavon editor 文章目录 06 Vue 集成mavon editor编辑器 一 mavon editor 安装 二 项
  • OTDR曲线中的事件类型

    我们通过OTDR原理一文了解了反射 散射的原理 现在我们结合上图 形象的看下正常连接点的光反射OTDR图形是什么样子的 首先是UPC连接 大家都知道UPC连接的端面是超球面 即使再精密的端面连接也会有间隙 也就是会有空气 折射率肯定就会变化
  • Python代码中“# -*- coding: utf-8 -*-”的作用及正确的位置

    语句 coding utf 8 的作用是告诉Python解释器和IDE 我这个文件中的内容你们该以什么样的编码进行读取和保存 在Python 3中 默认是以 utf 8 对代码内容进行读取和保存的 语句 coding utf 8 的具体作用
  • 蓝桥杯真题七段码

    题目 七段码对应的a b c d e f g分别用数字0 1 2 3 4 5 6表示 一 DFS check函数 这里与普通的dfs并不相同 因为这里的dfs不要求一定要到达叶子节点 但是dfs方法走了很多多余的路太笨了 这是我最开始的想法
  • vscode常用配置、快捷键及插件使用

    一 配置 通过文件 首选项 设置 或者ctrl 即可打开设置页面 以下是一些常用设置 1 字体 可以直接设置fontsize大小 也可以通过勾选mouse wheel zoom 去使用ctrl 滚轮对字体大小进行缩放 2 word wrap
  • Django-模型层(多表操作)

    1 创建模型 表和表之间的关系 一对一 多对一 多对多 用book表和publish表自己来想想关系 想想里面的操作 加外键约束和不加外键约束的区别 一对一的外键约束是在一对多的约束上加上唯一约束 实例 我们来假定下面这些概念 字段和关系