django 模型层

django 模型层

一、ORM操作(必知必会16条)

1. create()  # 添加数据,返回值得到queryset对象
2. all()  # 查询全部数据,返回值得到queryset对象
3. filter()  # 条件查询数据,返回值得到queryset对象
4. update()  # 更新数据,返回值得到的是受影响的行数
5. delete()  # 删除数据,返回值得到的是受影响的表和行数
6. first()  # 取第一个元素,返回值得到数据对象
7. last()  # 取最后一个元素,返回值得到数据对象
8. get()  # 取值,返回值直接获取对象本身,不推荐使用,当查询条件不存在直接报错
9. value()  # 取值,返回值得到queryset对象  [{}, {}, {}, {}, ...] 列表套字典
10. value_list()  # 取值,返回值得到queryset对象  [(), (), (), (), ...] 列表套元组
11. order_by()  # 排序,返回值得到queryset对象
12. count()  # 计数,返回值是数据的条数
13. exclude()  # 排除什么什么之外,返回值得到queryset对象
14. exists()  # 判断是否存在,返回的是布尔值
15. reverse()  # 反转但不是倒序,返回值得到queryset对象
16. distinct()  # 去重,去重的前提是 数据完全一模一样的(包括主键id),返回的是布尔值

  app01_movie数据表中的数据(ORM操作演示以app01_movie表数据为例):

class Movie(models.Model):
    name = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    publish_time = models.DateField()
    '''
    auto_now:每次修改数据的时候,都会自动将最新的更新记录下来
    auto_now_add:只在创建数据的时候将创建时间自动记录下来,之后不会自动改变
    '''
    # publish_time = models.DateTimeField()

    def __str__(self):
        return self.name
movie表创建

 

1、通过ORM操作查看原生sql语句

  ① 只要返回的结果是queryset对象就可以直接 .query 查看当前queryset对象内部的sql语句

# 例:查看movie表中的所有数据
from
app01 import models data = models.Movie.objects.all() print(data.query)

  打印结果:

SELECT `app01_movie`.`id`, `app01_movie`.`name`, `app01_movie`.`price`, `app01_movie`.`publish_time` FROM `app01_movie`

 

  ② 查看所有orm操作内部的sql语句,django终端会自动打印SQL语句(固定的日志文件配置,拷贝到settings.py配置文件中即可)

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level': 'DEBUG',
        },
    }
}
sql语句日志配置代码
# 例:查看movie表中的所有数据
from app01 import models


data = models.Movie.objects.all()
print(data)

 

2、create()

  添加数据,返回值得到queryset对象

models.Movie.objects.create(name='黑猫警长', price=1451.14, publish_time='2020-1-1')  # 添加日期数据的第一种方式
from datetime import datetime
ntime = datetime.today()
models.Movie.objects.create(name='西游记', price=1000.11, publish_time=ntime)  # 添加日期的第二种方式,直接传日期对象

 

3、all()

  查询全部数据,返回值得到queryset对象

data = models.Movie.objects.all()
print(data)

 

4、filter()

  where条件查询数据,返回值得到queryset对象,括号内可以放多个条件,关系是and

# data = models.Movie.objects.filter(name='西游记')[0]
# data = models.Movie.objects.filter(id=1)[0]
data = models.Movie.objects.filter(pk=1)[0]  # pk指的就是当前字段的主键名
print(data)
print(data.name)

 

5、update()

  更新数据,返回值得到的是受影响的行数

data = models.Movie.objects.filter(pk=2).update(name='师徒四人', price='1547.21')
print(data)

 

6、delete()

  删除数据,返回值得到的是受影响的表和行数

data = models.Movie.objects.filter(pk=1).delete()
print(data)

 

7、first()

  取第一个元素,返回值得到数据对象

data = models.Movie.objects.filter().first()
print(data)

 

8、last()

  取最后一个元素,返回值得到数据对象

data = models.Movie.objects.filter().last()
print(data)

 

9、get()

  取值,返回值直接获取对象本身,不推荐使用,当查询条件不存在直接报错

data = models.Movie.objects.get(pk=2)  # pk=2存在
print(data)

data = models.Movie.objects.get(pk=1)  # pk=1不存在
print(data)

 

10、values()

  取值,返回值得到queryset对象  [{}, {}, {}, {}, ...] 列表套字典

# 取值以字典的形式获取指定字段的数据
data = models.Movie.objects.values('name', 'publish_time')
print(data)

 

11、values_list()

  取值,返回值得到queryset对象  [(), (), (), (), ...] 列表套元组

