欢迎来到夜的世界

莫听穿林打叶声,何妨吟啸且徐行。竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生.料峭春风吹酒醒,微冷,山头斜照却相迎。回首向来萧瑟处,归去,也无风雨也无晴。
扩大
缩小

Django的模型层(2)---多表操作

               多表操作               

         创建模型          

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

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

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

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

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

总结 : 

创建一对一的关系:OneToOne("要绑定关系的表名") 

创建一对多的关系:ForeignKey("要绑定关系的表名")

创建多对多的关系:ManyToMany("要绑定关系的表名")  会自动创建第三张表

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)    #自增id可以不写,默认会自增
    title = models.CharField( max_length=32)
    publishDate=models.DateField()              #出版日期
    price=models.DecimalField(max_digits=5,decimal_places=2)   #一共5位数,保留2位小数
    # 不用命名 publish_id ,因为Django 会为我们自动加上 _id 
    # 与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对象转化为字符串的字段.
def __self__(self):
    return self.xxx(#字段,想将models对象转化为字符串的字段)

   字段选项 : 

  每个字段都需要有一些特定的参数 : 

1)null

如果为True,Django 将用NULL 来在数据库中存储空值。 默认值是 False.

(1)blank

如果为True,该字段允许不填。默认为False。
要注意,这与 null 不同。null纯粹是数据库范畴的,而 blank 是数据验证范畴的。
如果一个字段的blank=True,表单的验证将允许该字段是空值。如果字段的blank=False,该字段就是必填的。

(2)default

字段的默认值。可以是一个值或者可调用对象。如果可调用 ,每有新对象被创建它都会被调用。

(3)primary_key

如果为True,那么这个字段就是模型的主键。如果你没有指定任何一个字段的primary_key=True,
Django 就会自动添加一个IntegerField字段做为主键,所以除非你想覆盖默认的主键行为,
否则没必要设置任何一个字段的primary_key=True。

(4)unique

如果该值设置为 True, 这个数据字段的值在整张表中必须是唯一的

(5)choices
由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices ,
默认的表单将是一个选择框而不是标准的文本框,而且这个选择框的选项就是choices 中的选项。

这是一个关于 choices 列表的例子:

YEAR_IN_SCHOOL_CHOICES = (
    ('FR', 'Freshman'),
    ('SO', 'Sophomore'),
    ('JR', 'Junior'),
    ('SR', 'Senior'),
    ('GR', 'Graduate'),
)
每个元组中的第一个元素,是存储在数据库中的值;第二个元素是在管理界面或 ModelChoiceField 中用作显示的内容。 
在一个给定的 model 类的实例中,想得到某个 choices 字段的显示值,就调用 get_FOO_display 方法(这里的 FOO 就是 choices 字段的名称 )。例如:

from django.db import models

class Person(models.Model):
    SHIRT_SIZES = (
        ('S', 'Small'),
        ('M', 'Medium'),
        ('L', 'Large'),
    )
    name = models.CharField(max_length=60)
    shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)


>>> p = Person(name="Fred Flintstone", shirt_size="L")
>>> p.save()
>>> p.shirt_size
'L'
>>> p.get_shirt_size_display()
'Large

 

              添加表记录       

操作前可以先录入一些数据 :

   注意 : 录入数据的时候,先录没有联系的数据

publish 表 : 

books 表 : 

author 表 : 

authordetail 表 : 

 

         一对多 的添加              

# 方式一 : 

publish_obj=Publish.objects.get(nid=1)
book_obj=Book.objects.create(
                title="三人行",
                price=100,
                pub_date="2012-12-12",
                publish=publish_obj
)


# 方式二 : 
book_obj=Book.objects.create(
               title="三人行",
               price=100,
               pub_date="2012-12-12",
               publish_id=1
)

 

          多对多  的添加            

     书和作者的关系是多对多 : 一本书可以有多个作者,一个作者可以写多本书.

  步骤 ; 先找到书对象

      在找到需要的作者对象

                 给书对象绑定作者对象(add 方法),也就是绑定多对多的关系.

