Welcome to kimi's blog

django之模型层

模型层

模型层前期准备

使用django ORM要注意

  1. django自带的sqlite3数据可对时间字段不敏感,有时候会展示错乱,所以我们习惯切换成常见的数据库比如MySQL。

  2. django ORM并不会自动帮我们创建库,所以需要提前准备好''djangoday01''

  3. id字段是自动添加的,如果想自定义主键,只需要在其中一个字段指定primary_key = True,如果Django发现你已经明确地设置了Field.primary_key,它将不会添加自动ID列。

  4. Django支持MySQL5.5及更高版本。

django测试某个功能层

默认不允许单独测试某个py文件,如果想要测试某个py文件,一般事测试models.py文件

测试环境1:pycharm提供的python console(临时保存,不推荐使用)

测试环境2:自己搭建(自带的test或者自己创建的py文件)

1.拷贝manage.py前四行代码
2.自己再添加两行
	import django
    django.setup()

代码演示:

测试tests.py

# manage.py中拷贝前四行代码
import os
import sys
def main():
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoProject6.settings')
    # 手写两行
    import django
    django.setup()
	# 导入app01文件夹内models文件并 测试models文件内User运行
    from app01 import models
    models.User.objects.all()

models.py

class User(models.Model):
    name = models.CharField(max_length=32, verbose_name='用户名')
    age = models.IntegerField(verbose_name='年龄')
    register_time = models.DateTimeField(verbose_name='注册事件', auto_now_add=True)
    """
    DateField       : 年月日
    DateTimeField   : 年月日 时分秒
    
    两个重要参数
    auto_now        : 每次操作数据的时候 该字段会自动将当前时间更新 
    auto_now_add    : 在创建数据的时候会自动将当前创建时间记录下来 之后只要不人为的修改 那么就一直不变
    """

    def __str__(self):
        return '用户对象:%s' % self.name

image

切换MySQL数据库

1.提前终端创建好库djangoday01
2.将DATABASES的配置更改
	DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'djangoday01',
        'USER':'root',
        'PASSWORD':'123',
        'HOST':'127.0.0.1',
        'PORT':3306,
        'CHARSET':'utf8'

    }
}
3.连接MySQL库
4.makemigrations
5.migrate

如何查看django ORM 底层原理?

django ORM本质还是SQL语句。

1.如果有QuerySet对象,那么可以直接点query查看SQL语句

res =models.User.objects.filter(name='kimi')
print(res.query)
SELECT `app01_user`.`id`, `app01_user`.`name`, `app01_user`.`age`, `app01_user`.`register_time` FROM `app01_user` WHERE `app01_user`.`name` = kimi

结论:有些不是QuerySet对象,就不能通过点query的形式点出来,就只能使用通过的方法

2.如果想查看所有ORM底层的SQL语句,也可以直接在配置文件添加日志记录

res1 = models.User.objects.create(name='kimi',age=16)
print(res.query)  # 会报错

settings最后>>>拷贝代码放在settings

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

单表操作

模型层之ORM常见关键字

基础的增删改查

方法 返回值
create(字段名=数据) 刚创建的数据记录对象
filter(筛选条件) QuerySet列表对象
filter().update(修改内容) 受影响的行数
filter().delete() 受影响的行数即各表受影响的行数

常用的关键字

create 描述
filter 创建数据并直接获取当前创建的数据对象
first/last 根据条件筛选数据 结果是QuerySet [数据对象1,数据对象2]
update 拿queryset里面第一个元素/拿queryset里面最后一个元素
delete 删除数据(批量删除)
all 查询所有数据 结果是QuerySet [数据对象1,数据对象2]
values 根据指定字段获取数据 结果是QuerySet [{}},{},{},{}]
values_list 根据指定字段获取数据 结果是QuerySet [(),(),(),()]
distinct 去重 数据一定要一模一样才可以 如果有主键肯定不行
order_by 根据指定条件排序 默认是升序 字段前面加负号就是降序
get 根据条件筛选数据并直接获取到数据对象 一旦条件不存在会直接报错 不建议使用
exclude 取反操作
reverse 颠倒顺序(被操作的对象必须是已经排过序的才可以)
count 统计结果集中数据的个数
exists 判断结果集中是否含有数据 如果有则返回True 没有则返回False

1.create 创建数据并直接获取当前创建的数据对象

res1 = models.User.objects.create(name='kimi',age=16)
print(res1)
res2 = models.User.objects.create(name='rose', age=18)
print(res2)
res3 = models.User.objects.create(name='jason', age=20)
print(res3)
res4 = models.User.objects.create(name='kiki', age=27)
print(res4)

2.filter() 根据条件筛选数据 结果是QuerySet [数据对象1,数据对象2]

res5 =models.User.objects.filter()
res6 =models.User.objects.filter(name='kimi')
res7 =models.User.objects.filter(name='kimi',age=16 )
print(res5)
print(res6)  # <QuerySet [<User: 用户对象:kimi>, <User: 用户对象:kimi>, <User: 用户对象:kimi>, <User: 用户对象:kimi>]>
print(res7)  # <QuerySet [<User: 用户对象:kimi>, <User: 用户对象:kimi>, <User: 用户对象:kimi>, <User: 用户对象:kimi>]>

3.first()/last() QuerySet支持索引取值但是只支持正数 并且orm不建议你使用索引


res8 =models.User.objects.filter()[1]
# res9=models.User.objects.filter(pk=100)[0]  # 数据不存在索引取值会报错
res10 =models.User.objects.filter(pk=100).first()  # 数据不存在不会报错而是返回None
res11 =models.User.objects.filter(pk=100).last()  # 数据不存在不会报错而是返回None
print(res8)  # 用户对象:kimi
# print(res9)
print(res10)  # None
print(res11)  # None