# 取值以元组的形式获取指定字段的数据
data = models.Movie.objects.values_list('name', 'publish_time')
print(data)

 

12、order_by()

  排序,返回值得到queryset对象

data = models.Movie.objects.order_by('price')  # 默认是升序
data2 = models.Movie.objects.order_by('-price')  # 加个-就是倒序
print(data)
print(data2)

 

13、count()

  计数,返回值是数据的条数

data = models.Movie.objects.count()
print(data)

 

14、exclude()

  排除什么什么之外,返回值得到queryset对象

data = models.Movie.objects.exclude(pk=2)
print(data)

 

15、exists()

  判断是否存在,返回的是布尔值

data = models.Movie.objects.filter(pk=2).exists()  # pk=2存在
print(data)

data = models.Movie.objects.filter(pk=1).exists()  # pk=1不存在
print(data)

 

16、reverse()

  反转但不是倒序,返回值得到queryset对象

data = models.Movie.objects.order_by('price')  # 默认是升序
data2 = models.Movie.objects.order_by('price').reverse()  # 反转,但不是倒序
print(data)
print(data2)

 

17、distinct()

  去重,去重的前提是 数据完全一模一样的(包括主键id),返回的是布尔值

data = models.Movie.objects.all().distinct()  # 无法去重,因为主键id不一样
print(data)
models.Movie.objects.create(name='黑猫警长', price='1000.11', publish_time='2020-01-01')  # 添加一条name、price字段都一样的数据(id不一样)
print(models.Movie.objects.filter(name='黑猫警长'))  # 查看黑猫警长的数据
data2 = models.Movie.objects.values('name', 'price').distinct()  # 可以选择性去重,结果中不包括主键id
print(data2)

 

 

二、双下划线查询(单表查询)

1、大于、小于、大于等于、小于等于

# 查询电影价格大于500的电影
data = models.Movie.objects.filter(price__gt=500)
print(data)

# 查询电影价格小于500的电影
data = models.Movie.objects.filter(price__lt=500)
print(data)

# 查询电影价格大于等于500的电影
data = models.Movie.objects.filter(price__gte=500)
print(data)

# 查询电影价格小于等于500的电影
data = models.Movie.objects.filter(price__gte=500)
print(data)

 

2、and关系(什么和什么)

# 查询电影价格为100、200和300的电影
data = models.Movie.objects.filter(price__in=[100, 200, 300])
print(data)

 

3、在什么什么区间内

# 查询电影价格在200~500之间的电影
data = models.Movie.objects.filter(price__range=(200, 500))
print(data)

 

4、数据中含有什么字符

# 查询电影名中有字母x的电影
# data = models.Movie.objects.filter(name__contains='x')  # contains默认是区分大小写的
data = models.Movie.objects.filter(name__icontains='X')  # icontains不区分大小写
print(data)

 

5、查询时间字段中的年、月、日对应的数据

# 查询2020年出版的电影(不管月日)
data = models.Movie.objects.filter(publish_time__year='2020')
print(data)

# 查询12月出版的电影(不管年日)
data = models.Movie.objects.filter(publish_time__month='12')
print(data)

# 查询8号出版的电影(不管年月)
data = models.Movie.objects.filter(publish_time__day='8')
print(data)

 

 

三、多表查询

1、图书管理系统表创建并手动录入数据

class Book(models.Model):
    name = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    publish_time = models.DateField(auto_now_add=True)  # 该字段新增数据自动添加,无需考虑

    # 出版社 一对多 外键字段在多的一方
    publish = models.ForeignKey(to='Publish')
    # 作者 多对多 外键字段建在任意一端都可以 最好建立在查询频率高的一方
    author = models.ManyToManyField(to='Author')

    # 库存
    kucun = models.BigIntegerField(default=20000)

    # 售出数
    sale = models.BigIntegerField(default=20000)

    def __str__(self):
        return self.name


class Publish(models.Model):
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=64)

    def __str__(self):
        return self.name


class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()

    # 作者详情 一对一 外键字段在任意一方均可 最好建立在查询频率高的一方
    author_detail = models.OneToOneField(to='AuthorDetail')

    def __str__(self):
        return self.name


class AuthorDetail(models.Model):
    phone = models.BigIntegerField()
    addr = models.CharField(max_length=64)
图书管理系统表关系创建

  图书管理系统表关系模型:

 

