欢迎来到JIA的博客

django模型层(二)多表操作

 

一、 创建模型

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

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

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

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

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

# 作者表
class Author(models.Model):
    # nid = models.AutoField(primary_key=True) # 修改主键字段名称
    # id 默认
    name=models.CharField(max_length=32)
    # 与AuthorDetail建立一对一的关系,一对一的这个关系字段写在两个表的任意一个表里面都可以
    ad = models.OneToOneField(to="AuthorDetail",to_field="id",on_delete=models.CASCADE)
    # ad = models.OneToOneField("AuthorDetail",)  # 等同于上面注释的这个 django低版本



# 作者详细信息表
class AuthorDetail(models.Model): #不常用的放到这个表里面

    birthday=models.DateField()
    # telephone=models.BigIntegerField()
    telephone=models.CharField(max_length=32)
    addr=models.CharField(max_length=64)

# 出版社表
class Publish(models.Model):

    name = models.CharField(max_length=32)
    city = models.CharField(max_length=32)

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

# 书籍表
class Book(models.Model):

    title = models.CharField(max_length=32)
    publishDate = models.DateField()

    # DecimalField -- Decimal(12,4)类型 -- 完全精度
    price = models.DecimalField(max_digits=5,decimal_places=2) # 999.99

    # 与Publish建立一对多的关系,外键字段建立在多的一方,字段publishs如果是外键字段,那么它自动是int类型
    # to指向表,to_field指向你关联的字段,不写这个,默认会自动关联主键字段,on_delete级联删除
    publishs = models.ForeignKey(to="Publish",to_field="id",on_delete=models.CASCADE)
    # publishs = models.ForeignKey("Publish") # 同上等同于上面注释的这个 django低版本

    # authors不会生成book表的字段,而是生成book表和作者表的第三张关系记录表,通过这个属性可以操作第三张表
    authors = models.ManyToManyField(to='Author',)

 

1.1关于多对多表的创建方式

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

class Author2Book(models.Model):
    author = models.ForeignKey(to="Author")
    book = models.ForeignKey(to="Book")

方式二:通过ManyToManyField自动创建第三张表

# 通过ORM自带的ManyToManyField自动创建第三张表
class Author(models.Model):
    name = models.CharField(max_length=32, verbose_name="作者姓名")
    books = models.ManyToManyField(to="Book")  #自动生成的第三张表我们是没有办法添加其他字段的

方式三:设置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()方法排序过的数据)
创建表时的一些元信息设置

1.2 关于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表示复数形式的显示;中文的单数和复数一般不作区别。

 

 

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

 

 

二 、添加表记录

2.1 一对一

添加一个名称为王照的作者

方式一

关系属性名称= 模型类对象
  •   ad:为定义表时关系属性名称
  • author_detail_obj:模型类对象
author_detail_obj = models.AuthorDetail.objects.get(id=1)
models.Author.objects.create(
           name='王照',
           ad=author_detail_obj  #1 关系属性名称= 模型类对象 
    )      

方式二

关系属性名称_id = 关联的模型类对象的id值
  • ad_id 生成表后显示的外键的字段名
models.Author.objects.create(
        name='王照',
         ad_id=1 # author_detail_obj.id  # 2 关系属性名称_id = 关联的模型类对象的id值
     )

2.2 一对多

添加一个书籍记录

方式一

关系属性名称= 模型类对象
publish_obj = models.Publish.objects.get(id=2)

models.Book.objects.create(
    title='沈阳',
    publishDate='2020-11-11',
    price=20,
    publishs=publish_obj  #模型类对象
   

)

方式二

关系属性名称_id = 关联的模型类对象的id值
models.Book.objects.create(
    title='沈阳',
    publishDate='2020-11-11',
    price=20,
    publishs_id=2  

)

 2.3  多对多

其实就是添加多对多关系记录表的关系记录
添加沈阳这本书 是 王照和玉波写

 方式1

通过模型类对象添加

o1 = models.Author.objects.get(name='王照')  # 2
o2 = models.Author.objects.get(name='玉波')  # 3

book_obj = models.Book.objects.get(title='沈阳')
book_obj.authors.add(o1, o2)  #  2  3
                                             
book_obj.authors.add(*[o1, o2]) #  2  3

方式2

直接通过关系属性值添加

#o1 = models.Author.objects.get(name='王照')  # 2
#o2 = models.Author.objects.get(name='玉波')  # 3

book_obj.authors.add(2, 3)
book_obj.authors.add(*[2,3])