4.update() 更新数据(批量更新)

models.User.objects.filter().update()  # 批量更新
models.User.objects.filter(id=1).update(9)  # 单个删除

5.delete() 删除数据(批量删除)

models.User.objects.filter().delete()      批量删除
models.User.objects.filter(id=1).delete()  单个删除

6.all() 查询所有数据 结果是QuerySet [数据对象1,数据对象2]

  res = models.User.objects.all()

7.values() 根据指定字段获取数据 结果是QuerySet [{}},{},{},{}]

values()res=models.User.objects.all().values('name')  #结果是QuerySet [{'name': 'kimi'}},{'name': 'rose'},{},{}]
res=models.User.objects.filter().values()  #结果是QuerySet [[{'id': 2, 'name': 'kimi', 'age': 16,.....}]
res=models.User.objects.values()   # <QuerySet [{'id': 2, 'name': 'kimi', 'age': 16,

8.values_list() 根据指定字段获取数据 结果是QuerySet [(),(),(),()]

res=models.User.objects.all().values_list('name','age')  # <QuerySet [('kimi', 16), ('rose', 18), ('jason', 20), ('kiki', 27),...]

9.distinct() 去重 数据一定要一模一样才可以 如果有主键肯定不行

res = models.User.objects.values('name','age').distinct()

10.order_by() 根据指定条件排序 默认是升序 字段前面加负号就是降序

res = models.User.objects.all().order_by('age')

11.get() 根据条件筛选数据并直接获取到数据对象 一旦条件不存在会直接报错 不建议使用

res = models.User.objects.get(pk=1)
print(res)   # 用户对象:kimi
#res = models.User.objects.get(pk=100, name='kimi')
# print(res)  # 报错

12.exclude() 取反操作

res = models.User.objects.exclude(pk=2) 
print(res)   #<QuerySet [<User: 用户对象:rose>, <User: 用户对象:jason>, <User: 用户对象:kiki>]>

13.reverse() 颠倒顺序(被操作的对象必须是已经排过序的才可以)


# res = models.User.objects.all()
res = models.User.objects.all().order_by('age')  # 升序
res1 = models.User.objects.all().order_by('age').reverse()  # 返回升序之前
print(res, res1)

14.count() 统计结果集中数据的个数

res1 =models.User.objects.all().count()
print(res1)  # 4

15.exists() 判断结果集中是否含有数据 如果有则返回True 没有则返回False

# res=models.User.objects.all().exisrt()  报错
res1 = models.User.objects.filter(pk=100).exists()
    print(res1)  # False

基础方法总结
1、返回QuerySet对象的方法有(大多通过模型类.objects.方法调用)
QuerySet对象形似存储了一个个记录对象的列表,但拥有一些特殊的属性,如query。

方法 含义 补充
all() 拿到表中所有记录
filter() 拿到表中所有符合条件的记录 括号中需要写筛选条件
exclude() 拿到表中所有不符合条件的记录 括号中需要写筛选条件
order_by() 按照某个字段进行排序 括号中需要写字段名称
reverse() 将排序好的字段反转排序 只能对排过序的QuerySet对象操作
distinct() 对QuerySet对象进行去重 一般搭配value(*field)使用,传入筛选过字段的记录集合QuerySet对象
raw(sql) 执行原生的sql语句 注意实际的表名与模型表的类名区别

2.特殊的QuerySet

方法 含义 返回值
values() 筛选字段 <QuerySet[{},{},{}]>内部是字典
values_list() 筛选字段 <QuerySet[(),(),()]>内部是元组

3.返回具体对象的

方法 含义 返回值
create(**kwargs) 向表中插入记录表 刚创建的记录对象
QuerySet对象.first() 拿到结果的第一个记录 如果没有返回None
QuerySet对象.last() 拿到结果的最后一个记录 如果没有返回None
QuerySet对象.get(筛选条件) 拿到结果中符合条件的记录 如果没有符合条件则报错

4.返回布尔值的方法有

    exists()

5.返回数字的方法有count()

    count()

ORM执行SQL语言

有时候ORM的操作可能偏低,我们可以自己编写SQL语句。

方式一

res=models.User.objects.raw('select * from app01_user')
print(res)  #<RawQuerySet: select * from app01_user;>

方式二

from django.db import connection
cursor = connection.cursor()
cursor.execute('select name from app01_user;')
print(cursorfetchall())

终端打印
# (('kimi',), ('rose',), ('jason',), ('kiki',), ('ab',), ('ab',), ('ab',))

神奇的双下划线

结果对象还是query对象就可以无限制的点queryset对象的方法。

queryset.filter().values().filter().values_list().filter()....

数据准备

image

django中将字段后加上__条件的方式让关键字参数拥有除等号外的其他含义。

数据查询(双下划线)

__gt 大于
__lt 小于
__gte 大于等于
__lte 小于等于
__in 类似于成员运算,在...里面
__range 在什么范围之内
__contains 是否含有,区分大小写 ,模糊查询
__icontains 是否含有,不区分大小写 ,模糊查询
__year 查询年份
__day 查询日期天数
__second/minute 查看秒/分

双下划线小测试

1.查询年龄大于18的用户数据

res = models.User.objects.filter(age__gte=18)
print(res) # <QuerySet [<User: 用户对象:rose>, <User: 用户对象:jason>, <User: 用户对象:kiki>, <User: 用户对象:jerry>]>

2.查询年龄小于38的用户数据

res = models.User.objects.filter(age__lte=38)
print(res)  # <QuerySet [<User: 用户对象:kimi>, <User: 用户对象:rose>, <User: 用户对象:jason>, <User: 用户对象:kiki>]>

3.大于等于38的用户数据

res = models.User.objects.filter(age__gte=18)
 print(res)  # <QuerySet [<User: 用户对象:rose>, <User: 用户对象:jason>, <User: 用户对象:kiki>, <User: 用户对象:jerry>]>

