第六章:模型层

测试脚本

"""
当你只是想测试Django中的某一个py文件内容,那么你可以不用书写前后端交互的形式
而是直接写一个测试脚本即可

脚本文件是直接写在应用下的tests.py还是自己单独开设py文件都可以
"""
# 测试环境准备
1、打开manage.py文件,拷贝上面四行代码
2、删掉import sys
3、在代码块里面写
	import django
	django.setup()


# 实现效果如下
import os

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project_django04.settings")
    import django
    django.setup()
    # 在这个代码块的下面就可以测试Django里面单个py文件了
    # 所有的代码都要写在这个下面,包括import导入模块

在终端打印SQL语句

如果你想知道你对数据库进行操作时,Django内部到底是怎么执行它的sql语句时可以加下面的配置来查看

在Django项目的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',
        },
    }
}

配置好之后,再执行任何对数据库进行操作的语句时,会自动将Django执行的sql语句打印到pycharm终端上

ORM

ORM常用字段类型

所有字段类型都可加的属性:verbose_name=对字段的描述信息

主键

# ORM在你没有定义主键字段的时候,他会帮你自动创建名为id的主键字段
models.AutoField(primary_key=True)

字符

models.CharField(max_length=8)
"""max_length属性表示可储存长度,且必须要写"""

数字

models.IntegerField()  # 整型
models.FloatField()  # 浮点
models.DecimalField(max_digits=8,decimal_places=3)  # 精确浮点
"""max_digits:总长度,decimal_places:小数所占位数"""

时间

models.DateField()  # 年月日
models.DateTimeField()  # 年月日时分秒
"""
时间方法的两个重要参数
    auto_now:每次操作数据的时候,该字段会自动将当前时间更新
    auto_now_add:在创建数据的时候会自动将当前创建时间记录下来,之后只要不人为的修改,那么就一直不变
"""

文件

modles.FileField(upload_to='/data')
	upload_to = "文件路径"      上传文件的保存路径
"""
给该字段传一个文件对象,会自动将文件保存到参数upload_to后面的文件路径中,然后将文件路径保存到数据库中
"""

邮件

models.EmailFiled()
"""
对应的SQL其实就是varchar(254)
会自动校验传入的数据是否是一个邮件类型的
"""

布尔值

models.BooleanField()
"""
给该类型传布尔值:True或False,数据库中会存成1或0
"""

文本

models.TextField()
"""
该字段可以用来存大段内容(如:文章、博客等等),没有字数限制
"""

图片

ImageField(FileField)
        - 字符串,路径保存在数据库,文件上传到指定目录
        - 参数:
            upload_to = ""      上传文件的保存路径
            storage = None      存储组件,默认django.core.files.storage.FileSystemStorage
            width_field=None,   上传图片的高度保存的数据库字段名(字符串)
            height_field=None   上传图片的宽度保存的数据库字段名(字符串)

多表操作

一对多:models.ForeignKey(其他表)
多对多:models.ManyToManyField(其他表)
一对一:models.OneToOneField(其他表)

字段参数

verbose_name
对该字段的描述信息

null
用于表示某个字段可以为空。

unique
如果设置为unique=True 则该字段在此表中必须是唯一的 。

db_index
如果db_index=True 则代表着为此字段设置索引。

default
为该字段设置默认值。

on_delete
级联更新和级联删除,Django2.x及以上需要自己指定外键字段的级联更新和级联删除

to
外键字段参数,关联另一张表

to_field
外键字段参数,关联另一张表的字段,不写默认关联主键字段