# 多对多的添加的两种方式
    # 方式一:
    # 先创建一本书:
    pub_obj=Publish.objects.filter(name="万能出版社").first()
    book_obj = Book.objects.create(title="醉玲珑",publishDdata="2015-4-10",price="222",publish=pub_obj)
    # #通过作者的名字django默认找到id
    haiyan_obj = Author.objects.filter(name="haiyan")[0]
    egon_obj = Author.objects.filter(name="egon")[0]
    xiaoxiao_obj = Author.objects.filter(name="xiaoxiao")[0]
    # 绑定多对多的关系、
    book_obj.authorlist.add(haiyan_obj, egon_obj, xiaoxiao_obj)

    # 方式二=========,查出所有的作者
    pub_obj = Publish.objects.filter(name="万能出版社").first()
    book_obj = Book.objects.create(title="醉玲珑", publishDdata="2015-4-10", price="222", publish=pub_obj)
    authers = Author.objects.all()
    # #绑定多对多关系[打散添加,相当于循环添加]
    book_obj.authorlist.add(*authers)

 解除绑定 : remove : 将某个特定的对象从被关联对象集合中删除.

    book_obj.authors.remove(*[ ])             

    # 解除多对多的关系(remove)
    book_obj=models.Book.objects.filter(title="醉玲珑").last() #找到书对象
    authers=models.Author.objects.filter(id__lt=3)  #找到符合条件的作者对象
    book_obj.authorlist.remove(*authers) #因为清除的是多条,得加个*

清除绑定 : clear : 清空被关联对象的集合.

    # 清除关系方法(clear)
    book_obj= models.Book.objects.filter(title="红楼梦")
    for book_obj_item in book_obj:#把所有红楼梦的都给清空了
        book_obj_item.authorlist.clear()

先清空在设置 : set : 先清空被关联对象的集合在添加 . 

 总结 :   

  1 . remove 和 clear 的区别 : 

    remove : 要将你要清除的数据筛选出来,然后移除

    clear : 不用查, 直接就将数据清空.

  2 . 对于 所有类型的关联字段, add() , remove() , clear() , set() , 都会马上更新数据库,

 

        多表查询         

            基于对象的跨表查询  (相当于sql语句中的where子循环)      

          一对多查询        

正向查询 : 按字段 . 从关联表---->被关联表

反向查询 : 表名小写_set.all() 

   

# 正向查询  :主键为1的书籍的出版社所在的城市

book_obj=Book.objectes.filter(pk=1).first()   
#返回值是一个<QuerySet [<Book: 三人行>]> , 所以 .first() 取第一个对象,或者 [0] 也可以
# book_obj.publish 是主键为1的书籍所在出版社对象
print(book_obj.publish.city)


# 反向查询 : 查询苹果出版社出版的所有的书籍的名称

publish=Publish.objects.get(name="苹果出版社") 
# 因为出版社是唯一的,所以可以用 get 查询
# publish.book_set.all()  与苹果出版社关联的所有书籍的集合对象
book_list=publish.book_set.all()
for book_obj in book_set:
          print(book_obj.title)

             一对一  查询           

# 正向查询 :  查询alex的手机号

tel=Author.objects.filter(name="alex").first()
print(tel.authorDetail.telephone)


#反向查询 : 查询手机号为110的作者的名字

ad=AuthorDatail.objects.filter(telphone=110).first()
print(ad.author.name)
# author 是 Author的小写

              多对多  的查询         

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

注意:

你可以通过在 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实现的)  

Django 还提供了一种直观而高效的方式在查询(lookups)中表示关联关系,它能自动确认 SQL JOIN 联系。要做跨关系查询,就使用两个下划线来链接模型(model)间关联字段的名称,直到最终链接到你想要的 model 为止。(相当于用sql语句用join连接的方式,可以在settings里面设置,可查看sql语句).

  正向查询按字段 , 反向查询按表名小写 . 

          一对多    查询         

# 查询python这本书的出版社的名字 

# 正向查询 ; 
ret=Book.objects.filter(title="python").values("publish_name")  

# 反向查询 : 
ret=Publish.objects.filter(book__title="python").values("name")

     注意 :反向查询时 :  values(告诉Django ,谁和谁要链接在一起形成一张表)

# 查询苹果出版社出版过的所有的书籍和价格 

# 正向查询 , 按 字段 : publish
ret=Book.objects.filter(publish__name="苹果出版社").values("title","price")

# 反向查询 : 按 表名 小写 : book 
ret=Publish.objects.filter(name="苹果出版社").values("book__title","book__price")

 

              多对多 查询            

# 查询alex出版过的所有书籍的名字 (多对多)

# 正向查询 ; 
Book.objects.filter(authors__name="alex").values("title")

# 反向查询 : 
Author.objects.filter(name="alex").values("book_title")
正向查询按字段,反向查询按表明小写
# 查询python这本书的作者的年龄 

# 正向查询 : 
Book.objects.filter(title="python").values("authors__age")

# 反向查询 : 
Author.objects.filter(book__title="python").values("age")

 

            一对一查询            

# 查询alex的手机号
    
# 正向查询 : 
Author.objects.filter(name="alex").values("authorDetail__telephonr")