4.小于等于38的用户数据

res = models.User.objects.filter(age__lte=38)
print(res)  # <QuerySet [<User: 用户对象:kimi>, <User: 用户对象:rose>, <User: 用户对象:jason>, <User: 用户对象:kiki>]>

5.查询年龄是18或者20或者38的数据

res = models.User.objects.filter(age__in=(18,20,38))
print(res)  # <QuerySet [<User: 用户对象:rose>, <User: 用户对象:jason>, <User: 用户对象:kiki>]>

6.查询年龄在18到38范围之间的用户数据

res = models.User.objects.filter(age__range=(18,38))
print(res)  # <QuerySet [<User: 用户对象:rose>, <User: 用户对象:jason>, <User: 用户对象:kiki>]>

7.查询名字中含有字母j的用户数据

1.区分大小写
res = models.User.objects.filter(name__contains='j')
print(res)  # <QuerySet [<User: 用户对象:jason>, <User: 用户对象:jerry>]>
2.不区分大小写
res = models.User.objects.filter(name__icontains='j')
print(res)  # <QuerySet [<User: 用户对象:jason>, <User: 用户对象:jerry>, <User: 用户对象:Jeny>]>

8.查询注册年份是2022的数据

res = models.User.objects.filter(register_time__year=2022)
print(res)  # <QuerySet [<User: 用户对象:rose>, <User: 用户对象:jason>, <User: 用户对象:kiki>, <User: 用户对象:Jeny>]>

多表操作

ORM外键字段的创建

跟MySQL外键关系一样判断规律

1.一对多  外键字段建在多的一方
2.多对多  外键字段统一建在第三张关系表
3.一对一  建在任何一方都可以,但是建议建在查询频率较高的表中
注意:目前关系的判断可以采用表与表之间换位思考原则

基础表的准备

  1. 创建基础表(书籍表、出版社表、作者表、作者详情)

  2. 确定外键关系

    一对一  ORM与MySQL一致 外键字段建在查询较高的一方 
    一对多  ORM与MySQL一致 外键建在多的一方
     多对多  ORM比MySQL有更多的变化
     
	 1.外键字段可以直接建在某张表中(查询频率较高的)
           内部会自动帮你创建第三张关系表
         2.自己创建第三张关系表并创建外键字段
            后续讲解

  1. ORM创建

​ 针对一对多和一对一同步到表中之后自动_id的后缀,如book中的外键字段publish,会自动变成publish_id。

1.一对多关系
publish= models.ForeignKey(to='Publish',on_delete=models.CASCADE)
在多的表中建立外键字段,会在表中产生一个实际的字段(自动加_id后缀)
2.一对一
author_detail = models.OneToField(to='AuthorDetail',on_delete=models.CASCADE)
在查询频率较高的表中建立外键字段,会在表中产生一个实际的字段(自动加入_id后缀)

针对多对多,不会在表中有展示,而是自动创建第三张表

1.多对多
authors=models.ManyToManyField(to='Author')
在查询频率较高的表中建立外键字段(ORM自动创建的,也可自己创建),不会在表中产生实际的字段,而是告诉ORM创建第三张关系表。

模型表创建一对一、一对多和多对多的实例
image

需要注意的是

1.创建一对多关系

和sql语句一样,外键建立到多的那张表上,不同的是,我们可以不讲究关联表和被关联表的建立顺序。字段类为ForeignKey

在django2.x版本以上,建立一对多关系时需要指定on_delete参数为CASCADE,不加会报错,不过也不一定就是CASCADE,可能为其他实参,这里不展开。

建立外键时,系统会自动加上_id后缀作为字段名。

2.创建多对多关系

sql中是将两张表建立好后,将外键字段创建在第三张表中,而django为我们省去了这一步骤,我们可以在多对多关系双方的一个模型表中直接建立一个虚拟外键,ManyToManyField

在底层,sql依旧创建了第三张表来存储两表的多对多关系,但是在orm操作中我们就可以将模型表中的外键当做实实在在的联系,因为在查询时,我们感受不到第三张的表的存在。

多对多关系的外键没有on_delete关键字参数。

3.创建一对多关系

一对一的字段类为OneToOneField,建议建立在查询频率高的一方。

建立一对一关系时需要指定on_delete参数,否则报错。

多对多三种创建方法的补充

注意:多对多关系这种虚拟外键才有add、set、clear、remove,一对一和一对多的表是无法使用的

1.全自动创建

class Book(models.Model):
    title = models.CharField(max_length=32)
    authors=models.ManyToManyField(to='Author')
class Author(models.Model):
    name = models.CharField(max_length=32)

优势:自动创建第三张表,并且提供了add、remove、set、clear四种操作

劣势:第三张表无法创建更多的字段,扩展性较差。如果我们有一些业务逻辑就是在关系表上,我们就无法通过第三张表完成了。

2.纯手动创建

class Book(models.Model):
    title = models.CharField(max_length=32)
class Author(models.Model):
    name = models.CharField(max_length=32)
class Book2Author(models.Model):
    book=models.ForeignKey(to='Book')
    author= models.ForeigKey(to='Author')
    others=models.CharField(max_length=32)
    join_time = models.DataField(auto_now_add=True)

优势:第三张表完全由自己创建,扩展性强

劣势:编写繁琐,并不支持add、remove、set、clear以及正反向概念

3.半自动创建

class Book(models.Model):
	title = models.CharField(max_length=32)
    authors = models.ManyToManyField(to='Author',
                          through='Book2Author',
                          through_fields=('book','author')# 外键在哪个表就把book表放前面
                                        )
class Author(models.Model):
	name = models.CharField(max_length=32)