二 、删除表记录

一对一和一对多的删改和单表的删改是一样的,别忘了删除表的时候,咱们是做了级联删除的
models.Book.objects.filter(title='沈阳小青年').delete()

多对多

obj = models.Book.objects.filter(title='白洁').first()
obj.authors.remove(2,3) # 4 2和3 # 将第三张表中的这个obj对象对应的那个作者id为2和3的记录删除
obj.authors.clear() # 清除该书籍对象对应的第三张表里面的所有记录

 

三、修改表记录

一对一和一对多的关系记录操作  和单表一样
 models.Book.objects.filter(title='沈阳').update(
        title='沈阳小青年',
        # publishs=  # 出版社模型类对象
        publishs_id=3
    )

 

多对多

 obj = models.Book.objects.get(title='沈阳小青年')
  obj.authors.set(['3','4'])  #更新 -- 两步:1 删除之前的关系记录  2 添加新记录

 

四、基于对象的跨表查询

相当于mysql的子查询

正向:关系属性写在A,那么通过A表数据去查询B表数据时,就是正向查询

反向:反之就是反向查询

4.1  一对一

正向查询: 靠属性

查询一下 王照作者的家庭住址

author_obj = models.Author.objects.get(name='王照')
# author_obj.ad  # 直接就找到了对应的关系记录对象
print(author_obj.ad.addr)

反向查询: 模型类名小写
查询一下手机号为120的作者名称
author_detail_obj = models.AuthorDetail.objects.get(telephone='120')
print(author_detail_obj.author.name)

 

4.2 一对多  书籍和出版社

正向查询
查询白洁这本书是哪个出版社出版的
book_obj = models.Book.objects.get(title='白洁')
print(book_obj.publishs.name)  # 红浪漫出版社

 

反向查询: 模型类名小写_set  提示你:查询结果可能为多条记录
查询33期出版社出版了哪些书
pub_obj = models.Publish.objects.get(name='33期出版社')
books = pub_obj.book_set.all().values('title')  # 取出反向查询的多条记录
pub_obj.book_set.filter()  # 过滤
print(books)

4.2 多对多  

多对多
正向查询: 属性
查询一下三国这本书是哪几个作者写的
book_obj = models.Book.objects.get(title='三国')
authors = book_obj.authors.all()  #  book_obj.authors objects控制器,得到的也可能是多条记录
print(authors)
反向查询: 模型类名小写_set
查询一下王照这个作者写了哪些书
author_obj = models.Author.objects.get(name='王照')
print(author_obj.book_set.all())

五、基于双下划线的跨表查询

相当于mysql的inner join 连表查询

正向:关系属性写在A,那么通过A表数据去查询B表数据时,就是正向查询

反向:反之就是反向查询

 

5.1 一对一

正向查询: 靠属性
查询一下 王照作者的家庭住址
#正向连表 ad__
ret = models.Author.objects.filter(name='王照').values('ad__addr')
print(ret) #<QuerySet [{'ad__addr': '北京'}]>
反向查询 :模型类名小写  
# 反向连表 author__
ret = models.AuthorDetail.objects.filter(author__name='王照').values('addr',) 
print(ret) #<QuerySet [{'addr': '北京'}]>

 

5.2 一对多 书籍和出版社

查询白洁这本书是哪个出版社出版的
正向查询
ret = models.Book.objects.filter(title='白洁').values('publishs__name')
print(ret)  #<QuerySet [{'publishs__name': '红浪漫出版社'}]>

反向查询

ret = models.Publish.objects.filter(book__title='白洁').values('name')
print(ret) #<QuerySet [{'name': '红浪漫出版社'}]>

5.3  多对多

查询一下三国这本书是哪几个作者写的

正向查询
ret = models.Book.objects.filter(title='三国').values('authors__name')
print(ret) #<QuerySet [{'authors__name': '王照'}, {'authors__name': '玉波'}]>

反向查询

ret = models.Author.objects.filter(book__title='三国').values('name')
print(ret) #<QuerySet [{'name': '王照'}, {'name': '玉波'}]>


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