2、外键字段的增删改查

  PS:外键字段在1.x版本中默认就是级联更新,级联删除;在2.x版本中需要手动指定,给ForeignKey增加属性:on_delete=models.SET_NULL 即可

1)一对多

  增 create()

# 第一种:直接写实际的表字段 publish_id=数字
models.Book.objects.create(name='西游记', price='200.99', publish_id=2)
# 第二种:通过虚拟字段添加
publish_obj = models.Publish.objects.get(pk=1)
models.Book.objects.create(name='红楼梦', price='180.50', publish=publish_obj)

  改 update()

# 第一种:直接改实际的表字段 publish_id=数字
models.Book.objects.filter(pk=1).update(publish_id=3)
# 第二种:通过虚拟字段修改
publish_obj = models.Publish.objects.get(pk=3)
models.Book.objects.filter(pk=1).update(publish=publish_obj)

 

2)多对多

  增   add(),专门给第三张关系表添加数据,括号内既可以传数字,也可以传对象,并且都支持传多个

# 第一种:传数字
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.author.add(1)
# 第二种:传对象
book_obj = models.Book.objects.filter(pk=1).first()
author_obj1 = models.Author.objects.get(pk=2)
author_obj2 = models.Author.objects.get(pk=3)
book_obj.author.add(author_obj1, author_obj2)

  移除   remove()

# 第一种:传数字
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.author.remove(1, 2)
# 第二种:传对象
book_obj = models.Book.objects.filter(pk=1).first()
author_obj = models.Author.objects.get(pk=3)

  改   set(),括号内支持传数字和对象,但是需要是可迭代对象

# 第一种:传数字
book_obj = models.Book.objects.filter(pk=2).first()
book_obj.author.set([1, 2])
# 第二种:传对象
book_obj = models.Book.objects.filter(pk=2).first()
author_obj = models.Author.objects.get(pk=3)
book_obj.author.set((author_obj, ))

  清空   clear(),清空第三章关系表中的对象数据,不需要任何参数

book_obj = models.Book.objects.filter(pk=2).first()
book_obj.author.clear()

 

 

四、多表查询之跨表查询

跨表查询的方式:

  1. 子查询

    将一张表的查询结果当做另外一张表的查询条件。正常解决问题的思路是分步操作

  2. 联表查询

    inner join

    left join

    right join

    union

 

1、正反向查询的概念

  定义:跨表查询的时候,外键字段是否在当前数据对象中,如果在,查询另外一张关系表,叫正向查询;如果不在,查询另外一张表,叫反向查询

  口诀:正向查询按外键字段,反向查询按表名小写_set

 

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

例题:

  ① 查询书籍pk为1的出版社名称、地址

book_obj = models.Book.objects.filter(pk=1).first()
print(book_obj.publish.name)
print(book_obj.publish.addr)

  ② 查询书籍pk为2的所有作者姓名

book_obj = models.Book.objects.filter(pk=2).first()
print(book_obj.author.all())  # 拿到所有书籍主键值为2的作者对象
for author_obj in book_obj.author.all():
    print(author_obj.name)

  ③ 查询作者pk为1的电话号码

author_obj = models.Author.objects.filter(pk=2).first()
print(author_obj.author_detail.phone)

PS:①~③为正向查询,当外键字段对应的数据可以有多个的时候需要加.all(),否则点外键字段即可获取到对应的数据对象

 

  ④ 查询出版社名称为东方出版社的书籍

publish_obj = models.Publish.objects.filter(name='东方出版社').first()
print(publish_obj.book_set.all())  # 得到东方出版社出版过的所有书的对象,可使用for循环将对应的书名取出

  ⑤ 查询作者为tom的书

author_obj = models.Author.objects.filter(name='tom').first()
print(author_obj.book_set.all())  # 得到东方出版社出版过的所有书的对象,可使用for循环将对应的书名取出

  ⑥ 查询手机号为110的作者姓名

author_detail_obj = models.AuthorDetail.objects.filter(phone='110').first().author.name
print(author_detail_obj)

PS:④~⑥为反向查询,看表名小写是否需要加_set.all(),一对多和多对多的时候需要加,一对一不需要

 

3、基于双下划线跨表查询(联表查询)

例题:

  ① 查询书籍pk为1的出版社名称

# 正向
book = models.Book.objects.filter(pk=1).values('publish__name')  # 写外键字段,就以为着你已经在外键字段管理的那种表中了
print(book)
# 反向
book = models.Publish.objects.filter(book__pk=1).first().name  # 拿出版过pk为1的书籍对应的出版社
print(book)

  ② 查询书籍pk为1的作者姓名和年龄