class Book2Author(models.Model):
    book = models.ForeignKey(to='Book', on_delete=models.CASCADE)
    author = models.ForeignKey(to='Author', on_delete=models.CASCADE)
    others = models.CharField(max_length=32)
    join_time = models.DateField(auto_now_add=True)

优势:第三张表完全由自己创建的,扩展性强,正反向概念依然可以使用

劣势:编写繁琐不再支持add、remove、set、clear

外键字段的相关操作

数据的创建

​ 基本数据:提前将Publish,author以及authordetail三个表的数据信息录入,Book以及关系的绑定在下面详细介绍

一对多和一对一实际外键字段的绑定

1.外键关联的实际字段
针对一对多,插入数据可以直接填写表中的实际字段

models.BooK.objects.create(title='茶花女',price='888.66',publish_id=1)
models.BooK.objects.create(title='红楼梦',price='666.56',publish_id=1)
models.BooK.objects.create(title='西游记',price='555.34',publish_id=2)
models.BooK.objects.create(title='三国演义',price='222.98',publish_id=3)

2.外键的关联对象
针对一对多,插入数据也可以填写表中的类中字段名

publish_obj = models.Publish.objects.filter(pk=1).first()
models.BooK.objects.create(title='水浒传',price=999.05,publish=publish_obj)

3.一对一与一对多插入数据的方式是一致的

关于多对多关系外键字段的绑定

多对多外键属于实际不在模型表中的虚拟字段,多对多关系则需要django提供给我们的方法来实现增删改关系。拿到设立多对多外键的模型表的对象,用它点出外键属性,可以进行add、set、remove方法,这些方法都是这条记录对象的操作。

数据的增加add

  语法:book_obj.authors.add()   
       # 对象.外键.add()
  add可以通过关联的id或者关联的对象进行绑定关系

    book_obj = models.BooK.objects.filter(pk=1).first()
    1.书与作者一对一绑定
    book_obj.authors.add(1)  # 在第三张关系表中给当前书籍绑定作者
    2.书与作者一对多绑定
    book_obj.authors.add(2,3)
    3.作者对象与书对象的绑定
    book_obj = models.BooK.objects.filter(pk=4).first()
    author_obj1 =models.Author.objects.filter(pk=2).first()
    author_obj2 =models.Author.objects.filter(pk=3).first()
    # book_obj.authors.add(author_obj1)  # 可以添加一个作者对象
     book_obj.authors.add(author_obj1,author_obj2)  # 也可同时添加两个作者对象

    总结:add(1)  add(1,2)  add(obj1)  add(obj1,obj2)

数据的修改add和set**

  语法:book_obj.authors.set()  
       # 对象.外键.set()
  set可以通过关联的id或者关联的对象进行修改绑定关系


 4.绑定错误,如何修改使用set修改关系
   	""" 通过id修改的"""
    book_obj = models.BooK.objects.filter(pk=4).first()
    book_obj.authors.set((1,3))  # set括号里面只能填写一个可跌倒对象()/[]或者对象
    """ 原本id=4的书籍绑定的是作者2和作者3,通过set修改数据信息后绑定的是作者1和作者3"""
    
    book_obj.authors.set([2,4])
    """通过对象修改的"""
    
    book_obj = models.BooK.objects.filter(pk=2).first()
    book_obj.authors.add(1,2,4)
    """ id=2的书绑定了作者1,作者2和作者4"""
    
    book_obj = models.BooK.objects.filter(pk=2).first()
    author_obj1=models.Author.objects.filter(pk=1).first()
    author_obj2=models.Author.objects.filter(pk=2).first()
    author_obj4=models.Author.objects.filter(pk=4).first()
    book_obj.authors.set((author_obj1,author_obj2))
    """ id=2的书由绑定的作者1,作者2和作者4修改为作者1和作者4"""
    
    book_obj.authors.set((author_obj1,author_obj2,author_obj4))
    """ 通过修改,id=2的书还是绑定了作者1,作者2和作者4"""
	
	总结set((1,))  set((1,2))  set((obj1,))  set((obj1,obj2))

数据的删除remove

  语法:book_obj.authors.remove()   
       # 对象.外键.remove()
  remove可以通过关联的id或者关联的对象进行移除绑定关系
  
5.数据的删除
    book_obj= models.BooK.objects.filter(pk=1).first()
    author_obj1= models.Author.objects.filter(pk=1).first()
    author_obj2= models.Author.objects.filter(pk=2).first()
    """ 通过id去删除"""
    book_obj.authors.remove(2)  # 作者2
    #book_obj.authors.remove(1,3)
    """ 通过作者对象去删除"""
    book_obj.authors.remove(author_obj1)  # 作者1
    #book_obj.authors.remove(author_obj1,author_obj2)

	总结:remove(1)  remove(1,2)  remove(obj1)  remove(obj1,obj2)
  
  add()\remove()	多个位置参数(数字 对象)
  set()			  可迭代对象(元组 列表) 数字 对象 
  clear()	      情况当前数据对象的关系 ,不需要传参数

数据的清空 clear

      语法:book_obj.authors.clear()   
          # 对象.外键.clear()
      clear() 直接清空与book的id为1关联的所有作者
	  
	  
    """ 清空主键为1的绑定关系"""
    book_obj = models.BooK.objects.filter(pk=1).first()
    book_obj.authors.clear()

ORM跨表查询

MySQL跨表查询的思路

1.子查询
    分步操作:将一条SQL语句用括号括起来当做另外一条SQL语句的条件
2.连表操作
    先整合多张表之后基于单表查询即可
    	inner join	内连接
	left join	左连接
	right join	右连接

ORM跨表查询****的思路

正反向查询的概念(重要)

1.正向查询
    由外键字段所在的表数据查询关联的表数据 正向
2.反向查询
    没有外键字段的表数据查询关联的表数据	 反向
    
