Django模型层 orm多表操作

多表之间的关系

一对多(多对一)

  比如:书和出版社

key:必须在 “多” 的表中加关联字段,外加约束(外键)

约束和关联的区别?

  约束是建立 foreignkey 外键,一般情况下还是要加约束的,这样删除记录的时候可以设置级联删除,这样,当删除一中表的内容,多种的表也会删除,而删除多的表中的内容,由于别人在占用,别人就不能删除,可以防止误删。

  关联是建立一个字段,用来与别的表连接,但是该字段不一定是外键,此时两张表没有关联,不存在级联删除的情况。

一对一

  比如:作者和作者详情

key:可以再任意表中设置关联字段,要加唯一约束

多对多

  比如:作者和书籍

key:通过创建第三张关系表,且关联字段在可以任意表中,在 orm 中有语法可以自动生成第三关系表

    authors = models.ManyToManyField(to="Author")

模型的创建

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

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

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

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

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

模型的建立如下:

from django.db import models

class Book(models.Model):
    nid=models.AutoField(primary_key=True)
    title=models.CharField(max_length=32)
    price=models.DecimalField(max_digits=8,decimal_places=2) # 999999.99
    pub_date=models.DateTimeField()  # "2012-12-12"
    publish=models.ForeignKey(to="Publish",on_delete=models.CASCADE)  # 级联删除,在数据库中默认是publish_id
    authors=models.ManyToManyField(to="Author")  #自动创建第三张表,
    def __str__(self):
        return self.title

class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name=models.CharField(max_length=32)
    email=models.CharField(max_length=32)
    def __str__(self):
        return self.name

class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name=models.CharField(max_length=32)
    age=models.IntegerField()
    email=models.CharField(max_length=32)
    ad=models.OneToOneField(to="AuthorDetail",on_delete=models.CASCADE)  #建立一对一的关系
    #books=models.ManyToManyField("Book")
    def __str__(self):
        return self.name

class AuthorDetail(models.Model):
    addr=models.CharField(max_length=32)
    tel=models.IntegerField()
    #author=models.OneToOneField("Author",on_delete=models.CASCADE)
    def __str__(self):
        return self.addr
View Code

建立的表结构和生成的字段表明如下图:

  关联字段会自动再后面加 _id,

注意事项:

  1. 新建django项目启动之前需要的准备工作

    setting    __init__()     创建数据库    数据库迁移(makemigrations  migrate)

  2. 如果表结构都已经创建好,工作途中想要添加字段,需要给该字段设置默认值,

    但是如果是临时加外键的时候是不能给默认值得,因此在数据库迁移的时候,可以将之前建立的表给关掉(注释掉)或设为空

  3. 表名 app01_book_authors,是根据模型中的元数据自动生成的,也可以覆写为别的名称

  4. id 字段也可以是自动添加的,此时可以不用xie

  5. 对于外键字段,Django 会在字段名上添加"_id" 来创建数据库中的列名

  6. 这个例子中的CREATE TABLE SQL 语句使用PostgreSQL 语法格式,要注意的是Django 会根据settings 中指定的数据库类型来使用相应的SQL 语句。详见单表操作中的setting设置

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

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

添加表记录

之前学过的对于没有关联字段的表添加记录直接用关键字传参:

book = Book.objects.create(title="python",price="100",pub_date="2012-12-12"

  解释:添加记录返回的 book 为刚刚添加的这本书的记录

一对多

 方式一:(常用) 

    publish_id = 1

    book = Book.objects.create(
        title="python",
        price=120,
        pub_date="2012-12-12",
        publish_id=1  #直接等于某一个值,对应 publish 表中的id,此时添加的是 publish_id字段
    )

 方式二:可以按照对象的方式添加

    publish = publish_obj

    pub_obj=Publish.objects.filter(name="橙子出版社").first()

    book=Book.objects.create(
        title="python",
        price=120,
        pub_date="2012-12-12",
        publish=pub_obj  #先取出 橙子出版社 的一个对象,然后此时添加的是 publish字段
    )

注意:book.publish与book.publish_id是什么? 

  book.publish:是这本书的出版社对应的整个记录(对象)

  book.publish_id:是这本书出版社对应的 id 值

多对多

核心:为第三张表中添加记录,第三张表可以用  book_obj.authors     书对象.关联字段

    # 先为Book表添加其他字段以后,
    book_obj = Book.objects.create(title="追风筝的人", price=200, pub_date="2012-11-12", publish_id=1)

    # 方式1  为书籍绑定的做作者对象
    alex=Author.objects.filter(name="alex").first()
    egon=Author.objects.filter(name="egon").first()
    book_obj.authors.add(alex,egon) #可以将作者的整个对象添加到第三张表中

    # 方式2:
    book_obj.authors.add(1,2)  #也可以将主键 1,2 添加

    # 方式3:
    book_obj.authors.add(*[1,2])  #或者主键放入列表中,需要打散

 

注意:book_obj.authors.all()是什么?

  # 查询与这本书关联的所有queryset的集合

 

多对多中其他常用的一些API方法:

book_obj.authors.add(1,2)  #为这本书添加的作者为 1 2 
book_obj.authors.remove(1) #删除作者1的   解除
book_obj.authors.clear()  #删除所有的
book_obj.authors.set(3)  #重置作者的id 解除再绑定 

查询记录

跨表查询

基于对象跨表查询(利用子查询)

正向查询:关联属性所在的表查询关联表记录

反向查询:与之相反,这里的反向查询式,关联字段都是没有设置 teleted_name

key:

  正向查询按字段,多个对象的时候加 .all()

  反向查询,得到一个对象,按表名小写

       得到多个对象,按表名小写_set.all()

 

1、

    一对多: 
                      正向查询按字段:book.publish
            Book -----------------------------------------> Publish
                 <---------------------------------------
                  反向查询表名小写_set.all():pub_obj.book_set.all()

 练习:

    # 1 查询python这本书出版社的名字和邮箱
    book=Book.objects.filter(title="python").first()
    pub_obj=Publish.objects.filter(nid=book.publish_id).first()
    print(pub_obj.name)
    print(pub_obj.email)
    #######################
    book = Book.objects.filter(title="python").first()
    print(book.publish)    #  与book这本书关联出版社对象
    print(book.publish.name)   #  与book这本书关联出版社对象
    print(book.publish.email)   #  与book这本书关联出版社对象

# 2 查询苹果出版社出版的所有的书籍的名称 pub_obj=Publish.objects.get(name="苹果出版社") print(pub_obj.book_set.all()) # queryset print(pub_obj.book_set.all().values("title")) # queryset

2、

    多对多:
                    正向查询按字段 book.authors.all()
            Book  -------------------------------------->Author
                 <--------------------------------------
                    反向查询按表名小写_set.all(): alex.book_set.all()

练习:

    # 查询python这本书籍的作者的年龄
    book=Book.objects.filter(title="python").first()
    ret=book.authors.all().values("age") # 与这本书关联的左右作者的queryset的集合
    print(ret)
# #查询alex出版过的所有的书籍名称 alex=Author.objects.filter(name="alex").first() print(alex.book_set.all())

3、

    一对一:
                     正向查询安字段:alex.ad
        Author -----------------------------------------> AuthorDetail
               <------------------------------------------
                     反向查询按表名小写 ad.author

练习:

    # 查询alex的手机号
    alex = Author.objects.filter(name="alex").first()
    print(alex.ad.tel)
# 查询手机号为110的作者的名字 ad=AuthorDetail.objects.filter(tel=110).first() print(ad.author.name)  

补充:

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

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

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

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

基于上下划线的跨表查询(核心是基于 join实现  拼表)

将多张表拼成一张表,然后按照单表操作,(该方法 常用)

有直接关系的直接拼表,没有直接关系的要连续拼表

key: 正向查询按字段,反向查询按表名小写, 三种对应关系均满足该规律

示例:           book_title

一对多:
# 1 查询python这本书出版社的名字 ret=Book.objects.filter(title="python").values("price") ret=Book.objects.filter(title="python").values("publish__name") print(ret) ret=Publish.objects.filter(book__title="python").values("name") print(ret) # 2 查询苹果出版社出版的所有的书籍的名称 ret=Publish.objects.filter(name="苹果出版社").values("book__title") ret=Book.objects.filter(publish__name="苹果出版社").values("title") 多对多: # 3 查询python这本书籍的作者的年龄 ret=Book.objects.filter(title="python").values("authors__age") print(ret) ret=Author.objects.filter(book__title="python").values("age") # 4 查询alex出版过的所有的书籍名称 ret1=Book.objects.filter(authors__name="alex").values("title") ret2=Author.objects.filter(name="alex").values("book__title") print(ret1,ret2)
一对一: # 5 查询alex的手机号 ret=Author.objects.filter(name="alex").values("ad__tel") ret=AuthorDetail.objects.filter(author__name="alex").values("tel") # 6 查询手机号为110的作者的名字 ret=AuthorDetail.objects.filter(tel=110).values("author__name") ret=Author.objects.filter(ad__tel=110).values("name")

进阶练习(连续跨表)

  book_author__name

    # 查询苹果出版社出版过的所有书籍的名字以及作者的姓名
    ret=Publish.objects.filter(name="苹果出版社").values("book__title","book__authors__name")
    ret=Book.objects.filter(publish__name="苹果出版社").values("title","authors__name")
    ret=Book.objects.filter(publish__name="苹果出版社").values("title","authors__name")
    print(ret)

    # 手机号以110开头的作者出版过的所有书籍名称以及出版社名称
    # 方式1:
    ret=Author.objects.filter(ad__tel__startswith=110).values_list("book__title","book__publish__name")
    print(ret)

    # 方式2:
    ret=AuthorDetail.objects.filter(tel__startswith=110).values("author__book__title","author__book__publish__name")

    # 方式3:
    ret=Book.objects.filter(authors__ad__tel__startswith=110).values("title","publish__name")  

聚合查询和分组查询

在使用聚合函数之前需要在 views 视图函数中 引入: 聚合函数一般会和分组结合使用

from django.db.models import Avg,Max,Sum,Min,Count

聚合查询

aggregate(*args,**kwargs)

    from django.db.models import Avg,Max,Sum,Min,Count
    
    # 查询所有书籍的平均价格
    ret=Book.objects.all().aggregate(priceAvg = Avg("price"))
    print(ret) # {'priceAvg': 142.0}
    
    # 查询所有书籍的个数
    ret=Book.objects.all().aggregate(c = Count(1))  #字段的名字为c
    print(ret) # {'c': 4}

单独的使用聚合 aggregate()  是对整个表(queryset) 求平均值、总数、最大值、最小值、个数等。

将整个表作为一个组,进行分析。

分组查询

 1、分组结构图及解释

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

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

emp:

id  name age   salary    dep
  alex  12   2000     销售部
  egon  22   3000     人事部
  wen   22   5000     人事部


sql语句:
select dep,Count(*) from emp group by dep;

ORM:
emp.objects.values("dep").annotate(c=Count("id")

###################################--多表分组查询--###########################


多表分组查询:

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


emp:

id  name age   salary   dep_id
  alex  12   2000       1
  egon  22   3000       2
  wen   22   5000       2


dep

id   name 
   销售部
   人事部



emp-dep:

id  name age   salary   dep_id   id   name 
  alex  12   2000       1      1    销售部
  egon  22   3000       2      2    人事部
  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")
View Code

 

  orm构建表结构模型

class Emp(models.Model):
    name=models.CharField(max_length=32)
    age=models.IntegerField(default=20)
    dep=models.CharField(max_length=32)
    pro=models.CharField(max_length=32)
    salary=models.DecimalField(max_digits=8,decimal_places=2)

 

2、annotate()

语法:

   Book.objects.annotate( ... ).values( ... )

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

总结 :跨表分组查询本质就是将关联表join成一张表,再按单表的思路进行分组查询。 

3、单表分组查询

示例: 

  annotate()前values哪一个字段就按哪一个字段group by

    # 查询书籍表每一个出版社id以及对应的书籍个数
    # key: annotate()前values哪一个字段就按哪一个字段group by
    ret=Book.objects.values("publish_id").annotate(c=Count(1))   #以publish_id分组,查询每组的个数
    print(ret)

    # 查询每一个部门的名称以及对应员工的平均薪水
    ret=Emp.objects.values("dep").annotate(avg_salary=Avg("salary"))
    print(ret) # [{'dep': '教学部', 'avg_salary': 2500.0}, {'dep': '保洁部', 'avg_salary': 3500.0}, {'dep': '保安部', 'avg_salary': 4000.0}]>

    # 查询每一个省份的名称以及对应的员工最大年龄
    ret=Emp.objects.values("pro").annotate(max_age=Max("age"))
    print(ret) # <QuerySet [{'pro': '山东省', 'max_age': 123}, {'pro': '河南省', 'max_age': 23}, {'pro': '河北省', 'max_age': 56}]>


    # 单表按主键分组没有意义
    # Emp.objects.values("id").annotate()

4、跨表分组查询

#SQL语句
select
app01_publish.name,COUNT(1) from app01_book INNER JOIN app01_publish ON app01_book.publish_id=app01_publish.nid GROUP BY app01_publish.nid

 

示例:

    # 1 查询每一个出版社的名称以及对应的书籍平均价格
    # 方式1:
    ret=Publish.objects.values("name","email").annotate(avg_price=Avg("book__price"))
    print(ret) # <QuerySet [{'name': '苹果出版社', 'avg_price': 117.0}, {'name': '橙子出版社', 'avg_price': 112.0}, {'name': '西瓜出版社', 'avg_price': 222.0}]>
    # 方式2:
    ret=Publish.objects.all().annotate(avg_price=Avg("book__price")).values("name","email","avg_price")
    print(ret) # <QuerySet [<Publish: 苹果出版社>, <Publish: 橙子出版社>, <Publish: 西瓜出版社>]>
    
    # 方式3:(常用的方式)
    ret=Publish.objects.annotate(avg_price=Avg("book__price")).values("name","email","avg_price")
    print(ret) # <QuerySet [<Publish: 苹果出版社>, <Publish: 橙子出版社>, <Publish: 西瓜出版社>]>

    # 2 查询每一个作者的名字以及出版的书籍的最高价格
    ret=Author.objects.values("pk","name").annotate(max_price=Max("book__price"))
    print(ret)

    ret=Author.objects.annotate(maxprice=Max("book__price")).values("name","maxprice")
    print(ret)

    # 3 查询每一个书籍的名称以及对应的作者的个数
    ret=Book.objects.values("title").annotate(c=Count("authors"))
    print(ret) # <QuerySet [{'title': 'python', 'authors__count': 2}, {'title': 'linux', 'authors__count': 1}, {'title': 'go', 'authors__count': 1}, {'title': 'java', 'authors__count': 0}]>

    ret=Book.objects.annotate(c=Count("authors")).values("title","c")
    print(ret)

    # 4 查询作者数不止一个的书籍名称以及作者个数
    ret=Book.objects.annotate(c=Count("authors__name")).filter(c__gt=1).values("title","c")
    print(ret)

    # 5 根据一本图书作者数量的多少对查询集 QuerySet进行排序
    ret=Book.objects.annotate(c=Count("authors__name")).order_by("c")

    # 6 统计每一本以py开头的书籍的名称以及作者个数
    ret=Book.objects.filter(title__startswith="py").annotate(c=Count("authors__name"))

F 与 Q 查询

需要引入:

from django.db.models import F,Q

F查询

涉及到其他字段(条件中有两个以上字段的)要用 F

    # 查询评论数大于100的所有的书籍名称
    ret=Book.objects.filter(comment_count__gt=100).values("title")
# 查询评论数大于2倍点赞数的所有的书籍名称 ret=Book.objects.filter(comment_count__gt=F("poll_count")*2) #两个字段之间进行比较,第二个要用F包住 print(ret) # 给每一本书籍的价格提升100 Book.objects.all().update(price=100+F("price")) #字段用于计算也要用F

Q查询

1、基本使用

  逻辑:  (   &  且,也可以用逗号)(  |   或)(  ~   非)

  涉及到 且 或 非 需要用 Q包住各条件,再进行逻辑判断  

    ret=Book.objects.filter(price__gt=300,comment_count__gt=3000)  #价格大于300且评论数大于3000
    print(ret)

    ret = Book.objects.filter(Q(price__gt=300)|Q(comment_count__gt=3000))  #价格大于300评或者论数大于3000
    print(ret)

    # 判断一下三种:如果用逗号表示且,不加Q的条件必须放在后面
    ret = Book.objects.filter(Q(Q(price__gt=300) | ~Q(comment_count__gt=3000))&Q(poll_count__gt=2000))
    ret = Book.objects.filter(Q(Q(price__gt=300) | ~Q(comment_count__gt=3000)),poll_count__gt=2000)

    ret = Book.objects.filter(poll_count__gt=2000,Q(Q(price__gt=300) | ~Q(comment_count__gt=3000)))  #错误的
    print(ret)

2、Q() 实例化使用

  Q() 实例化之后可以利用字段的字符串作为筛选条件,可以解决当字段为字符串时,如何进行筛选:

Book.objects.filter(price=100)   # 这里的price是字段,并非字符串,如果获取到字段的字符串形式,可以使用Q()实例化的形式

  示例一:

    字符串可以当做字段用,q.children.append(("name","alex")) 里面是元组的形式,可以多次append添加,也可以同时添加多个元组。

  q=Q()  # 实例化一个Q的对象q,我们可以给它加条件
  q.children.append(("name","alex")) # 添加筛选条件,即name字段为alex的记录
  
  q.children.append(("gender","")) # 再添加一个条件,即gender字段为男,与上一个筛选条件的关系默认是"且",即叫alex的男同学
  Student.objects.filter(q) # 过滤name为alex的男同学

  示例二:

    字符串时也可以模糊查询,直接拼 __ 即可,例如:price__gt=100

  q=Q()  # 实例化一个Q的对象q
  q.children.append(("name__contains","")) # 添加筛选条件
  Student.objects.filter(q)   # 过滤name字段中包含"赵"的同学

  示例三:

    当append多个是的时候,默认是且的关系,如果更改,设置,q.connertor="or" 即可

  # 改为"或"的关系
  q=Q()  # 实例化一个Q的对象q
  q.connertor="or"  # 改为"或"的关系
  q.children.append(("name","alex"),("gender","")) 
  Student.objects.filter(q)  # 过滤name为alex或者性别是男的所有同学
                  # 注意:上面示例中都是确定字段,即name字段为alex,gender字段为男,最重要的是q对象中条件元组中除了字符串,也可以是变量。

 

多表操作参考链接:

  https://www.cnblogs.com/yuanchenqi/articles/8963244.html

posted @ 2018-10-26 20:21  葡萄想柠檬  Views(198)  Comments(0)    收藏  举报
目录代码