字段集合

	AutoField(Field)
        - int自增列,必须填入参数 primary_key=True

    BigAutoField(AutoField)
        - bigint自增列,必须填入参数 primary_key=True

        注:当model中如果没有自增列,则自动会创建一个列名为id的列
        from django.db import models

        class UserInfo(models.Model):
            # 自动创建一个列名为id的且为自增的整数列
            username = models.CharField(max_length=32)

        class Group(models.Model):
            # 自定义自增列
            nid = models.AutoField(primary_key=True)
            name = models.CharField(max_length=32)

    SmallIntegerField(IntegerField):
        - 小整数 -3276832767

    PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
        - 正小整数 032767
    IntegerField(Field)
        - 整数列(有符号的) -21474836482147483647

    PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
        - 正整数 02147483647

    BigIntegerField(IntegerField):
        - 长整型(有符号的) -92233720368547758089223372036854775807

    BooleanField(Field)
        - 布尔值类型

    NullBooleanField(Field):
        - 可以为空的布尔值

    CharField(Field)
        - 字符类型
        - 必须提供max_length参数, max_length表示字符长度

    TextField(Field)
        - 文本类型

    EmailField(CharField):
        - 字符串类型,Django Admin以及ModelForm中提供验证机制

    IPAddressField(Field)
        - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制

    GenericIPAddressField(Field)
        - 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
        - 参数:
            protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
            unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启此功能,需要protocol="both"

    URLField(CharField)
        - 字符串类型,Django Admin以及ModelForm中提供验证 URL

    SlugField(CharField)
        - 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)

    CommaSeparatedIntegerField(CharField)
        - 字符串类型,格式必须为逗号分割的数字

    UUIDField(Field)
        - 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证

    FilePathField(Field)
        - 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
        - 参数:
                path,                      文件夹路径
                match=None,                正则匹配
                recursive=False,           递归下面的文件夹
                allow_files=True,          允许文件
                allow_folders=False,       允许文件夹

    FileField(Field)
        - 字符串,路径保存在数据库,文件上传到指定目录
        - 参数:
            upload_to = ""      上传文件的保存路径
            storage = None      存储组件,默认django.core.files.storage.FileSystemStorage

    ImageField(FileField)
        - 字符串,路径保存在数据库,文件上传到指定目录
        - 参数:
            upload_to = ""      上传文件的保存路径
            storage = None      存储组件,默认django.core.files.storage.FileSystemStorage
            width_field=None,   上传图片的高度保存的数据库字段名(字符串)
            height_field=None   上传图片的宽度保存的数据库字段名(字符串)

    DateTimeField(DateField)
        - 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]

    DateField(DateTimeCheckMixin, Field)
        - 日期格式      YYYY-MM-DD

    TimeField(DateTimeCheckMixin, Field)
        - 时间格式      HH:MM[:ss[.uuuuuu]]

    DurationField(Field)
        - 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型

    FloatField(Field)
        - 浮点型

    DecimalField(Field)
        - 10进制小数
        - 参数:
            max_digits,小数总长度
            decimal_places,小数位长度

    BinaryField(Field)
        - 二进制类型

ORM对应MySQL

    'AutoField': 'integer AUTO_INCREMENT',
    'BigAutoField': 'bigint AUTO_INCREMENT',
    'BinaryField': 'longblob',
    'BooleanField': 'bool',
    'CharField': 'varchar(%(max_length)s)',
    'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
    'DateField': 'date',
    'DateTimeField': 'datetime',
    'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
    'DurationField': 'bigint',
    'FileField': 'varchar(%(max_length)s)',
    'FilePathField': 'varchar(%(max_length)s)',
    'FloatField': 'double precision',
    'IntegerField': 'integer',
    'BigIntegerField': 'bigint',
    'IPAddressField': 'char(15)',
    'GenericIPAddressField': 'char(39)',
    'NullBooleanField': 'bool',
    'OneToOneField': 'integer',
    'PositiveIntegerField': 'integer UNSIGNED',
    'PositiveSmallIntegerField': 'smallint UNSIGNED',
    'SlugField': 'varchar(%(max_length)s)',
    'SmallIntegerField': 'smallint',
    'TextField': 'longtext',
    'TimeField': 'time',
    'UUIDField': 'char(32)',

数据的基本操作

# 第一种方法:create
models.MyDb.objects.create(name='liu', age=28, register_date='2002-7-7')

# 第二种方法:obj.save
user_obj = models.MyDb(name='ppp', age=22, register_date='2012-12-27')
user_obj.save()

# 第一种方法:delete
models.MyDb.objects.filter(pk=1).delete()
"""pk指向表的主键字段,不管你的主键值叫什么"""

# 第二种方法
user_obj = models.MyDb.objects.filter(pk=2).first()
user_obj.delete()

# update
models.MyDb.objects.filter(pk=3).update(name='aaa')

# 查单个数据:filter(),括号里面写筛选条件
models.MyDb.bojects.filter(pk=1)

# 查所有数据:all()
# 方法一:不推荐使用,语义不明
models.User.objects.filter()
# 方法二:all  推荐使用,语义明确
models.User.objects.all()

必知必会十三条

返回QuerySet对象的方法有

all():查询所有数据

# 找到所有的数据
models.MyDb.objects.all()

filter(kwargsd): 它包含了与所给筛选条件相匹配的对象

# 找到主键为1的数据,找不到时会返回一个空
models.MyDb.objects.filter(pk=1)

exclude(**keargs):把符合条件的排除在外

# 拿到除了name='json'的所有数据
models.MyDb.objects.exclude(name='json')

order_by(*field):对查询结果排序

# 默认升序
models.MyDb.objects.order_by('age')
# 降序:在查询条件前加'-'
models.MyDb.objects.order_by('-age')