ps:正反向的核心就看外键字段在不在当前数据所在的表中   

ORM跨表查询的口诀

正向查询按外键字段
反向查询按表名小写

基于对象的跨表查询(子查询)

数据的准备
image
步骤:

  1. 先根据条件获取数据对象
  2. 判断正反向查询(正向查询按外键,反向查询按表名小写)

正向查询

1.查询主键为1的书籍对应的出版社名称

# 先根据条件获取数据对象
book_obj=models.BooK.objects.filter(pk=1).first()
# 再判断正反向的概念  由书查出版社 外键字段在书所在的表中 所以是正向查询
 print(book_obj.publish.name)  # 人民出版社

2.查询主键为4的书籍对应的作者姓名

book_obj=models.BooK.objects.filter(pk=4).first()
# 再判断正反向的概念  由书查作者 外键字段在书所在的表中 所以是正向查询
print(book_obj.authors)  # app01.Author.None
print(book_obj.authors.all())  # <QuerySet [<Author: Author object (1)>, <Author: Author object (3)>]>
print(book_obj.authors.all().values('name'))  # <QuerySet [{'name': 'kiki'}, {'name': 'rose'}]>
res =book_obj.authors.all().values('name')
for i in res:
    # print(i)
    print(i.get('name'))

3.查询kimi的电话号码

author_obj = models.Author.objects.filter(name='kimi').first()
print(author_obj.author_detail.phone)  # 120

反向查询

4.查询人民出版社出版过的书籍

publish_obj = models.Publish.objects.filter(name='人民出版社').first()
print(publish_obj.book_set)  # app01.BooK.None
print(publish_obj.book_set.all())  # <QuerySet [<BooK: BooK object (1)>, <BooK: BooK object (2)>, <BooK: BooK object (5)>]>
print(publish_obj.book_set.all().values('title'))  # <QuerySet [{'title': '茶花女'}, {'title': '红楼梦'}, {'title': '水浒传'}]>

5.查询kimi写过的书籍

author_obj = models.Author.objects.filter(name='kimi').first()
print(author_obj.book_set)  # app01.BooK.None
print(author_obj.book_set.all().values('title'))  # <QuerySet [{'title': '红楼梦'}]>

6.查询电话号码是110的作者姓名

author_detail_obj = models.AuthorDetail.objects.filter(phone='110').first()
print(author_detail_obj.author)  # Author object (1)
print(author_detail_obj.author.name)  # kiki

总结方法

正向查询:数据对象可以点出它有的外键字段的名称属性
反向查询:数据对象可以点出关联它的表名小写_set的名称属性

基于上下划线的跨表查询

正向查询

1.查询主键为1的书籍对应的出版社名称

res = models.BooK.objects.filter(pk=1).values('publish__name','title')
print(res)  # <QuerySet [{'publish__name': '人民出版社', 'title': '茶花女'}]>

2.查询主键为4的书籍对应的作者姓名

res1 = models.BooK.objects.filter(pk=4).values('authors__name','title')
print(res1)  # <QuerySet [{'authors__name': 'kiki', 'title': '三国演义'}, {'authors__name': 'rose', 'title': '

3.查询kimi的电话号码

res2 = models.Author.objects.filter(name='kimi').values('author_detail__phone','name')
print(res2)  # <QuerySet [{'author_detail__phone': 120, 'name': 'kimi'}]>

反向查询

4.查询人民出版社出版过的书籍和价格

res3= models.Publish.objects.filter(name='人民出版社').values('book__title','book__price')
print(res3)  # <QuerySet [{'book__title': '茶花女', 'book__price': Decimal('888.66')}, {'book__title': '红楼梦', 'book__price': Decimal('666.56')}, {'book__title': '水浒传', 'book__price': Decimal('999.05')}]>

5.查询kimi写过的书籍

res4 = models.Author.objects.filter(name='kimi').values('book__title','name')
print(res4)  # <QuerySet [{'book__title': '红楼梦', 'name': 'kimi'}]>

6.查询电话号码是110的作者姓名

res5 = models.AuthorDetail.objects.filter(phone=110).values('author__name','phone')
print(res5)  # <QuerySet [{'author__name': 'kiki', 'phone': 110}]>

进阶操作

两个表

""" 前提是前面的条件不能使用,只能使用后面的条件"""

1.查询主键为1的书籍对应的出版社名称

res=models.Publish.objects.filter(book__pk=1).values('name')
print(res)  #<QuerySet [{'name': '人民出版社'}]>

2.查询主键为4的书籍对应的作者姓名

res1 = models.Author.objects.filter(book__pk=4).values('name')
print(res1)  #<QuerySet [{'name': 'kiki'}, {'name': 'rose'}]>

3.查询kimi的电话号码

res2 = models.AuthorDetail.objects.filter(author__name='kimi').values('phone')
print(res2)  # <QuerySet [{'phone': 120}]>

4.查询人民出版社出版过的书籍和价格

res3=models.BooK.objects.filter(publish__name='人民出版社').values('title','price')
print(res3)  # <QuerySet [{'title': '茶花女', 'price': Decimal('888.66')}, {'title': '红楼梦', 'price': Decimal('666.56')}, {'title': '水浒传', 'price': Decimal('999.05')}]>

5.查询kimi写过的书籍

res4 = models.BooK.objects.filter(authors__name='kimi').values('title')
print(res4)  # <QuerySet [{'title': '红楼梦'}]>

6 查询电话号码是110的作者姓名

res5 = models.Author.objects.filter(author_detail__phone=110).values('name')
print(res5)  # <QuerySet [{'name': 'kiki'}]>

三个表

1.查询主键为4的书籍对应的作者的电话号码