# 正向
data = models.Book.objects.filter(pk=1).values('author__name', 'author__age')
print(data)
# 反向
data = models.Author.objects.filter(book__pk=1).values('name', 'age')
print(data)

  ③ 查询作者为jack的年龄和手机号

# 正向
data = models.Author.objects.filter(name='jack').values('age', 'author_detail__phone')
print(data)
# 反向
data = models.AuthorDetail.objects.filter(author__name='jack').values('author__age', 'phone')
print(data)

  ④ 查询书籍pk为1的作者的手机号

# 正向
data = models.Book.objects.filter(pk=1).values('author__author_detail__phone')
print(data)
# 反向
data = models.AuthorDetail.objects.filter(author__book__pk=1).values('phone')
print(data)

PS:只要表中有关系,就可以通过正向的外键字段或者反向的表名小写,进行连续的跨表操作

 

 

五、聚合函数

  求最大值、最小值、平均值、计数、和,返回值得到字典

例题:

# 求最大值、最小值、平均值、计数、总和
from django.db.models import Max, Min, Avg, Count, Sum


data = models.Movie.objects.aggregate(Max('price'), Min('price'), Avg('price'), Count('pk'), Sum('price'))
print(data)

 

 

六、分组查询

关键字 annotate

例题:

  ① 统计每一本书的作者个数

data = models.Book.objects.annotate(author_num=Count('author__pk')).values('name', 'author_num')
print(data)

  ② 统计出每个出版社卖的最便宜的书的价格

data = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'min_price')
print(data)

  ③ 统计不止一个作者的图书

data = models.Book.objects.annotate(author_num=Count('author')).filter(author_num__gt=1).values('name')
print(data)

  ④ 查询各个作者出版的书的总价格

data = models.Author.objects.annotate(price_sum=Sum('book__price')).values('name', 'price_sum')
print(data)

  上面的例子都是按照表来分组,annotate在表后面就是按表来分组,annotate在value后面就是按照字段分组,那么如何按照表中的某一个指定的字段分组呢?

data = models.Book.objects.values('price').annotate()  # 以价格分组
print(data)

 

 

七、F与Q查询

1、F查询

  能够获取到数据库中某个字段对应的值,并将其拿出一个一个进行操作,操作之前需要导入一个模块

from django.db.models import F

例题:

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

res = models.Book.objects.filter(kucun__gt=F('sale')).values('name')
print(res)

  ② 将所有书的价格提高100块钱

 

2、Q查询

  能够改变查询的条件关系  and  or  not,操作之前需要导入一个模块

from django.db.models import Q

例题:

  查询书的名字是西游记或者价格是500的书籍

"""
data = models.Book.objects.filter(Q(price=500), Q(name='西游记')).values('name')  # 逗号是and关系
data = models.Book.objects.filter(~Q(price=500) | Q(name='西游记')).values('name')  # ~是not关系,取反
"""
data = models.Book.objects.filter(Q(price=500) | Q(name='西游记')).values('name')  # |是or关系
print(data)

 

3、Q查询的高阶用法

  应用场景:网页的搜索功能,根据用户选择的内容和输入内容来搜索,比如用户输入  name  西游记  等

from django.db.models import Q

q = Q()
q.connector = 'or'  # q对象默认也是and关系,可以通过connector改变or
q.children.append(('name', '西游记'))
q.children.append(('price', 500))
data = models.Book.objects.filter(q).values('name')
print(data)
from django.db.models import Q

column_name = input('输入查询的字段名:').strip()
column_value = input('输入查询的字段值:').strip()
q = Q()
q.connector = 'or'  # q对象默认也是and关系,可以通过connector改变为or
q.children.append((column_name, column_value))
data = models.Book.objects.filter(q).values('name')
print(data)
高阶用法例子

 

 

八、models中常用字段

1. AutoField(primary_key=True)  # 主键字段
2. CharField(max_length=32)     # varchar(32)
3. IntegerField()               # int
4. BigIntegerField()            # bigint
5. DecimalField()               # decimal
6. EmailField()                 # varchart(254)
7. DateField()                  # date
8. DateTimeField()              # datetime
      auto_now:每次编辑数据时候都会自动更新该字段时间
      auto_now_add:创建时候自动更新,更新字段不会改变
9. BooleanField()               # 给该字段传布尔值,会对应成数字0/1
      应用场景:is_delete    is_vip    is_status
