06.django模型之多表操作

一、创建表结构

作者与作者详细信息是一对一的关系(onetoone)

出版社与书的关系是一对多(onetomany)

书与作者的关系是多对多(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",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、创建多对多关系第三张表方式

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

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方法来管理多对多的关系了。

2、参数

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

to
    设置要关联的表。

to_field
    设置要关联的字段。
    
on_delete
    同ForeignKey字段。

2.2、创建一对多关系字段的一些参数

to
    设置要关联的表

to_field
    设置要关联的表的字段

related_name
    反向操作时,使用的字段名,用于代替原反向查询时的'表名_set'。
related_query_name
    反向查询操作时,使用的连接前缀,用于替换表名。

on_delete
    当删除关联表中的数据时,当前表与其关联的行的行为。

2.3、创建多对多关系字段的一些参数

多对多的参数:
    to
        设置要关联的表

    related_name
        同ForeignKey字段。

    related_query_name
        同ForeignKey字段。
    through
        在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。

        但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过        
    through来指定第三张表的表名。

    through_fields
        设置关联的字段。

    db_table
        默认创建第三张表时,数据库中表的名称。 

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

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

2.5、关于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(可执行对象)
View Code

2.6、ForeignKey的db_contraint

ForeignKey的db_contraint参数

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

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

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

二、添加表记录

publish表:

               

author表:

                

authordetail表:

               

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)
方式2:
    book_obj=Book.objects.create(title="金瓶眉",publishDate="2012-12-12",price=100,publish_id=1)  
#直接可以写id值,注意字段属性的写法和上面不同,这个是publish_id=xxx,上面是publish=xxx。

2、多对多

方式1:
    # 当前生成的书籍对象
    book_obj=Book.objects.create(title="追风筝的人",price=200,publishDate="2012-11-12",publish_id=1)
    # 为书籍绑定的做作者对象
    ee=Author.objects.filter(name="ee").first()
    rr=Author.objects.filter(name="alex").first()
    book_obj.authors.add(ee,rr)
#  将某些特定的 model 对象添加到被关联对象集合中。  book_obj.authors.add(*[])
#book_obj是书籍对象,authors是book表里面那个多对多的关系字段名称。
#其实orm就是先通过book_obj的authors属性找到第三张表,然后将book_obj的id值和两个作者对象的id值组合成两条记录添加到第三张表里面去

方式2:
    book_obj.authors.add(3,4)
    book_obj.authors.add(*[3,4]) #这种方式用的最多,因为一般是给用户来选择,用户选择是多选的,选完给你发送过来的就是一堆的id值

数据库记录如下:

book表:

             

book_authors表:

              

多对多关系其他常用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()

二、基于对象的跨表查询

1、一对多查询(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)

2、一对一查询(Author和AuthorDetail)

正向查询(authorDetail):

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

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

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

3、多对多查询(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):

# 查询qq出过的所有书籍的名字
 
    author_obj=Author.objects.get(name="qq")
    book_list=author_obj.book_set.all()        #与qq作者相关的所有书籍
    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()  # 与北京出版社关联的所有书籍对象集合

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

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

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

2、多对多查询

# 练习: 查询qq出过的所有书籍的名字(多对多)

    # 正向查询 按字段:authors:
    queryResult=Book.objects
            .filter(authors__name="qq")
            .values_list("title")

    # 反向查询 按表名:book
    queryResult=Author.objects
              .filter(name="qq")
              .values_list("book__title","book__price")

3、一对一查询

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

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

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


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


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

5、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查询

1、聚合查询

# 计算所有图书的平均价格
from django.db.models import Avg,Max,Min,Count,Sum
Book.objects.all().aggregate(Avg('price')) #或者给它起名字:aggretate(a=Avg('price'))
{'price__avg': 34.35}

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

from django.db.models import Avg,Max,Min,Count,Sum
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')}

2、分组查询

# 统计一下每个出版社出版书的平均价格
原生sql
根据出版社id分组
select publishs_id,avg(price) from app01_book group by publishs_id;
根据出版社名分组
select avg(app01_book.price) from app01_book inner join app01_publish on app01_book.publishs_id = app01_publish.id group by app01_publish.name;

orm
ret = models.Book.objects.values('publishs_id').annotate(a=Avg('price'))
print(ret)
ret = models.Publish.objects.annotate(a=Avg('book__price'))
print(ret.values('a','name'))

3、F查询

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

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

from django.db.models import F
# 点赞数大于评论数的
# ret = models.Book.objects.filter(dianzan__gt=F('comment'))
# print(ret)
# 所有书籍上调10块
models.Book.objects.all().update(price=F('price')+10) #支持四则运算

4、Q查询

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

from django.db.models import Q
Q(title__startswith='Py')

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

ret = models.Book.objects.filter(Q(comment__gt=30)|Q(dianzan__gt=50))
ret = models.Book.objects.filter(Q(comment__gt=30)&Q(dianzan__gt=50))
等同于# ret = models.Book.objects.filter(comment__gt=30,dianzan__gt=50)

ret = models.Book.objects.filter(Q(comment__gt=30) | Q(dianzan__gt=50),publishDate__year='2018')
# 注意没有Q包裹的条件,写在Q包裹的条件后面.
# 多层嵌套
# ret = models.Book.objects.filter(Q(Q(comment__gt=30) | Q(dianzan__gt=50))&Q(xx=11),publishDate__year='2018')

# 条件取反
# 取评论数小于等于30 的,或者点赞数大于50的
# ret = models.Book.objects.filter(~Q(comment__gt=30)|Q(dianzan__gt=50))
# print(ret)

练习:

#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
'''
注意:如果你使用的mysql5.7及以上版本,那么这道题的答案如下  set sql_mode='STRICT_TRANS_TABLES';  
select * from (SELECT app01_book.xx,app01_book.price,app01_author.id from app01_author       INNER join app01_book_authors on app01_author.id = app01_book_authors.author_id        INNER JOIN app01_book on app01_book_authors.book_id = app01_book.id HAVING 1=1 ORDER BY app01_book.price desc )               as t GROUP BY t.id;

    print(ret)
练习

五、ORM执行原生sql语句

执行原生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()方法自动将查询字段映射到模型字段。还可以通过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()
原文地址:https://www.cnblogs.com/kongxiangqun/p/13701133.html