res = models.BooK.objects.filter(pk=4).values('authors__author_detail__phone')
print(res)  # <QuerySet [{'authors__author_detail__phone': 110}, {'authors__author_detail__phone': 119}]>
"""1.拿到主键为4的book对象
   2.书与作者是多对多关系,从书到作者是正向查询,使用外键authors,进入了author表
   3.author表与作者详情表是一对多的关系,从作者表到作者详情表是正向查询,使用外键author_detail,进入作者详情表
   4.在作者详情表使用表中phone获取号码 """
res1 =models.Author.objects.filter(book__pk=4).values('author_detail__phone')
print(res1)   # <QuerySet [{'author_detail__phone': 110}, {'author_detail__phone': 119}]>

"""1.拿到author作者对象
   2.书与作者是多对多关系,从作者到书是反向查询,使用表名小写book,知道主键为4,可以拿到主键为4的书名
   3.author表与作者详情表是一对多的关系,从作者表到作者详情表是正向查询,使用外键author_detail,进入作者详情表
   4.在作者详情表使用表中phone获取号码 """

res2=models.AuthorDetail.objects.filter(author__book__pk=4).values('phone')
print(res2)  # <QuerySet [{'phone': 110}, {'phone': 119}]>
"""1.拿到AuthorDetail作者详情表对象,作者与作者详情表有关系,作者与书有关系,
   2.作者与作者详情表是一对一关系,从作者详情表到作者是反向查询,使用表名小写author,知道主键为4,可以拿到主键为4的书名
   3.在作者详情表使用表中phone获取号码 ,"""

聚合查询

​ 聚合函数:Max Min Sum Count Avg

函数名 描述
Max 大于
Min 小于
Sum 求和
Count 统计某个数据
Avg 平均值

image

如果我们在ORM中使用聚合函数,ORM支持单独使用聚合函数,步骤如下:

1.引入模块
from django.db.models import Max,Min,Sum,Count,Avg
2.使用关键字aggregate
res = models.Book.objects.aggregate(Max('xx'),Min('xx'),最小价格=Sum('xx'),拼averge=Avg('xxx'))  # 建议取名要英文,不要用中文

eg:
     
    from django.db.models import Max,Min,Sum,Count,Avg
    res= models.BooK.objects.aggregate(Max('price'),Min('price'),Sum('price'),Count('pk'),Avg('price'))
    print(res)  # {'price__max': Decimal('999.05'), 'price__min': Decimal('222.98'), 'price__sum': Decimal('3332.59'), 'pk__count': 5, 'price__avg': Decimal('666.518000')}

分组查询

引入

​ 如果执行ORM分组查询报错,并且又关键sql_mode / strict mode ,那么就去移除sql_mode中的only_full_group_by

  1. 统计每一本的作者个数
    res = models.BooK.objects.annotate(author_num=Count('authors__pk')).values('title','author_num')
    print(res)  # <QuerySet [{'title': '茶花女', 'author_num': 1}, {'title': '红楼梦', 'author_num': 3}, {'title': '西游记', 'author_num': 0}, {'title': '三国演义', 'author_num': 2}, {'title': '水浒传', 'author_num': 0}]>
  1. 统计出每个出版社卖的最便宜的书的价格
    res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name','min_price')
    print(res)  # <QuerySet [{'name': '人民出版社', 'min_price': Decimal('666.56')}, {'name': '商务印书馆', 'min_price': Decimal('555.34')}, {'name': '上海译文出版社', 'min_price': Decimal('222.98')}, {'name': '国家图书馆出版社', 'min_price': None}]>

  1. 统计不止一个作者的图书
1.先统计每本书的作者个数
    res = models.Book.objects.annotate(author_num=Count('authors__pk'))
2.筛选出作者个数大于1的数据    
    res = models.BooK.objects.annotate(author_num =Count('authors__pk')).filter(author_num__gt=1).values('title','author_num')
    print(res)  # <QuerySet [{'title': '红楼梦', 'author_num': 3}, {'title': '三国演义', 'author_num': 2}]>
  1. 查询每个作者出的书的总价格
  res=models.Author.objects.annotate(book_sum=Sum('book__price')).values('name','book_sum')
    print(res)  # <QuerySet [{'name': 'rose', 'book_sum': Decimal('1111.64')}, {'name': 'kiki', 'book_sum': Decimal('889.54')}, {'name': 'kimi', 'book_sum': Decimal('666.56')}, {'name': 'jerry', 'book_sum': Decimal('666.56')}]>

上述分组都是按照表来分组,我们也可以按照表中的字段名来分组

1.按照表分组
	models.表名.objects.annotate()
2.按照表中字段名来分组
	models.表名.objects.values('字段名').annotate()
    eg:
        res= models.Book.objects.values('publish_id').annotate(count_pk=Count('pk')).values('publish_id','count_pk')
        print(res)
        
  注意:values在annotate前就是按照values()括号里面字段名来分组;values()在annotate后就是按照前面的表名分组,values就是拿值的

F与Q查询

注:字段中填写了两个参数,一个是null,即允许为空(默认情况下为非空),一个是default默认值,即在记录中这个字段如果没填写则自动填充的值。如果不设置这两个参数,那么在添加这两个字段的时候,因为表中已经有数据了,所以原本记录的这两个字段即要求非空又没有默认值就无法迁移。然后,涉及到我们的数据迁移,需要执行那两条命令makemigrations migrate。

数据添加,首先在Book表中添加两个字段库存数和卖出数。但是由于我们前面就已经数据迁移过了,那么如何处理呢?

"""后续添加两个字段,必须要加入默认值"""
inventory_number=models.IntegerField(verbose_name='库存数',default=1000)
number_sold = models.IntegerField(verbose_name='售出数',null=True)

image

将售出数添加后方便后续操作,如下展示

image

F查询