# 反向查询 : 
AuthorDetail.objects.filter(author__name="alex").values("telephont")

 

# 查询手机号为110的作者的名字 

# 正向查询 : 
author.objects.filter(authorDetail__telephont=110).values("name")

# 反向查询 : 
AuthorDetail.objects.filter(telephtone=110).values("author__name")

 

            连续跨表查询        ******            

     双下划线  __

# 查询苹果出版社出版过的所有的书籍名字和作者姓名 
# 这里就牵扯到了 publish表, author表 , book表,这就要进行连续跨表查询

# 方式一 : 
Punlish.objects.filter(name="苹果出版社").values("book__title","book__authors__name")

# 方式二 ; 
Book.objects.filter(publish__name="苹果出版社").values("title","authors__name")

# 方式三 : 
author.objects.filter(book__publish__name="苹果").values("book__title","name")
# 查询手机号以110开头的作者出版过的所有书的名称以及出版社的名称

# 方式一 : 
AuthorDetail.objects,filter(telephone_startswith="110").values_list("bool__title","book__publish__name")
# 因为作者可能出版过很多书,所有values_list

# 方式二 : 
    # Author.objects.filter(authorDetail__telephonel__startswith=110).values_list("book__title","book__publish__name")

# 方式三 : Book.objects.filter(authors__authorDetail__telephone__startswith=110).values("title","publish__name")

 

             聚合 , 分组      *******            

 聚合 : aggregate(*args,**kwargs),只对一个组进行聚合

from django.db.models import Avg,Sum,Count,Max,Min
# 1、查询所有图书的平均价格
ret=Book.objects.all().aggregate(priceAvg=Avg("price"))
print(ret)   #['priceAvg':142.0]

# 2 .查询所有书籍的个数
ret=Book.objects.all().aggregate(c=count(1))
print(c)    #['c':4]


#注意 : 要给查询的数据加个 ---别名 *****

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

 分组 :  annotate() : 为queryset 中的每一个对象生成一个独立的汇总值,是对分组之后的结果进行聚合.

     总结 : 

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

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

  3 . 单表按主键分组没有意义

 # 1 . 统计每个出版社最便宜的书

#方式一 : 
publishList=Publish.objects.annotate(MinPrice=Min("book__price"))
for publish_obj in publishList:
    print(publish_obj.name,publish_obj.MinPrice)

# annotate的返回值是queryset,如果不想遍历对象,可以用 values_list

# 方式二 : 
Publish.objects.annotate(minPrice=min("book__price")).values_list("name","minPrice")
# 2 查询每一个出版社的名称以及对应的书籍平均价格
    # 方式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: 西瓜出版社>]>
# 3 查询每一个作者的名字以及出版的书籍的最高价格

ret=Author.objects.values("pk","name").annotate(max_price=Max("book__price"))
print(ret)
# ("pk","name") 确保唯一性



#4 . 查询每一个书籍的名称以及对应的作者的个数
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}]>


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


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


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

 

             F查询  和 Q 查询          

F查询:

在上面所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?

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

 from django.db.models import F,Q

# 1 . 比如在Book表里加一个评论数和阅读数,查看评论数大于阅读数的书
Book.objects.filter(commentNum__gt=F("readNum"))


# 2 . 修改操作也可以使用F函数,比如将id大于1的所有的书的价格涨价100元
Book.objects.filter(nid__gt=1).update(price=F("price")+100)

#  Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作。
# 3 . 查询评论数大于收藏数2倍的书籍
Book.objects.filter(commnetNum__lt=F('keepNum')*2)

 

 Q 查询 :

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

      &---与         |---或         ~    非

# 1 . 查询id大于1并且评论数大于100的书
   #方法一 ; 
Book.objects.filter(nid__gt=1,commentNum__gt=100)
   #方法二 : 
Book.objects.filter(Q(nid__gt=1)&Q(commentNum__gt=100))


# 2 . 查询评论数大于100或者阅读数小于200的书
Book.objects.filter(Q(commentNum__gt=100)|Q(readNum__lt=200))

#Q 对象可以使用& 和| 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。

# 3 . 查询年份等于2017年或者价格大于200的书
Book.objects.filter(Q(publishDdata__year=2017)|Q(price__gt=200))


# 4 . 查询年份不是2017年或者价格大于200的书
Book.objects.filter(~Q(publishDdata__year=2017)&Q(price__gt=200))

 

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

bookList=Book.objects.filter(Q(publishDate__year=2016) | Q(publishDate__year=2017),title__icontains="python")
                              

posted on 2018-10-26 15:50  二十四桥_明月夜  阅读(236)  评论(0编辑  收藏  举报

导航