reverse(): 对查询结果反向排序,请注意reverse()通常只能在具有已定义顺序的QuerySet上调用(在model类的Meta中指定ordering或调用order_by()方法)

models.MyDb.objects.order_by('age').reverse()

distinct():去重

"""
去重一定要是一模一样的数据才可以,如果带有主键就一定去不了重,所以在去重时一定不要忽略主键
"""
# 把字段为name和age相同的数据进行去重
models.MyDb.objects.values('name', 'age').distinct()

特殊的QuerySet

values() :指定获取的数据字段,列表套字典的形式 可迭代的字典序列

# 获取字段为name和age的数据,括号内可以写多个也可以写一个
res = models.MyDb.objects.values('name', 'age')
print(res)  # <QuerySet [{'name': 'aaa', 'age': 44}, {'name': 'ddd', 'age': 32}]>

values_list() 指定获取的数据字段,列表套元组的形式 可迭代的元组序列

res = models.MyDb.objects.values_list('name', 'age')
print(res)  # <QuerySet [('aaa', 44), ('ddd', 32)]>

返回具体对象的

get(**kwargs): 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误。

# 找到主键值为1的数据,找不到会报错
models.MyDb.objects.get(pk=1)

first():拿到queryset中第一个元素

models.MyDb.objects.all().first()

last():拿到queryset中最后一个元素

models.MyDb.objects.all().last()

返回布尔值的方法有

exists():判断当前queryset中是否有数据,返回布尔值(基本用不到,直接拿数据判断就行了)

# 判断主键值为5的数据是否存在
models.MyDb.objects.filter(pk=5).exists()

返回数字的方法有

count():返回数据库中匹配查询(QuerySet)的对象数量

# 查询当前总共有多少条数据
models.MyDb.objects.all().count()

单表查询,神奇的双下划线