​ 当查询条件不是很明确的,也需要从数据库中获取,就需要使用F查询。
简单理解:

  1. 两个字段进行比较的筛选条件(库存数大于卖出数),
  2. 在原来的数值字段增加数值(888),
  3. 在原来的字段名后面加字(+爆款)
    在上述条件等,我们只借助ORM操作,是实现不了的,我们需要在ORM中就需要借助F方法。

1.查询库存数大于卖出数的书籍

    from django.db.models import F
    res = models.BooK.objects.filter(inventory_number__gt=F('number_sold')).values('title')
    print(res)  # <QuerySet [{'title': '红楼梦'}, {'title': '西游记'}]>

2.将所有书的价格涨800

    from django.db.models import F
    res=models.BooK.objects.update(price=F('price')+800)
    print(res)  # 5

image

3.将所有书的名称后面追加爆款

    from django.db.models.functions import Concat
    from django.db.models import Value
    models.BooK.objects.update(title=Concat(F('title'),Value('爆款')))

image

Q查询
在ORM操作中,筛选条件中存在或、非的关系需要借助Q方法来实现。

符号 描述
,(逗号) and的关系
| or的关系
~ not的关系

1.查询主键是1或者价格大于2000的书籍

1.常用的查询方法
    res = models.BooK.objects.filter(pk=1,price__gt=2000).values('title')  # 逗号默认是and关系
    print(res)  # <QuerySet [{'title': '茶花女爆款'}]>

2.逗号是and
    from django.db.models import Q
    res = models.BooK.objects.filter(Q(pk=1),Q(price__gt=2000)).values('title')  # 逗号是and
    print(res)  # <QuerySet [{'title': '茶花女爆款'}]>

3.|是or
    res1 = models.BooK.objects.filter(Q(pk=1) | Q(price__gt=2000)).values('title')  # |是or
    print(res1)  # <QuerySet [{'title': '茶花女爆款'}, {'title': '红楼梦爆款'}, {'title': '西游记爆款'}, {'title': '水浒传爆款'}]>

4.~是not
    res2 = models.BooK.objects.filter(~Q(pk=1 ) | Q(price__gt=2000)).values('title')  # ~是not
    print(res2)  # <QuerySet [{'title': '茶花女爆款'}, {'title': '红楼梦爆款'}, {'title': '西游记爆款'}, {'title': '三国演义爆款'}, {'title': '水浒传爆款'}]>

Q方法使用总结:

  • 两个条件是或关系Q(条件1) | Q(条件2)
  • 两个条件是非关系~Q(条件)

Q查询进阶操作

from django.db.models import Q
q_obj = Q()  # 1.产生一个Q对象
q_obj.connector =''   # 默认是多个条件的连接时and
q_obj.connector ='or'  # 可以修改为or
q_obj.children.append(('pk',1)) # 2.添加查询条件
q_obj.children.append(('price__gt',2000)) # 支持添加多个
res = models.Book.objects.filter(q_obj)  # 查询支持直接填写Q对象

查询条件成为'字符串',也就意味着,可以让用户交互,进行筛选条件的选择与添加。

ORM查询优化

  1. ORM的查询默认都是惰性查询

  2. ORM的查询自带分页处理

    将来如果自己做SQL,最好要加上LIMIT分页
    
  3. only与defer

# res = models.Book.objects.all() # 获取所有的[数据对象1,],不走sql语句
只想拿某些字段的话,使用value,但是value拿到的是[{},{},{}]。只有only可以实现
1.only
    '''数据对象+含有指定字段对应的数据'''
    # res = models.Book.objects.only('title', 'price')
    # print(res)  # queryset [数据对象、数据对象]
    # for obj in res:
        # print(obj.title)  # 点击括号内填写的字段 不走SQL查询
        # print(obj.price)
        # print(obj.publish_time)  # 可以点击括号内没有的字段获取数据 但是会走SQL查询
        
    总结:only会将括号内填写的字段封装到数据对象中,后续获取不走SQL,但是获取括号内没有的字段数据则需要走SQL
        
2.defer   
    res = models.Book.objects.defer('title', 'price')
    # print(res)  # queryset [数据对象、数据对象]
    for obj in res:
        # print(obj.title)  # 点击括号内填写的字段 走SQL查询
        # print(obj.price)
        print(obj.publish_time)  # 点击括号内没有的字段获取数据 不走SQL查询
        
 总结:defer会将括号内填写的字段封装到数据对象中,后续获取有字段需要走SQL,但是获取括号内没有的字段数据则不需要走SQL
    

4.select_related与prefetch_related

括号内都是只支持一对一和一对多外键字段,多对多外键字段是不支持的。

1.select_related  连表操作
    # res = models.Book.objects.all()
    # for obj in res:
    #     print(obj.publish.name)  # 每次查询都需要走SQL
""" 通过点book外的表,每次查询都会走SQL,如果不想走SQL,可以将两个表连起来后再查询不走SQL语句"""
    # res = models.Book.objects.select_related('authors')  # 先连表后查询封装
    # res1 = models.Author.objects.select_related('author_detail')  # 括号内不支持多对多字段 其他两个都可以
    # print(res1)
    # for obj in res:
    #     print(obj.publish.name)  # 不再走SQL查询

   """ select_related括号内填写一对多、一对一字段 自动连表然后继续数据封装"""
2.prefetch_related  子查询
    res = models.Book.objects.prefetch_related('publish')  # 子查询
    for obj in res:
        print(obj.publish.name)
        
   """ prefetch_related括号内填写一对多、一对一字段 基于子查询然后封装数据  """

ORM事务操作

引入事务

1.事务的四大特性
	原子性、一致性、隔离性、持久性
2.相关SQL关键字
	start transaction;
	rollback;
	commit;
	savapoint;
3.相关重要概念
	脏读、幻读、不可重复读、MVCC多版本控制

django orm提供了至少三种开启事务的方式