10. TextField()                 # 文本类型,存储大段文本
11. FileField()                 # 自渡船,路径保存在数据库,文件上传到指定目录,只存文件路径
      upload_to = '指定文件路径' 给该字段传文件对象,文件会自动保存到upload_to指定的文件夹下,然后改字段存文件的路径

 

1、自定义char类型字段

from django.db.models import Field


class DefCharField(Field):  # 需要继承Field类
    def __init__(self, max_length, *args, **kwargs):
        self.max_length = max_length  # 拦截一个父类的方法  操作完之后  利用super调用父类的方法
        super().__init__(max_length=max_length, *args, **kwargs)

    def db_type(self, connection):
        return 'char(%s)' % self.max_length

 

2、字段内的关键性参数

  null

  default

  on_delete = models.CASCADE

 

3、choices参数

  如果数据能够被列举出来供用户选择,那么可以考虑使用choices参数

class UserInfo(models.Model):
    name = models.CharField(max_length=64)
    gender_choice = (
        (1, ''),
        (2, ''),
        (3, '其他'),
    )
    gender = models.IntegerField(choices=gender_choice)   # 该字段还是存数字, 并且可以匹配关系之外的数字
    record_choices = (('checked', "已签到"),
                      ('vacate', "请假"),
                      ('late', "迟到"),
                      ('noshow', "缺勤"),
                      ('leave_early', "早退"),
                      )
    record = models.CharField("上课纪录", choices=record_choices, default="checked", max_length=64)
models.py中的userinfo表

  ① 查询pk为1的人的性别

user_obj = models.UserInfo.objects.get(pk=2)
print(user_obj.gender)
# 针对choices参数字段,取值的时候,get_字段名_display()
print(user_obj.get_gender_display())

  ② 查询pk为5的人的性别

user_obj = models.UserInfo.objects.get(pk=5)
print(user_obj.name, user_obj.gender, user_obj.get_gender_display())

 

 

九、数据库操作优化

  only与defer,defer与only互为反对象

 

1、only()

  作用:括号内传字段名,得到的结果是一个列表套数据对象,该对象内只含有指定的字段属性,对象.该属性是不会走数据库的,但是一旦对象.非括号内的字段,也能拿到数据,但是是重新走的数据库查询

data = models.Book.objects.only('name')  # data对象内部只有name属性
print(data)
for i in data:
    print(i.name)  # 不走数据库
    print(i.price)  # 走数据库

 

2、defer()

  作用:括号内传字段名,得到的结果是一个列表套数据对象,该对象内不含有该字段属性,对象一旦.非括号内的字段是不会走数据库的,但是对象.该属性,也能拿到数据,但是是重新走的数据库查询

data = models.Book.objects.defer('name')  # data对象内部含有除了name属性之外的所有属性
print(data)
for i in data:
    print(i.price)  # 不走数据库
    print(i.name)  # 走数据库

 

3、select_related()

  作用:select_related() 括号内只能传外键字段,不能是多对多字段,只能是一对多、一对一。内部是连表操作,先将关系表全部连接起来,之后在一次性查询出来,封装到对象中,数据对象之后再获取任意表中的数据的时候都不需要再走数据库了,因为全部封装成了对象的属性。select_related('外键字段1__外键字段2__外键字段3...') 将所有含有外键字段的表都连起来

data = models.Book.objects.select_related('publish')
print(data)
for i in data:
    print(i.publish.name)
    print(i.publish.addr)

 

4、prefetch_related()

  作用:prefetch_related() 内部是子查询,但是给我们的感觉是连表操作,内部通过子查询将外键关联表中的数据也全部封装到对象中,之后对象.当前表或者外键关联表中的字段都不需要走数据库

data = models.Book.objects.prefetch_related('publish')
print(data)
for i in data:
    print(i.publish.name)

 

5、select_related和prefetch_related优缺点比较

  select_related是连表操作,好处在于只走一次sql查询

    耗时耗在 “连接表的操作”

  prefetch_related是子查询,走两次sql查询

    耗时耗在 “查询次数”

 

 

十、django orm开启事务操作

1、事务的四大特性(ACID)

  ① 原子性

  ② 一致性

  ③ 隔离性

  ④ 持久性

 

2、orm如何开启事务

from django.db import transaction
with transaction.atomic():
    # 在with代码块中执行的orm语句中同属于一个事务
    pass
    # 代码块运行结束,事务就结束了

 

3、数据库的三大范式

  自查...

posted @ 2020-12-11 21:58  chchcharlie、  阅读(135)  评论(0编辑  收藏  举报