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

一 建立模型

表和表之间的关系前端

一对1、多对1、多对多 ,用book表和publish表本身来想一想关系,想一想里面的操做,加外键约束和不加外键约束的区别,一对一的外键约束是在一对多的约束上加上惟一约束。python

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

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

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

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

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

  模型创建以下:app

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这个参数的时候,最后后面的值是个字符串,否则你就须要将你要关联的那个表放到这个表的上面

关于多对多表的三种建立方式(目前你先做为了解)函数

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

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

 注意:

当咱们须要在第三张关系表中存储额外的字段时,就要使用第三种方式,第三种方式仍是可使用多对多关联关系操做的接口(all、add、clear等等)

当咱们使用第一种方式建立多对多关联关系时,就没法使用orm提供的set、add、remove、clear方法来管理多对多的关系了。

关键一对一关系字段时的一些参数

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()方法排序过的数据)

 获取元信息,能够经过model对象._meta.verbose_name等获取本身经过verbose_name指定的表名,model对象._meta.model_name获取小写的表名,还有model对象.app_label能够获取这个对象的app应用名等等操做。例如:book_obj = models.Book.objects.get(id=1),book_obj._meta.model_name。

  关于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工具来看看数据库里面的那些表,出版社这个表里面没有任何的关系字段,这种单表的数据,咱们能够先添加几条数据,在进行下面的增删改查的操做。

  生成表以下:

    

    

    

    

    

  注意事项:

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

  我们的表里面包含了一对1、一对多、多对多的关系,咱们基于这几个表来练习,未来不管有多少张表,都逃脱不了这三个关系,操做起来都是同样的。

关于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参数

ForeignKey的db_contraint参数
关系和约束你们要搞清楚,我不加外键能不能表示两个表之间的关系啊,固然能够
可是咱们就不能使用ORM外键相关的方法了,因此咱们单纯的将外键换成一个其余字段类型,只是单纯的存着另一个关联表的主键值是不能使用ORM外键方法的。
#db_constraint=False只加二者的关系,没有强制约束的效果,而且ORM外键相关的接口(方法)还能使用,因此若是未来公司让你创建外键,而且不能有强制的约束关系,那么就能够将这个参数改成False
    customer = models.ForeignKey(verbose_name='关联客户', to='Customer',db_constraint=False)

二 添加表记录

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

publish表: 

author表:

authordetail表:

    

一对多

方式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.publish与book_obj.publish_id是什么?

多对多

方式一: 多对多通常在前端页面上使用的时候是多选下拉框的样子来给用户选择多个数据,这里可让用户选择多个书籍,多个做者  # 当前生成的书籍对象
    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表

    

  book_authors表

    

  核心:book_obj.authors.all()是什么?

  多对多系其它经常使用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',]) #这么写也能够,可是注意列表中的元素是字符串,列表前面没有*,以前我测试有*,感受是版本的问题,没事,可以用哪一个用哪一个

  more

  一对一和一对多的删改和单表的删改是同样的,别忘了删除表的时候,我们是作了级联删除的。

更新:
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()

  

三 基于对象的跨表查询

跨表查询是分组查询的基础,F和Q查询是最简单的,因此认真学习跨表查询

一对多查询(Publish 与 Book)

    

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

一对一查询(Author与AuthorDetail)   img

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

多对多查询(Author与Book)

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

`# 金瓶眉全部做者的名字以及手机号` `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)`

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

`# 查询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)`

    注意:

​ 你能够经过在 ForeignKey() 和ManyToManyField的定义中设置 related_name 的值来覆写 FOO_set 的名称。例如,若是 Article model 中作一下更改:

`publish ``=` `ForeignKey(Book, related_name``=``'bookList'``)`

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

`# 查询 人民出版社出版过的全部书籍` `publish``=``Publish.objects.get(name``=``"人民出版社"``)``book_list``=``publish.bookList.``all``()  ``# 与人民出版社关联的全部书籍对象集合`

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

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

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

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

一对多查询

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

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

一对一查询

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

进阶练习(连续跨表)

# 练习: 查询人民出版社出版过的全部书籍的名字以及做者的姓名


    # 正向查询
    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 ,则用related_name替换 表名,例如:

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

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

聚合

  aggregate(*args, **kwargs)

`# 计算全部图书的平均价格``    ``>>> ``from` `django.db.models ``import` `Avg``    ``>>> Book.objects.``all``().aggregate(Avg(``'price'``)) #或者给它起名字:aggretate(a=Avg('price'))``    ``{``'price__avg'``: ``34.35``}`

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

`>>> Book.objects.aggregate(average_price``=``Avg(``'price'``))``{``'average_price'``: ``34.35``}`

  若是你但愿生成不止一个聚合,你能够向aggregate()子句中添加另外一个参数。因此,若是你也想知道全部图书价格的最大值和最小值,能够这样查询:

`>>> ``from` `django.db.models ``import` `Avg, ``Max``, ``Min``>>> Book.objects.aggregate(Avg(``'price'``), ``Max``(``'price'``), ``Min``(``'price'``))  #count('id'),count(1)也能够统计个数,Book.objects.all().aggregete和Book.objects.aggregate(),均可以``{``'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") #注意: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语句写法:

查询练习

  (1) 练习:统计每个出版社的最便宜的书

`publishList``=``Publish.objects.annotate(MinPrice``=``Min``(``"book__price"``)) #若是没有使用objects后面values或者values_list,获得的结果是queryset类型,里面是Publish的model对象,而且是对全部记``录进行的统计,统计的Minprice也成了这些model对象里面的一个属性,这种连表分组统计的写法最经常使用,思路也比较清晰``for` `publish_obj ``in` `publishList:``    ``print``(publish_obj.name,publish_obj.MinPrice)`

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

  (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') #还有这种写法,看看你能不能明白这是在作什么

  (3) 统计每一本以py开头的书籍的做者个数:

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

  (4) 统计不止一个做者的图书:

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

  (5) 根据一本图书做者数量的多少对查询集 QuerySet进行排序:

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

  (6) 查询各个做者出的书的总价格:

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

F查询与Q查询

F查询

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

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

`# 查询评论数大于收藏数的书籍` `   ``from` `django.db.models ``import` `F``   ``Book.objects.``filter``(commentNum__lt``=``F(``'keepNum'``))`

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

`# 查询评论数大于收藏数2倍的书籍``    ``Book.objects.``filter``(commentNum__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"``)``bookList``=``Book.objects.``filter``(Q(Q(authors__name``=``"yuan"``) & ~Q(publishDate__year``=``2017``))&Q(id__gt=6)).values_list(``"title"``) #能够进行Q嵌套,多层Q嵌套等,其实工做中比较经常使用`

  查询函数能够混合使用Q 对象和关键字参数。全部提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一块儿。可是,若是出现Q 对象,它必须位于全部关键字参数的前面。例如:

`bookList``=``Book.objects.``filter``(Q(publishDate__year``=``2016``) | Q(publishDate__year``=``2017``),``                              ``title__icontains``=``"python"  #也是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)

六 ORM执行原生sql语句

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

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

**执行原生查询*

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

直接执行自定义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()

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

八 补充多个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的方法引入进行进行关联。

  不须要进行其余的配置了,直接执行数据库同步指令就能够了。

相关文章
相关标签/搜索