方式一:全局开启事务

配置文件数据库(DATABASE)相关添加键值对   全局有效
"ATOMIC_REQUESTS":True每次请求所涉及到的orm操作同属于一个事务

如果过程中报错了,就会往回滚

方式二:装饰器 局部有效

from django.db import transaction # 引入事务模块
@transaction.atomic  # 原子装饰器
def index():pass

eg:
 from django.db import transaction
@transaction.atomic
def index():
    model.book.objects.create(title='简爱',price='345',publish_id=1)
    hhshhsldlllds   # 报错>>>就会过滚
    return HttpResponse('添加成功')
    return 123  #在事务里面,这也算成功

​ 装饰器针对的是视图函数,当视图函数需要设计ORM操作,如果函数从上往下执行,遇到报错会自动回滚到视图函数开始的状态。

方式三:with 上下文管理 局部有效

from django.db import transaction
def reg():
    with transaction.atomic():
        pass  # 这下面看作是同一个事务处理,遇到报错就会回滚

ORM常用字段类型

1.AutoField(primary_key)  # 字段才用,可以让它自动创建
2.CharField(max_length)   # 对应varchar字段,存储有限的字符
3.IntegerField            # 整型
4.BigIntergerField        #整型(比如手机号11位)
5.DecimalField(max_digits,decimal_places)   # 小数字段
6.DateField(auto_now,aoto_now_add)    # 日期(年月日)
7.DateTimeField(auto_now auto_now_add)  #日期(年月日时分秒)
8.Booleanfiels:传布尔值自动存0(False)或者1(True)   
9.TextField:存储大段文本
10.EmailField:存储邮箱格式数据
11.FileField:传文件对象,自动保存到提前配置好的路径下并存储该路径信息
12.ForeignKeyField(to='',on_delete)  实际外键字段,建立一对一关系
13.OneToOneField(to='',on_delete)    实际外键字段,建立一对多关系
14.ManyToManyField(to='')             虚拟外键字段,建立多对多关系

需要说明的是,这些orm字段并非和sql字段一一对应,有些是封装了一些逻辑功能在字段的创建、存储过程中的。

ORM还支持用户自定义字段类型

class MyCharField(models.Field):
     def __init__(self,max_length,*args,**kwargs):
         self.max_length=max_length
         super().__init__(max_length=max_length,*args,**kwargs)
     def db_type(self,connection):
        return 'char(%s)' % self.max_length
        
class User(models.Model):
    name=models.CharField(max_length=32)
    info=MyCharField(max_length=64)

ORM常用字段参数

1.primary_key      主键字段
2.verbose_name     字段注释
3.max_length       字段长度
4.max_digits       小数总共多少位
5.decimal_places   小数点后面的位数
6.auto_now         每次操作数据自动更新事件
7.auto_now_add     首次创建自动更新事件后续不自动更新
在时间相关字段的独有参数,设置为True则会自动执行相关功能。
8.null             允许字段为空
9.default          字段默认值
10.unique           唯一值
11.db_index         给字段添加索引
12.choices          当某个字段的可能性能够被列举完全的情况下使用
13.to/to_field/on_delete 
eg:性别、学历、工作状态...
    class User(models.Model):
        name=models.CharField(max_length=32)
        info= MyCharField(max_length=64)
        # 提前列举好对应关系
        gender_choice=(
        	(1,'男性'),
            (2,'女性'),
            (3,'其他'),
        )
        gender = models.IntergerField(choices=gender_choice,null=True)
        user_obj = User.objects.filter(pk=1).first()  # 拿到一个对象
        user_obj.gender  # 直接点显示存储的真实数据
        user_obj.get_gender_display()  # 通过这个方法拿显示转义后的选项

外键相关参数

to				关联表
to_field		关联字段(不写默认关联数据主键)
on_delete		当删除关联表中的数据时,当前表与其关联的行的行为。(只针对一对多和一对一关系)

on_delete()里面相关参数
1、models.CASCADE
    级联操作,当主表中被连接的一条数据删除时,从表中所有与之关联的数据同时被删除
2、models.SET_NULL
    当主表中的一行数据删除时,从表中所有与之关联的数据的相关字段设置为null,此时注意定义外键时,这个字段必须可以允许为空
3、models.PROTECT
    当主表中的一行数据删除时,由于从表中相关字段是受保护的外键,所以都不允许删除
4、models.SET_DEFAULT
    当主表中的一行数据删除时,从表中所有相关的数据的关联字段设置为默认值,此时注意定义外键时,这个外键字段应该有一个默认值
5、models.SET()
    当主表中的一条数据删除时,从表中所有的关联数据字段设置为SET()中设置的值,与models.SET_DEFAULT相似,只不过此时从表中的相关字段不需要设置default参数
6、models.DO_NOTHING
    什么都不做,一切都看数据库级别的约束,注数据库级别的默认约束为RESTRICT,这个约束与django中的models.PROTECT相似
ps:主表一般是指在一对一、一对多中一的那一方,当对其进行删除时,另外一张表的所有元素都应该受到影响

练习题建议总结:

  1. 如果要更改模型表中的书籍on_delete()里面的参数
    比如把on_delete=models.CASCADE更改为on_delete=models.SET_NULL,除了更改参数外,要提前给null=True,因为你在添加book书籍时候,没用绑定外键关联的表时,自动设置为null
    2.在建立模型表时,on_delete属性设置成CASCADE,那你删除出版社就直接把所有的书籍删除
    3.一对一、一对多的关系都是实际字段,如果想单方面解除关系,直接对实际的外键字段增删改就行了
    4.所有上述的关于多对多关系的操作(add,set,remove,clear)都是基于第三张表是ORM自动创建的
posted @ 2022-12-14 22:01  魔女宅急便  阅读(51)  评论(0)    收藏  举报
Title