models.Tb1.objects.filter(id__gt=10# 获取id大于10的
models.Tb1.objects.filter(id__lt=10# 获取id小于10的
models.Tb1.objects.filter(id__gte=10# 获取id大于等于10的
models.Tb1.objects.filter(id__lte=10# 获取id小于等于10的
models.Tb1.objects.filter(id__lt=10, id__gt=1)   # 获取id大于1 且 小于10的值
 
models.Tb1.objects.filter(id__in=[11, 22, 33])   # 获取id等于11、22、33的数据
models.Tb1.objects.exclude(id__in=[11, 22, 33])  # not in
 
models.Tb1.objects.filter(name__contains="ven")  # 获取name字段包含"ven"的,对大小写敏感
models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感
 
models.Tb1.objects.filter(id__range=[1, 3])      # id范围是1到3的,包含首尾
 
类似的还有:startswith,istartswith, endswith, iendswith 

date字段还可以:
models.Class.objects.filter(first_day__year=2017)
date字段可以通过在其后加__year,__month,__day等来获取date的特点部分数据
# date
        #
        # Entry.objects.filter(pub_date__date=datetime.date(2005, 1, 1))
        # Entry.objects.filter(pub_date__date__gt=datetime.date(2005, 1, 1))

        # year
        #
        # Entry.objects.filter(pub_date__year=2005)
        # Entry.objects.filter(pub_date__year__gte=2005)

        # month
        #
        # Entry.objects.filter(pub_date__month=12)
        # Entry.objects.filter(pub_date__month__gte=6)

        # day
        #
        # Entry.objects.filter(pub_date__day=3)
        # Entry.objects.filter(pub_date__day__gte=3)

        # week_day
        #
        # Entry.objects.filter(pub_date__week_day=2)
        # Entry.objects.filter(pub_date__week_day__gte=2)
需要注意的是在表示一年的时间的时候,我们通常用52周来表示,因为天数是不确定的

多表操作

一对多

models.ForeignKey(其他表)

作者与图书是一对多关系,所以字段建在多的一方

# 图书表
class Books(models.Model):
    name = models.CharField(max_length=32)
    price = models.IntegerField()

    # 作者与图书是一对多的关系,图书是多,所以建在图书表中
    author = models.ForeignKey(to='Author')
    
# 作者表
class Author(models.Model):
    name = models.CharField(max_length=16)
    age = models.IntegerField()

在输入数据库迁移命令后,生成图书表时,图书表中的author字段会自动加上_id的后缀

image

一对多的增删改查

# 第一种方式:写实际的字段,写要建立关系的作者id
models.Books.objects.create(name='三国演义', price=111, author_id=4)

# 第二种方式:写虚拟的字段,写要建立关系的作者的对象
author_obj = models.Author.objects.filter(pk=3).first()
models.Books.objects.create(name='西游记', price=222, author=author_obj)
# 级联删除,id为2的作者被删除,那么与id为2的作者建立关系的图书也会被删除
models.Author.objects.filter(pk=2).delete()
# 第一种方式:写实际的字段,写要建立关系的作者id
models.Books.objects.filter(pk=2).update(author_id=4)
# 第二种方式:写虚拟的字段,写要建立关系的作者的对象
author_obj = models.Author.objects.filter(pk=3).first()
models.Books.objects.filter(pk=2).update(author=author_obj)

多对多

models.ManyToManyField(其他表)

作者与出版社是多对多的关系,表建在哪一方都可以

# 出版社表
class Publish(models.Model):
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=32)

    # 出版社表与作者是多对多的关系,可以建在任意一方
    authors = models.ManyToManyField(to='Author')

# 作者表
class Author(models.Model):
    name = models.CharField(max_length=16)
    age = models.IntegerField()

在输入数据库迁移命令后,生成出版社表和作者表时,会额外再生成第三张表,表名为publish_authors

多对多的增删改查及清空

操作有多对多关系的表时,其实就是操作第三张表

publish_obj = models.Publish.objects.filter(pk=1).first()
publish_obj.authors  # 这样就相当于进入到第三张表中了,可以对数据进行操作
增:add()

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

publish_obj = models.Publish.objects.filter(pk=1).first()
# 第一种方法:写实际字段
publish_obj.authors.add(3)  # 把当前出版社对象与id为3的作者对象进行多对多的关系绑定
publish_obj.authors.add(1,3)  # 括号中可以写多个,用逗号隔开即可

# 第二种方法:写虚拟字段
author_obj1 = models.Author.objects.filter(pk=4).first()
author_obj2 = models.Author.objects.filter(pk=5).first()
publish_obj.authors.add(author_obj1)
publish_obj.authors.add(author_obj1, author_obj2)  # 括号中也可以写多个,用逗号隔开即可
删:remove()

remove删除第三张表中的数据,括号内既可以传数字可也以传对象,并且都支持多个

publish_obj = models.Publish.objects.filter(pk=2).first()
# 第一种方法:写实际字段
publish_obj.authors.remove(5)  # 删除id为2的出版社和id为5的作者之间的关系字段
publish_obj.authors.remove(5, 6)  # 括号中可以写多个,用逗号隔开即可

# 第二种方法:写虚拟字段
author_obj = models.Author.objects.filter(pk=3).first()
author_obj1 = models.Author.objects.filter(pk=4).first()
publish_obj.authors.remove(author_obj)
publish_obj.authors.remove(author_obj, author_obj1)  # 括号中也可以写多个,用逗号隔开即可
改:set()

括号内必须传一个可迭代对象,该对象既可以是数字可以是对象,并且都支持多个

对要修改的数据是先进行删除然后再添加,可以观察第三张表的id变化

"""
该方法属于更新操作,对要修改的数据会直接删除,然后再添加进来
例如与id为1的出版社有多对多关系的作者有id为1,2,3,4的,那么表如下
-----------------------------------
|  id  |  publish_id  |  author_id|
|---------------------------------|
|  1   |  1           |  1        |
|---------------------------------|
|  2   |  1           |  2        |
|---------------------------------|
|  3   |  1           |  3        |
|---------------------------------|
|  4   |  1           |  4        |
-----------------------------------
当把与id为1的出版社有多对多关系的作者id改为2,3,6时,那么表如下
-----------------------------------
|  id  |  publish_id  |  author_id|
|---------------------------------|
|  2   |  1           |  2        |
|---------------------------------|
|  3   |  1           |  3        |
|---------------------------------|
|  5   |  1           |  6        |
-----------------------------------
"""
publish_obj = models.Publish.objects.filter(pk=1).first()
# 第一种方法:写实际字段
publish_obj.authors.set([3])  # 把与id为1的出版社建立多对多关系的作者改为id改为3
publish_obj.authors.set([3, 4])  # 把与id为1的出版社建立多对多关系的作者改为id改为3和4

# 第二种方法:写虚拟字段
author_obj = models.Author.objects.filter(pk=3).first()
author_obj1 = models.Author.objects.filter(pk=7).first()
publish_obj.authors.set([author_obj])
publish_obj.authors.set([author_obj, author_obj1])  # 也可以传多个对象
清空:clear()

括号内不要加任何参数

"""某个出版社倒闭了,把所有作者与该出版社的联系统统清空掉"""
publish_obj = models.Publish.objects.filter(pk=1).first()
# 在第三张关系表中清空所有id为1的出版社与作者的绑定关系
publish_obj.authors.clear()


"""
清空前:
-----------------------------------
|  id  |  publish_id  |  author_id|
|---------------------------------|
|  1   |  1           |  1        |
|---------------------------------|
|  2   |  1           |  2        |
|---------------------------------|
|  3   |  2           |  3        |
|---------------------------------|
|  4   |  2           |  4        |
-----------------------------------
清空后:
-----------------------------------
|  id  |  publish_id  |  author_id|
|---------------------------------|
|  3   |  2           |  3        |
|---------------------------------|
|  4   |  2           |  4        |
-----------------------------------
"""

一对一

models.OneToOneField(其他表)

作者表与作者详情表是一对一关系,所以表关系建在哪一方都可以

# 作者表
class Author(models.Model):
    name = models.CharField(max_length=16)
    age = models.IntegerField()

    # 作者表与作者详情表是一对一的关系,建在使用评率高的一方
    author_detailed = models.OneToOneField(to='AuthorDetailed')
    
# 作者详情表
class AuthorDetailed(models.Model):
    phone = models.BigIntegerField()
    email = models.EmailField()

一对一的怎删改查

一对一的增删改查与一对多的增删改查是一致的

正反向的概念

假设我们有一个作者表和一个作者详情表,他们之间建立了一对一的外键关系,并且外键字段建在了作者表中

那么我们从作者表去查作者详情表中的内容就是正向查询,反之则是反向查询

一对多和多对多也是同理

正向查询按字段

反向查询按表名小写(或者表名小写加 "_set.all()" )

多表查询

前期表准备

# 图书表
class Book(models.Model):
    name = models.CharField(max_length=16)
    price = models.IntegerField(null=True)

    # 图书表与作者表一对多的外键字段
    author = models.ForeignKey(to='Author')


# 出版社表
class Publish(models.Model):
    name = models.CharField(max_length=16)
    addr = models.CharField(max_length=32)

    # 作者表与出版社表的多对多的外键字段
    author = models.ManyToManyField(to='Author')


# 作者表
class Author(models.Model):
    name = models.CharField(max_length=16)
    age = models.IntegerField(null=True)

    # 作者表与作者详细表的一对一外键字段
    author_details = models.OneToOneField(to='AuthorDetails')


# 作者详情表
class AuthorDetails(models.Model):
    phone = models.IntegerField(null=True)
    addr = models.CharField(max_length=16)

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

正向查询按字段

反向查询按表名小写(或者表名小写加 "_set.all()" )

正向查询

正向查询按照外键字段查

先找到一个有外键字段的对象

然后通过该对象点外键字段的方法去到想操作的表中

最后就可以通过点表中的值来进行增删改查

对象.外键字段.另一张表的字段
对象.外键字段.另一张表的字段.all()

当你的查询结果有多个的时候就需要加.all(),例如多对多
当你的查询结果只有一个的时候不需要加,他会直接拿到数据对象
"""使用实例"""
# 1、查找id为1的图书是谁写的
book_obj1 = models.Book.objects.filter(pk=1).first()
res = book_obj1.author.name
print(res)

# 2、查找写id为1的图书的作者的地址在那
book_obj1 = models.Book.objects.filter(pk=1).first()
res = book_obj1.author.author_details.addr
print(res)

# 3、查找id为1的出版社与哪几个作者有关系
publish_obj = models.Publish.objects.filter(pk=1).first()
# res = publish_obj.author  # app01.Author.None
res = publish_obj.author.all()
for i in res:
    res = i.name
    print(res)

# 4、查找id为1的作者的电话号码
author_obj = models.Author.objects.filter(pk=1).first()
res = author_obj.author_details.phone
print(res)
"""
正向什么时候加.all()?
	当你的查询结果有多个的时候就需要加.all()
	当你的查询结果只有一个的时候不需要加,他会直接拿到数据对象
"""
反向查询

反向查询按表名小写(或者表名小写加 "_set.all()" )

先找到一个对象

然后点小写的表名(当查询结果有多个的时候要写成 表名_set.all() )

最后就可对点表中的值进行增删改查

对象.小写的表名.另一个表的字段
对象.小写的表名_set.all().另一个表的字段

当你的查询结果有多个的时候就需要加_set.all()
当你的查询结果只有一个的时候不需要加,他会直接拿到数据对象
"""使用实例"""
"""结合多表操作开头的前期表准备来看"""
# 1、查询id为1的作者写了那几本书
author_obj = models.Author.objects.filter(pk=1).first()
res = author_obj.book_set.all()
for i in res:
    res = i.name
    print(res)

# 2、查询id为1的作者与那几个出版社有关系
author_obj = models.Author.objects.filter(pk=1).first()
res = author_obj.publish_set.all()
for i in res:
    res = i.name
    print(res)

# 3、 查询电话号码为130的作者是谁
author_details_obj = models.AuthorDetails.objects.filter(phone=130).first()
res = author_details_obj.author.name
print(res)
"""
反向什么时候加_set.all()?
	当你的查询结果有多个的时候就需要加_set.all()
	当你的查询结果只有一个的时候不需要加,他会直接拿到数据对象
"""

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

利用values、values_list和双下划先查询的方式实现联表查询

"""结合多表操作开头的前期表准备来看"""
# 这是获取图书id为1的书名
models.Book.objects.filter(pk=1).values('name')
# 但是我们想知道写这本书的作者叫什么,就涉及到跨表操作了,可以这样做:
models.Book.objects.filter(pk=1).values('author')  # 这样写就说明我们从图书表跨入到作者表了
# 再结合双下划线的方法
models.Book.objects.filter(pk=1).values('author__name')  # 这样就获取到作者的名字了
# 因为我们一开始拿到的对象是图书对象,所以在values里面写'name'获取的是当前图书的名字
models.Book.objects.filter(pk=1).values('author__name', 'name')
# 最后打印出来的结果是  # <QuerySet [{'author__name': '刘杰', 'name': '三国演义'}]>

# filter()里面也可用双下划线方法来进行反向查询
# 找到图书id为1的作者对象
models.Author.objects.filter(book__id=1)

# 也可以跨多个表
# 因为图书与作者详情表没有直接联系,所以要先跨到作者表,再从作者表跨到作者详情表中查询电话
models.Book.objects.filter(pk=1).values('author__author_details__phone')

这个不好描述,直接看题目

# 找到id为1的图书名字和他的作者名字
# 正向
res = models.Book.objects.filter(pk=1).values('author__name', 'name')
print(res)
# 反向
res1 = models.Author.objects.filter(book__id=1).values('name', 'book__name')
print(res1)


# 找到写出id为1的图书的作者名字和电话
# 正向
res = models.Book.objects.filter(pk=1).values('author_name', 'author__author_details__phone')
print(res)
# 反向
res1 = models.AuthorDetails.objects.filter(author__book__id=1).values('author__name', 'phone')
print(res1)

基于对象的跨表操作和基于双下划线的跨表操作是两种不同的跨表操作,可以根据不同的实际情况来使用

聚合查询

聚合查询就是聚合函数的使用:max、min、sum、count、avg

"""结合多表操作开头的前期表准备来看"""

"""
聚合查询一般都是配合分组一起使用的
当你想不进行分组也要用聚合函数时就要用到 aggregate
"""
# Django使用聚合函数需要先导入
from django.db.models import Max, Min, Sum, Avg, Count
# 查出图书表中价格最高的
models.Book.objects.aggregate(Max('price'))
# 也可以一次性查多个
res = models.Book.objects.aggregate(Max('price'), Min('price'), Sum('price'), Avg('price'), Count('pk'))
price_max = res['price__max']
a = 0
l = ['书籍最高价格:', '书籍最低价格:', '书籍价格总和:', '书籍平均价格:', '书籍总数:']
for i in res:
    print(f'{l[a]}{res[i]}')
    a += 1

分组查询

分组查询的关键字是:annotate

# models后面点什么,annotate就是按照什么分组,例如下面这个就是按照作者来分组
models.Author.objects.annotate()

"""查询作者写过的书出售的价格总和"""
1、先把每个作者分成一组:models.Author.objects.annotate()
2、算书的出售价总和:Sum('book__price')
# book_sum是起的一个别名,用来存储书的价格总和
# 作者表查图书表是反向查询,所以用图书表的小写表名,算的是价格总和,所以拿图书的价格字段,合起来就是book__price
# 分组查询配合聚合函数来使用,这里用到的是Sum求和函数
models.Author.objects.annotate(book_sum=Sum('book__price'))

# values:把求出来的值拿出来,name:拿的是作者的名字,book_sum拿的是每个作者的书的价格总和
models.Author.objects.annotate(book_sum=Sum('book__price')).values('name', 'book_sum')

# 分组完成后还可以对结果进行筛选
# 查询每个作者的书出售的价格总和大于2000的
"""只要你的ORM语句得出来的结果是queryset对象,就可以一直无限的点queryset对象方法"""
models.Author.objects.annotate(book_sum=Sum('book__price')).filter(book_sum__gt=2000).values('name')

按照指定的字段进行分组

# 按作者的年龄进行分组
models.Author.objects.values('age').annotate()

如果分组的使用总是报错,看看是不是因为mysql的严格模式

# 分组查询   annotate
from django.db.models import Max, Min, Sum, Avg, Count
# 1、统计每一个作者写了几本书
"""当你要查询的字段是主键时可以省略不写,例如这个book__id或者book__pk就可省略成book"""
res = models.Author.objects.annotate(book_sum=Count('book')).values('name', 'book_sum')
print(res)

# 2、统计每个作者卖的最便宜的书的价格
res = models.Author.objects.annotate(min_book=Min('book__price')).values('name', 'min_book')
print(res)

# 3、统计出版的书大于2的作者
res = models.Author.objects.annotate(book_sum=Count('book')).filter(book_sum__gt=2).values('name', 'book_sum')
print(res)

# 4、查询每个作者出的书的总价格
res = models.Author.objects.annotate(book_price=Sum('book__price')).values('name', 'book_price')
print(res)

# 5、查询作者出版的书的平均价格大于5000的
res = models.Author.objects.annotate(book_avg=Avg('book__price')).filter(book_avg__gt=5000).values('name')
print(res)

F查询

F与Q查询表准备

# 图书表
class Book(models.Model):
    # 书名
    name = models.CharField(max_length=16)
    # 价格
    price = models.IntegerField
    # 库存
    kucun = models.IntegerField
    # 卖出的数量
    maichu = models.IntegerField

F查询可以获取某个字段对应的数据

使用F查询要要先导入模块

from django.db.models import F

# 使用F查询要要先导入F
from django.db.models import F
# 1、把所有的书籍价格提高100
models.Book.objects.update(price=F('price') + 100)

# 2、统计卖出的大于库存的书籍
res = models.Book.objects.filter(maichu__gt=F('kucun')).values('name')
print(res)

字符串拼接

在操作字符类型的数据时,F查询不能做到直接字符串的拼接

要导入两个模块

from django.db.models.functions import Concat
from django.db.models import Value

# 3、在所有的书籍名称后面加上名著两个字
from django.db.models.functions import Concat
from django.db.models import Value
models.Book.objects.update(name=Concat(F('name'), Value('名著')))

"""
在用F查询做拼接的时候要注意操作字段的数据类型
如果是字符形式的直接做拼接会让当前数据全部为空
"""

Q查询

Q查询可以把filter方法默认的与关系改为或关系和非关系

filter括号内多个只能是 与(and) 关系,不能改成 或(or) 关系或者 非(not) 关系
要改变逻辑关系就要借助Q模块
from django.db.models import Q

# 价格大于5000且库存小于500
models.Book.objects.filter(price__gt=5000, kucun__lt=500)

# 当我想要查询价格大于5000或库存小于500的书籍时,就要把price__gt=5000和kucun__lt=500用Q()分别包起来
models.Book.objects.filter(Q(price__gt=5000), Q(kucun__lt=500))

改变之间的逗号就可以改变逻辑关系
,:与(and)
|:或(or)
Q()前面加 ~ :非(not
# 查询价格大于5000或者库存小于500的书

# 导入Q模块
from django.db.models import Q
# 逗号隔开是and
res = models.Book.objects.filter(Q(price__gt=5000), Q(kucun__lt=500)).values('name')
print(res)

# 竖杠隔开是or
res = models.Book.objects.filter(Q(price__gt=5000) | Q(kucun__lt=500)).values('name')
print(res)

# 在Q()前面加~是not,指当前筛选条件取反
res = models.Book.objects.filter(~Q(price__gt=5000) | Q(kucun__lt=500)).values('name')
print(res)
Q查询的高阶用法
"""
想要把查询条件变为动态的,根据用户的选择来确定筛选的字段
也就是把 price__gt=5000 中的price__gt变为用户输入的字符
但是获取的用户输入是字符串形式的,而filter括号中要的是变量名形式的,不是字符串形式
所以要换一种方式
"""
from django.db.models import Q
# Q模块是一个类,创建一个q对象
q = Q()

# 改变逻辑关系为or关系,默认是and关系
q.connector = 'or'

# 这样写就相当于 Q(price__gt=5000),但是price__gt是字符串形式的,这样就可以动态的改变筛选条件
# filter(Q(price__gt=5000))
q.children.append(('price__gt', 5000))
# filter(Q(price__gt=5000), Q(kucun__lt=500))
q.children.append(('kucun__lt', 500))
# filter(Q(price__gt=5000), Q(kucun__lt=500), Q(maichu__gt=500))
q.children.append(('maichu__gt', 500))
"""每写一个q.children.append()就是在filter中添加一个Q(),再用指定的逻辑符链接"""

# models.Book.objects.filter(Q(price__gt=5000) | Q(kucun__lt=500) | Q(maichu__gt=500))
res = models.Book.objects.filter(q).values('name')
print(res)

简单的使用

from django.db.models import Q
# 选择字段
print('选择字段')
ziduan1 = input()
ziduan2 = input()

# 判断条件
print('判断条件')
panduan1 = input()
panduan2 = input()

# 筛选条件
print('筛选条件')
shaixuan1 = input()
shaixuan2 = input()
shaixuan1 = int(shaixuan1)
shaixuan2 = int(shaixuan2)

# 拼接字符串
user_info1 = f'{ziduan1}__{panduan1}'
user_info2 = f'{ziduan2}__{panduan2}'

q = Q()
q.connector = 'or'
q.children.append((user_info1, shaixuan1))
q.children.append((user_info2, shaixuan2))
res = models.Book.objects.filter(q).values('name', 'price', 'kucun')
print(res)

在Django中开启事务

# 只需要导入一个模块
from django.db import transaction
with transaction.atomic():
    # sql语句1
    # sql语句2
    # 。。。
    # 在with代码块内书写的所有ORM操作都属于同一个事务
    
# 当你写的事务报错的时候可以使用异常捕获
from django.db import transaction
try :
    with transaction.atomic():
        # sql语句1
        # sql语句2
        # 。。。
        # 在with代码块内书写的所有ORM操作都属于同一个事务
except Exception as e:
	print(e)

数据库查询优化

# ORM语句的特点:惰性查询
如果你仅仅只是书写了ORM语句,但是在后面根本没有用到该语句所查询出来的参数,那么ORM会自动识别,不执行该语句

only与defer

"""想要获取图书表中所有书的名字"""

# 使用values得到的是一个列表套字典的形式,需要for循环取值
res = models.Book.objects.values('name')
for i in res:
    print(i.get('name'))
    
    
# 使用only得到的是一个列表,其中元素是一个个queryset对象
# <QuerySet [<Book: Book object>, <Book: Book object>, <Book: Book object>]>
res = models.Book.objects.only('name')
for i in res:
    # 点only括号内有的字段不会走数据库
	print(i.name)
    # 点only括号内没有的字段会重新走数据库查询
    print(i.price)
"""
only括号里面可以放多个字段,用逗号隔开
only括号内有的字段在查询出来的对象里面,点字段可以不重新走数据库
only括号内没有的字段不在查询出来的对象里面,点字段要重新走数据库

但是如果使用.all()来查询,那么查所有的字段数据都不用重新走数据库了
res = models.Book.objects.all()
for i in res:
	print(i.name, i.price)
"""

# defer
res = models.Book.objects.defer('name')
for i in res:
    # 点defer括号内有的字段会重新走数据库查询
	print(i.name)
    # 点defer括号内没有的字段不会走数据库
    print(i.price)
"""
defer括号里面可以放多个字段,用逗号隔开
defer与only刚好相反
	defer括号内的字段不在查询出来的对象里面,查询该字段需要重新走数据库
	而如果查询的字段不是括号内的字段,则不需要搜数据库了
"""

select_related与prefetch_related涉及到的是跨表操作

select_related连表操作

"""获取每本书对应的作者的名字"""

# 传统的方式,先获取图书对象,再通过点外键字段获取作者对象
res = models.Book.objects.all()
for i in res:
    # 用这种方式每循环一次就要走一次数据库查询
    print(i.author.name)

# 使用select_related
res = models.Book.objects.select_related('author')
for i in res:
    print(i.author.name)
"""
select_related内部是联表操作,在括号里写author字段,它内部就会将book表和author表连起来,然后将大表里面的所有数据全部封装给查询出来的对象
这时候无论是点book表中的数据还是author表中的数据都不需要在走数据库查询了

注意:select_related括号内只能放外键字段:一对一和一对多的外键字段
	多对多的外键字段也不能放,会报错
"""

prefetch_related子查询

"""获取每本书对应的作者的名字"""
res = models.Book.objects.prefetch_related('author')
for i in res:
    print(i.author.name)
"""
prefetch_related该方法内部就是子查询
	将子查询出来的所有结果也给封装到对象中
	给你的感觉也是一次性搞定的
"""

总结

虽然连表操作比子查询步骤要少一步,但是当两个表特别大的时候就不一定连表操作要快了,根据实际情况选择多表查询的方法

posted @   7七柒  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Blazor Hybrid适配到HarmonyOS系统
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· 解决跨域问题的这6种方案,真香!
· 一套基于 Material Design 规范实现的 Blazor 和 Razor 通用组件库
· 数据并发安全校验处理工具类
点击右上角即可分享
微信分享提示