6.1 聚合查询

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 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它。(结果是字典,所以不能在后面继续使用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'))  
#{'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}

 

6.2 F查询

用于对同一张表中多个字段进行操作时使用

查询一下点赞数大于评论数的书籍
ret = models.Book.objects.filter(dianzan__gt=F('comment')).values('title')
print(ret)

字段做统一操作时也能有效

书籍价格上调10元
  models.Book.objects.all().update(
        price=F('price') + 10,  # 支持四则运算
     )

 

6.3 分组查询

相当于sql语句中的 group by

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

annotate里面必须写个聚合函数,不然没有意义,并且必须有个别名=,别名随便写,但是必须有,用哪个字段分组,values里面就写哪个字段,annotate其实就是对分组结果的统计,统计你需要什么

 

查看一下每个出版社出版书的平均价格

 # 方式1
 ret = models.Book.objects.values('publishs_id').annotate(a=Avg('price'))
 #select avg('price') as a from app01_book group by publishs_id ;

 

# 方式2

# ret = models.Publish.objects.all().annotate()
# 出版社模型类对象: 包含出版社表的所有字段数据和a这个分组统计结果
#默认以id分组
ret = models.Publish.objects.annotate(a=Avg('book__price')).values('name','a')
# select app01_publish.name,avg(app01_book.price) as a from app01_publish inner join app01_book on app01_publish.id = app01_book.publishs_id group by app01_publish.id;
print(ret)
# <QuerySet [{'name': '33期出版社', 'a': 21.0}, {'name': '红浪漫出版社', 'a': 32.0}, {'name': '大头出版社', 'a': None}]>

 

统计每一个出版社的最便宜的书

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)

 

6.4 Q查询

filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR 语句),你可以使用对象
Q 对象可以使用&(与) 、|(或)、~(非) 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。
查询一下点赞数大于等于20 或者 价格大于20的书籍
 ret = models.Book.objects.filter(~Q(dianzan__lt=20) | Q(price__gt=20))

查询一下点赞数小于20 或者 价格大于20的书籍, 并且出版日期为2021年的

ret = models.Book.objects.filter(Q(dianzan__lt=20) | Q(price__gt=20), publishDate__year='2021')  
Q(dianzan__lt=20) | Q(price__gt=20)是一个条件,和逗号后面的是and的关系,如果有的条件没有用Q包裹,那么这个条件要放到被Q包裹的条件后面

支持 Q 嵌套

ret = models.Book.objects.filter(Q(Q(dianzan__lt=20) | Q(price__gt=20))&Q(publishDate__year='2021'))  # 支持 Q 嵌套
print(ret)

 

七 、对原生sql的操作

7.1 查看原生sql语句

方式1

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
}  

方式2:

 ret = models.Book.objects.filter(dianzan__gt=F('comment')).values('title')
    print(ret)

    from django.db import connection
    print(connection.queries)

 

7.2 执行原生sql语句

方式一

查询一下所有书籍
ret = models.Book.objects.raw('select * from app01_book')
# raw只能写操作本表的原生sql
# <RawQuerySet: select * from app01_book>
print(ret)
for i in ret:
    print(i.title,i.price)

 方式2

 from django.db import connection
    # connection -- pymysql连接--conn
 cursor = connection.cursor()
 cursor.execute('select * from app01_book;')
 print(cursor.fetchall())
#((3, '红楼', datetime.date(2021, 2, 4), Decimal('21.00'), 2, 11, 12), (4, '白洁', datetime.date(2021, 2, 4), Decimal('32.00'), 1, 11, 23), (5, '水浒', datetime.date(2021, 2, 4), Decimal('21.00'), 2, 80, 11))

 

八、orm锁和事务

8.1 锁

行级锁

models.Book.objects.select_for_update().filter(id=1)
#手动为id=1加了锁
# select * from app01_book where id=1 for update; 

一般会用到事务当中,

事务中加锁,事务不释放,锁也不释放

8.2事务

方式1: 视图函数中加事务

do_stuff() 没有什么特殊含义,代表事务执行的代码
from django.db import transaction

# 视图中的所有orm或者sql语句都捆绑为了一个事务
@transaction.atomic
def viewfunc(request):
    # This code executes inside a transaction.
    do_stuff() 

方式2:

局部逻辑使用事务

from django.db import transaction

def viewfunc(request):
    # This code executes in autocommit mode (Django's default).
    do_stuff()
    
    #with语句中的所有sql加上了事务
    with transaction.atomic():   
          # models.Book.objects.select_for_update().filter(id=1)
        # This code executes inside a transaction.
        do_more_stuff()

    do_other_stuff()

 

九、python脚本调动django环境

import os

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_orm02.settings")
import django
django.setup()



from app01 import models
ret = models.Book.objects.all()
print(ret)

 

posted @ 2021-03-29 19:34  讷言敏行~  阅读(69)  评论(0编辑  收藏  举报