Django之模型层:表操作

Django之模型层:表操作

目录

一、ORM简介

ORM

  • MVC或者MTV框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动
  • ORM是“对象-关系-映射”的简称。

ORM对应关系

#sql中的表                                                      

 #创建表:
     CREATE TABLE employee(                                     
                id INT PRIMARY KEY auto_increment ,                    
                name VARCHAR (20),                                      
                gender BIT default 1,                                  
                birthday DATA ,                                         
                department VARCHAR (20),                                
                salary DECIMAL (8,2) unsigned,                          
              );


  #sql中的表纪录                                                  

  #添加一条表纪录:                                                          
      INSERT employee (name,gender,birthday,salary,department)            
             VALUES   ("alex",1,"1985-12-12",8000,"保洁部");               

  #查询一条表纪录:                                                           
      SELECT * FROM employee WHERE age=24;                               

  #更新一条表纪录:                                                           
      UPDATE employee SET birthday="1989-10-24" WHERE id=1;              

  #删除一条表纪录:                                                          
      DELETE FROM employee WHERE name="alex"                             
#python的类
class Employee(models.Model):
     id=models.AutoField(primary_key=True)
     name=models.CharField(max_length=32)
     gender=models.BooleanField()
     birthday=models.DateField()
     department=models.CharField(max_length=32)
     salary=models.DecimalField(max_digits=8,decimal_places=2)


 #python的类对象
      #添加一条表纪录:
          emp=Employee(name="alex",gender=True,birthday="1985-12-12",epartment="保洁部")
          emp.save()
      #查询一条表纪录:
          Employee.objects.filter(age=24)
      #更新一条表纪录:
          Employee.objects.filter(id=1).update(birthday="1989-10-24")
      #删除一条表纪录:
          Employee.objects.filter(name="alex").delete()

示例

img

我们在使用Django框架开发web应用的过程中,不可避免地会涉及到数据的管理操作(如增、删、改、查),而一旦谈到数据的管理操作,就需要用到数据库管理软件,例如mysql、oracle、Microsoft SQL Server等。

如果应用程序需要操作数据(比如将用户注册信息永久存放起来),那么我们需要在应用程序中编写原生sql语句,然后使用pymysql模块远程操作mysql数据库,详见图1

但是直接编写原生sql语句会存在两方面的问题,严重影响开发效率,如下

#1. sql语句的执行效率:应用开发程序员需要耗费一大部分精力去优化sql语句
#2. 数据库迁移:针对mysql开发的sql语句无法直接应用到oracle数据库上,一旦需要迁移数据库,便需要考虑跨平台问题

为了解决上述问题,django引入了ORM的概念,ORM全称Object Relational Mapping,即对象关系映射,是在pymysq之上又进行了一层封装,对于数据的操作,我们无需再去编写原生sql,取代代之的是基于面向对象的思想去编写类、对象、调用相应的方法等,ORM会将其转换/映射成原生SQL然后交给pymysql执行,详见图2

。。。。。。插图1

插图1

原生SQL与ORM的对应关系示例如下

插图2

如此,开发人员既不用再去考虑原生SQL的优化问题,也不用考虑数据库迁移的问题,ORM都帮我们做了优化且支持多种数据库,这极大地提升了我们的开发效率,下面就让我们来详细学习ORM的使用吧

django测试环境搭建

import os
if __name__ == "__main__":
        os.environ.setdefault("DJANGO_SETTINGS_MODULE", "one_search.settings")
        import django
        django.setup()
        # 你就可以在下面测试django任何的py文件

Django终端打印SQL语句

如果你想查看orm语句内部真正的sql语句有两种方式

            1.如果是queryset对象 可以直接点query查看
            2.配置文件中 直接配置
            LOGGING = {
                'version': 1,
                'disable_existing_loggers': False,
                'handlers': {
                    'console': {
                        'level': 'DEBUG',
                        'class': 'logging.StreamHandler',
                    },
                },
                'loggers': {
                    'django.db.backends': {
                        'handlers': ['console'],
                        'propagate': True,
                        'level': 'DEBUG',
                    },
                }}
#只要是queryset对象就可以无限制的点queryset对象的方法
    queryset.filter().filter().filter()

二 单表操作

2.1 按步骤创建表

2.1.1

创建django项目,新建名为app01的app,在app01的models.py中创建模型
class Employee(models.Model): # 必须是models.Model的子类
    id=models.AutoField(primary_key=True)

    name=models.CharField(max_length=16)

    gender=models.BooleanField(default=1)

    birth=models.DateField()

    department=models.CharField(max_length=30)

    salary=models.DecimalField(max_digits=10,decimal_places=1)

2.1.2

django的orm支持多种数据库,如果想将上述模型转为mysql数据库中的表,需要settings.py中
# 删除\注释掉原来的DATABASES配置项,新增下述配置
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql', # 使用mysql数据库
        'NAME': 'db1',          # 要连接的数据库
        'USER': 'root',         # 链接数据库的用于名
        'PASSWORD': '',         # 链接数据库的用于名                  
        'HOST': '127.0.0.1',    # mysql服务监听的ip  
        'PORT': 3306,           # mysql服务监听的端口  
        'ATOMIC_REQUEST': True, #设置为True代表同一个http请求所对应的所有sql都									放在一个事务中执行 
                                #(要么所有都成功,要么所有都失败),这是全局性的配									置,如果要对某个
                                #http请求放水(然后自定义事务),可以用											non_atomic_requests修饰器 
        'OPTIONS': {
            "init_command": "SET storage_engine=INNODB", #设置创建表的存储引																擎为INNODB
        }
    }
}

2.1.3

在链接mysql数据库前,必须事先创建好数据库
mysql> create database db1; # 数据库名必须与settings.py中指定的名字对应上

2.1.4

确保配置文件settings.py中的INSTALLED_APPS中添加我们创建的app名称,django2.x与django1.x处理添加方式不同
# django1.x版本,在下述列表中新增我们的app名字即可
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01',
    # 'app02' # 若有新增的app,依次添加即可
]

# django2.x版本,可能会帮我们自动添加app,只是换了一种添加方式
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config', # 如果默认已经添加了,则无需重复添加
    # 'app02.apps.App02Config', # 若有新增的app,按照规律依次添加即可
]

2.1.5

如果想打印orm转换过程中的sql,需要在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',
        },
    }
}

2.1.6

最后我们需要的驱动是PyMySQL,然后在命令行中执行两条数据库迁移命令,即可在指定的数据库db1中创建表 :

NAME即数据库的名字,在mysql连接前该数据库必须已经创建,而上面的sqlite数据库下的db.sqlite3则是项目自动创建 USER和PASSWORD分别是数据库的用户名和密码。设置完后,再启动我们的Django项目前,我们需要激活我们的mysql。然后,启动项目,会报错:no module named MySQLdb 。这是因为django默认你导入的驱动是MySQLdb,可是MySQLdb 对于py3有很大问题,所以我们需要的驱动是PyMySQL 所以,我们只需要找到项目名文件下的__init__,在里面写入:

import pymysql
pymysql.install_as_MySQLdb()

插图3

$ python manage.py makemigrations
$ python manage.py migrate

# 注意:
# 1、makemigrations只是生成一个数据库迁移记录的文件,而migrate才是将更改真正提交到数据库执行
# 2、数据库迁移记录的文件存放于app01下的migrations文件夹里
# 3、了解:使用命令python manage.py showmigrations可以查看没有执行migrate的文件

注意1:

在使用的是django1.x版本时,如果报如下错误

django.core.exceptions.ImproperlyConfigured: mysqlclient 1.3.3 or newer is required; you have 0.7.11.None

那是因为MySQLclient目前只支持到python3.4,如果使用的更高版本的python,需要找到文件C:\Programs\Python\Python36-32\Lib\site-packages\Django-2.0-py3.6.egg\django\db\backends\mysql
这个路径里的文件

# 注释下述两行内容即可
if version < (1, 3, 3):
     raise ImproperlyConfigured("mysqlclient 1.3.3 or newer is required; you have %s" % Database.__version__)

注意2:

当我们直接去数据库里查看生成的表时,会发现数据库中的表与orm规定的并不一致,这完全是正常的,事实上,orm的字段约束就是不会全部体现在数据库的表中,比如我们为字段gender设置的默认值default=1,去数据库中查看会发现该字段的default部分为null

mysql> desc app01_employee; # 数据库中标签前会带有前缀app01_
+------------+---------------+------+-----+---------+----------------+
| Field      | Type          | Null | Key | Default | Extra          |
+------------+---------------+------+-----+---------+----------------+
| id         | int(11)       | NO   | PRI | NULL    | auto_increment |
| name       | varchar(16)   | NO   |     | NULL    |                |
| gender     | tinyint(1)    | NO   |     | NULL    |                |
| birth      | date          | NO   |     | NULL    |                |
| department | varchar(30)   | NO   |     | NULL    |                |
| salary     | decimal(10,1) | NO   |     | NULL    |                |
+------------+---------------+------+-----+---------+----------------+

,虽然数据库没有增加默认值,但是我们在使用orm插入值时,完全为gender字段插入空,orm会按照自己的约束将空转换成默认值后,再提交给数据库执行

2.1.7

在表生成之后,如果需要增加、删除、修改表中字段,需要这么做
# 一:增加字段
#1.1、在模型类Employee里直接新增字段,强调:对于orm来说,新增的字段必须用default指定默认值
publish = models.CharField(max_length=12,default='人民出版社',null=True)
#1.2、重新执行那两条数据库迁移命令


# 二:删除字段
#2.1 直接注释掉字段
#2.2 重新执行那两条数据库迁移命令

# 三:修改字段
#2.1 将模型类中字段修改
#2.2 重新执行那两条数据库迁移命令

2.1.8

更多字段和参数

每个字段有一些特有的参数,例如,CharField需要max_length参数来指定VARCHAR数据库字段的大小。还有一些适用于所有字段的通用参数。 这些参数在文档中有详细定义,这里我们只简单介绍一些最常用的:

字段
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):
        - 小整数 -32768 ~ 32767

    PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
        - 正小整数 0 ~ 32767
    IntegerField(Field)
        - 整数列(有符号的) -2147483648 ~ 2147483647

    PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
        - 正整数 0 ~ 2147483647

    BigIntegerField(IntegerField):
        - 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807

    自定义无符号整数字段

        class UnsignedIntegerField(models.IntegerField):
            def db_type(self, connection):
                return 'integer UNSIGNED'

        PS: 返回值为字段在数据库中的属性,Django字段默认的值为:
            '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)',

    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)
        - 二进制类型
参数
(1)null
 
如果为True,Django 将用NULL 来在数据库中存储空值。 默认值是 False.
 
(1)blank
 
如果为True,该字段允许不填。默认为False。
要注意,这与 null 不同。null纯粹是数据库范畴的,而 blank 是数据验证范畴的。
如果一个字段的blank=True,表单的验证将允许该字段是空值。如果字段的blank=False,该字段就是必填的。
 
(2)default
 
字段的默认值。可以是一个值或者可调用对象。如果可调用 ,每有新对象被创建它都会被调用。
 
(3)primary_key
 
如果为True,那么这个字段就是模型的主键。如果你没有指定任何一个字段的primary_key=True,
Django 就会自动添加一个IntegerField字段做为主键,所以除非你想覆盖默认的主键行为,
否则没必要设置任何一个字段的primary_key=True。
 
(4)unique
 
如果该值设置为 True, 这个数据字段的值在整张表中必须是唯一的
 
(5)choices
由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,<br>而且这个选择框的选项就是choices 中的选项。
元信息
class UserInfo(models.Model):
        nid = models.AutoField(primary_key=True)
        username = models.CharField(max_length=32)
        class Meta:
            # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名
            db_table = "table_name"

            # 联合索引
            index_together = [
                ("pub_date", "deadline"),
            ]

            # 联合唯一索引
            unique_together = (("driver", "restaurant"),)

            # admin中显示的表名称
            verbose_name

            # verbose_name加s
            verbose_name_plural

2.2记录

删除,直接注释掉字段,执行数据库迁移命令即可

新增字段,在类里直接新增字段,直接执行数据库迁移命令会提示输入默认值,此时需要设置

publish = models.CharField(max_length=12,default='人民出版社',null=True)

注意:

  1 数据库迁移记录都在 app01下的migrations里

  2 使用showmigrations命令可以查看没有执行migrate的文件

  3 makemigrations是生成一个文件,migrate是将更改提交到数据量

2.2.1 添加记录

方式一:
# 1、用模型类创建一个对象,一个对象对应数据库表中的一条记录
obj = Employee(name="Egon", gender=0, birth='1997-01-27', department="财务部", salary=100.1)
# 2、调用对象下的save方法,即可以将一条记录插入数据库
obj.save()
方式二:
# 每个模型表下都有一个objects管理器,用于对该表中的记录进行增删改查操作,其中增加操作如下所示
obj = Employee.objects.create(name="Egon", gender=0, birth='1997-01-27', department="财务部", salary=100.1)

2.2.2 查询记录

2.2.2.1 查询API

模型Employee对应表app01_employee,表app01_employee中的每条记录都对应类Employee的一个对象,我们以该表为例,来介绍查询API,读者可以自行添加下述记录,然后配置url、编写视图测试下述API

mysql> select * from app01_employee;
+----+-------+--------+------------+------------+--------+
| id | name  | gender | birth      | department | salary |
+----+-------+--------+------------+------------+--------+
|  1 | Egon  |      0 | 1997-01-27 | 财务部     |  100.1 |
|  2 | Kevin |      1 | 1998-02-27 | 技术部     |   10.1 |
|  3 | Lili  |      0 | 1990-02-27 | 运营部     |   20.1 |
|  4 | Tom   |      1 | 1991-02-27 | 运营部     |   30.1 |
|  5 | Jack  |      1 | 1992-02-27 | 技术部     |   11.2 |
|  6 | Robin |      1 | 1988-02-27 | 技术部     |  200.3 |
|  7 | Rose  |      0 | 1989-02-27 | 财务部     |   35.1 |
|  8 | Egon  |      0 | 1997-01-27 | 财务部     |  100.1 |
|  9 | Egon  |      0 | 1997-01-27 | 财务部     |  100.1 |
+----+-------+--------+------------+------------+--------+

每个模型表下都有一个objects管理器,用于对该表中的记录进行增删改查操作,其中查询操作如下所示

img

Part1:

!!!强调!!!:下述方法(除了count外)的返回值都是一个模型类Employee的对象,为了后续描述方便,我们统一将模型类的对象称为"记录对象",每一个”记录对象“都唯一对应表中的一条记录,

# 1. get(**kwargs)
# 1.1: 有参,参数为筛选条件
# 1.2: 返回值为一个符合筛选条件的记录对象(有且只有一个),如果符合筛选条件的对象超过一个或者没有都会抛出错误。
obj=Employee.objects.get(id=1)
print(obj.name,obj.birth,obj.salary) #输出:Egon 1997-01-27 100.1

# 2、first()
# 2.1:无参
# 2.2:返回查询出的第一个记录对象
obj=Employee.objects.first() # 在表所有记录中取第一个
print(obj.id,obj.name) # 输出:1 Egon

# 3、last()
# 3.1: 无参
# 3.2: 返回查询出的最后一个记录对象
obj = Employee.objects.last() # 在表所有记录中取最后一个
print(obj.id, obj.name)  # 输出:9 Egon

# 4、count():
# 4.1:无参
# 4.2:返回包含记录对象的总数量
res = Employee.objects.count() # 统计表所有记录的个数
print(res) # 输出:9

# 注意:如果我们直接打印Employee的对象将没有任何有用的提示信息,我们可以在模型类中定义__str__来进行定制
class Employee(models.Model):
    ......
    # 在原有的基础上新增代码如下
    def __str__(self):
        return "<%s:%s>" %(self.id,self.name)
# 此时我们print(obj)显示的结果就是: <本条记录中id字段的值:本条记录中name字段的值>

Part2:

!!!强调!!!:下述方法查询的结果都有可能包含多个记录对象,为了存放查询出的多个记录对象,django的ORM自定义了一种数据类型Queryeset,所以下述方法的返回值均为QuerySet类型的对象,QuerySet对象中包含了查询出的多个记录对象

# 1、filter(**kwargs):
# 1.1:有参,参数为过滤条件
# 1.2:返回值为QuerySet对象,QuerySet对象中包含了符合过滤条件的多个记录对象
queryset_res=Employee.objects.filter(department='技术部')
# print(queryset_res) # 输出: <QuerySet [<Employee: <2:Kevin>>, <Employee: <5:Jack>>, <Employee: <6:Robin>>]>

# 2、exclude(**kwargs)
# 2.1: 有参,参数为过滤条件
# 2.2: 返回值为QuerySet对象,QuerySet对象中包含了不符合过滤条件的多个记录对象
queryset_res=Employee.objects.exclude(department='技术部')

# 3、all()
# 3.1:无参
# 3.2:返回值为QuerySet对象,QuerySet对象中包含了查询出的所有记录对象
queryset_res = Employee.objects.all() # 查询出表中所有的记录对象

# 4、order_by(*field):
# 4.1:有参,参数为排序字段,可以指定多个字段,在字段1相同的情况下,可以按照字段2进行排序,以此类推,默认升序排列,在字段前加横杆代表降序排(如"-id")
# 4.2:返回值为QuerySet对象,QuerySet对象中包含了排序好的记录对象
queryset_res = Employee.objects.order_by("salary","-id") # 先按照salary字段升序排,如果salary相同则按照id字段降序排

# 5、values(*field)
# 5.1:有参,参数为字段名,可以指定多个字段
# 5.2:返回值为QuerySet对象,QuerySet对象中包含的并不是一个个的记录对象,而上多个字典,字典的key即我们传入的字段名
queryset_res = Employee.objects.values('id','name')
print(queryset_res) # 输出:<QuerySet [{'id': 1, 'name': 'Egon'}, {'id': 2, 'name': 'Kevin'}, ......]>
print(queryset_res[0]['name']) # 输出:Egon

# 6、values_list(*field):
# 6.1:有参,参数为字段名,可以指定多个字段
# 6.2:返回值为QuerySet对象,QuerySet对象中包含的并不是一个个的记录对象,而上多个小元组,字典的key即我们传入的字段名
queryset_res = Employee.objects.values_list('id','name')
print(queryset_res) # 输出:<QuerySet [(1, 'Egon'), (2, 'Kevin'),), ......]>
print(queryset_res[0][1]) # 输出:Egon

Part3:

Part2中所示查询API的返回值都是QuerySet类型的对象,QuerySet类型是django ORM自定义的一种数据类型,专门用来存放查询出的多个记录对象,该类型的特殊之处在于
1、queryset类型类似于python中的列表,支持索引操作

# 过滤出符合条件的多个记录对象,然后存放到QuerySet对象中
queryset_res=Employee.objects.filter(department='技术部') 
# 按照索引从QuerySet对象中取出第一个记录对象
obj=queryset_res[0]
print(obj.name,obj.birth,obj.salary)

2、管理器objects下的方法queryset下同样可以调用,并且django的ORM支持链式操作,于是我们可以像下面这样使用

# 简单示范:
res=Employee.objects.filter(gender=1).order_by('-id').values_list('id','name')
print(res) # 输出:<QuerySet [(6, 'Robin'), (5, 'Jack'), (4, 'Tom'), (2, 'Kevin')]>

Part4:

其他查询API

# 1、reverse():
# 1.1:无参
# 1.2:对排序的结果取反,返回值为QuerySet对象
queryset_res = Employee.objects.order_by("salary", "-id").reverse()

# 2、exists():
# 2.1:无参
# 2.2:返回值为布尔值,如果QuerySet包含数据,就返回True,否则返回False
res = Employee.objects.filter(id=100).exists()
print(res)  # 输出:False

# 3、distinct():
# 3.1:如果使用的是Mysql数据库,那么distinct()无需传入任何参数
# 3.2:从values或values_list的返回结果中剔除重复的记录对象,返回值为QuerySet对象
res = Employee.objects.filter(name='Egon').values('name', 'salary').distinct()
print(res) # 输出:<QuerySet [{'name': 'Egon', 'salary': Decimal('100.1')}]>

res1 = Employee.objects.filter(name='Egon').values_list('name', 'salary').distinct()
print(res1) # 输出:<QuerySet [('Egon', Decimal('100.1'))]>
2.2.2.2 基于双下划线的模糊查询

插图4

Django 还提供了一种直观而高效的方式在查询(lookups)中表示关联关系,它能自动确认 SQL JOIN 联系。要做跨关系查询,就使用两个下划线来链接模型(model)间关联字段的名称,直到最终链接到你想要的model 为止。

'''
    正向查询按字段,反向查询按表名小写用来告诉ORM引擎join哪张表
'''
一对多查询
# 练习:  查询苹果出版社出版过的所有书籍的名字与价格(一对多)

    # 正向查询 按字段:publish

    queryResult=Book.objects
            .filter(publish__name="苹果出版社")
            .values_list("title","price")

    # 反向查询 按表名:book

    queryResult=Publish.objects
              .filter(name="苹果出版社")
              .values_list("book__title","book__price")
查询的本质一样,就是select from的表不一样
    # 正向查询按字段,反向查询按表名小写
    # 查询红楼梦这本书出版社的名字
    # select * from app01_book inner join app01_publish
    # on app01_book.publish_id=app01_publish.nid
    ret=Book.objects.filter(name='红楼梦').values('publish__name')
    print(ret)
    ret=Publish.objects.filter(book__name='红楼梦').values('name')
    print(ret)
多对多查询
# 练习: 查询alex出过的所有书籍的名字(多对多)

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

    # 反向查询 按表名:book
    queryResult=Author.objects
              .filter(name="yuan")
              .values_list("book__title","book__price")
    # 正向查询按字段,反向查询按表名小写
    # 查询红楼梦这本书出版社的名字
    # select * from app01_book inner join app01_publish
    # on app01_book.publish_id=app01_publish.nid
    ret=Book.objects.filter(name='红楼梦').values('publish__name')
    print(ret)
    ret=Publish.objects.filter(book__name='红楼梦').values('name')
    print(ret)
    # sql 语句就是from的表不一样
    # -------多对多正向查询
    # 查询红楼梦所有的作者
    ret=Book.objects.filter(name='红楼梦').values('authors__name')
    print(ret)
    # ---多对多反向查询
    ret=Author.objects.filter(book__name='红楼梦').values('name')
    ret=Author.objects.filter(book__name='红楼梦').values('name','author_detail__addr')
    print(ret)

多对多关系其它常用API:

book_obj.authors.remove()      # 将某个特定的对象从被关联对象集合中去除。    ======   book_obj.authors.remove(*[])
book_obj.authors.clear()       #清空被关联对象集合
book_obj.authors.set()         #先清空再设置 
一对一查询
# 查询alex的手机号
    
    # 正向查询
    ret=Author.objects.filter(name="alex").values("authordetail__telephone")

    # 反向查询
    ret=AuthorDetail.objects.filter(author__name="alex").values("telephone")
    # 查询lqz的手机号
    # 正向查
    ret=Author.objects.filter(name='lqz').values('author_detail__telephone')
    print(ret)
    # 反向查
    ret= AuthorDatail.objects.filter(author__name='lqz').values('telephone')
    print(ret)
进阶练习(连续跨表)
# 练习: 查询人民出版社出版过的所有书籍的名字以及作者的姓名


    # 正向查询
    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")


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


    # 方式1:
    queryResult=Book.objects
            .filter(authors__authorDetail__telephone__regex="151")
            .values_list("title","publish__name")
    # 方式2:    
    ret=Author.objects
              .filter(authordetail__telephone__startswith="151")
              .values("book__title","book__publish__name")
  # ----进阶练习,连续跨表
    # 查询手机号以33开头的作者出版过的书籍名称以及书籍出版社名称
    # author_datail author book publish
    # 基于authorDatail表
    ret=AuthorDatail.objects.filter(telephone__startswith='33').values('author__book__name','author__book__publish__name')
    print(ret)
    # 基于Author表
    ret=Author.objects.filter(author_detail__telephone__startswith=33).values('book__name','book__publish__name')
    print(ret)
    # 基于Book表
    ret=Book.objects.filter(authors__author_detail__telephone__startswith='33').values('name','publish__name')
    print(ret)
    # 基于Publish表
    ret=Publish.objects.filter(book__authors__author_detail__telephone__startswith='33').values('book__name','name')
    print(ret)
publish = ForeignKey(Blog, related_name='bookList')
# 练习: 查询人民出版社出版过的所有书籍的名字与价格(一对多)

# 反向查询 不再按表名:book,而是related_name:bookList


    queryResult=Publish.objects
              .filter(name="人民出版社")
              .values_list("bookList__title","bookList__price") 

反向查询时,如果定义了related_name ,则用related_name替换表名,例如:

2.2.2.3 F与Q查询
F查询

在上面所有的例子中,我们在进行条件过滤时,都只是用某个字段与某个具体的值做比较。如果我们要对两个字段的值做比较,那该怎么做呢?

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

# 一张书籍表中包含字段:评论数commentNum、收藏数keepNum,要求查询:评论数大于收藏数的书籍
from django.db.models import F
Book.objects.filter(commnetNum__lt=F('keepNum'))

Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作

# 查询评论数大于收藏数2倍的书籍
from django.db.models import F
Book.objects.filter(commnetNum__lt=F('keepNum')*2)

修改操作也可以使用F函数,比如将每一本书的价格提高30元:

Book.objects.all().update(price=F("price")+30) 
Q查询

img

filter() 等方法中逗号分隔开的多个关键字参数都是逻辑与(AND) 的关系。 如果我们需要使用逻辑或(OR)来连接多个条件,就用到了Django的Q对象

可以将条件传给类Q来实例化出一个对象,Q的对象可以使用&| 操作符组合起来,&等同于and,|等同于or

from django.db.models import Q
Employee.objects.filter(Q(id__gt=5) | Q(name="Egon"))

# 等同于sql:select * from app01_employee where id < 5 or name = 'Egon';

Q 对象可以使用~ 操作符取反,相当于NOT

from django.db.models import Q
Employee.objects.filter(~Q(id__gt=5) | Q(name="Egon"))

# 等同于sql:select * from app01_employee where not (id < 5) or name = 'Egon';

当我们的过滤条件中既有or又有and,则需要混用Q对象与关键字参数,但Q 对象必须位于所有关键字参数的前面

from django.db.models import Q
Employee.objects.filter(Q(id__gt=5) | Q(name="Egon"),salary__lt=100)

# 等同于sql:select * from app01_employee where (id < 5 or name = 'Egon') and salary < 100;
2.2.24 聚合查询

聚合查询aggregate()是把所有查询出的记录对象整体当做一个组,我们可以搭配聚合函数来对整体进行一个聚合操作

from django.db.models import Avg, Max, Sum, Min, Max, Count # 导入聚合函数

# 1. 调用objects下的aggregate()方法,会把表中所有记录对象整体当做一组进行聚合
res1=Employee.objects.aggregate(Avg("salary")) # select avg(salary) as salary__avg from app01_employee;
print(res1) # 输出:{'salary__avg': 70.73}

# 2、aggregate()会把QuerySet对象中包含的所有记录对象当成一组进行聚合
res2=Employee.objects.all().aggregate(Avg("salary")) # select avg(salary) as salary__avg from app01_employee;
print(res2) # 输出:{'salary__avg': 70.73}

res3=Employee.objects.filter(id__gt=3).aggregate(Avg("salary")) # select avg(salary) as salary__avg from app01_employee where id > 3;
print(res3) # 输出:{'salary__avg': 71.0}

aggregate()的返回值为字典类型,字典的key是由”聚合字段的名称___聚合函数的名称”合成的,例如

Avg("salary") 合成的名字为 'salary__avg'

若我们想定制字典的key名,我们可以指定关键参数,如下

res1=Employee.objects.all().aggregate(avg_sal=Avg('salary')) # select avg(salary) as avg_sal from app01_employee;

print(res1) # 输出:{'avg_sal': 70.73} # 关键字参数名就会被当做字典的key

如果我们想得到多个聚合结果,那就需要为aggregate传入多个参数

res1=Employee.objects.all().aggregate(nums=Count('id'),avg_sal=Avg('salary'),max_sal=Max('salary')) 
# 相当于SQL:select count(id) as nums,avg(salary) as avg_sal,max(salary) as max_sal from app01_employee;

print(res1) # 输出:{'nums': 10, 'avg_sal': 70.73, 'max_sal': Decimal('200.3')}
2.2.2.5 分组查询

分组查询annotate()相当于sql语句中的group by,是在分组后,对每个组进行单独的聚合,需要强调的是,在进行单表查询时,annotate()必须搭配values()使用:values("分组字段").annotate(聚合函数),如下

# 表中记录
mysql> select * from app01_employee;
+----+-------+--------+------------+------------+--------+
| id | name  | gender | birth      | department | salary |
+----+-------+--------+------------+------------+--------+
|  1 | Egon  |      0 | 1997-01-27 | 财务部     |  100.1 |
|  2 | Kevin |      1 | 1998-02-27 | 技术部     |   10.1 |
|  3 | Lili  |      0 | 1990-02-27 | 运营部     |   20.1 |
|  4 | Tom   |      1 | 1991-02-27 | 运营部     |   30.1 |
|  5 | Jack  |      1 | 1992-02-27 | 技术部     |   11.2 |
|  6 | Robin |      1 | 1988-02-27 | 技术部     |  200.3 |
|  7 | Rose  |      0 | 1989-02-27 | 财务部     |   35.1 |
+----+-------+--------+------------+------------+--------+

# 查询每个部门下的员工数
res=Employee.objects.values('department').annotate(num=Count('id')) 
# 相当于sql:
# select department,count(id) as num from app01_employee group by department;

print(res) 
# 输出:<QuerySet [{'department': '财务部', 'num': 2}, {'department': '技术部', 'num': 3}, {'department': '运营部', 'num': 2}]>

跟在annotate前的values方法,是用来指定分组字段,即group by后的字段,而跟在annotate后的values方法,则是用来指定分组后要查询的字段,即select 后跟的字段

res=Employee.objects.values('department').annotate(num=Count('id')).values('num')
# 相当于sql:
# select count(id) as num from app01_employee group by department;

print(res)
# 输出:<QuerySet [{'num': 2}, {'num': 3}, {'num': 2}]>

跟在annotate前的filter方法表示where条件,跟在annotate后的filter方法表示having条件,如下

# 查询男员工数超过2人的部门名
res=Employee.objects.filter(gender=1).values('department').annotate(male_count=Count("id")).filter(male_count__gt=2).values('department')

print(res) # 输出:<QuerySet [{'department': '技术部'}]>

# 解析:
# 1、跟在annotate前的filter(gender=1) 相当于 where gender = 1,先过滤出所有男员工信息
# 2、values('department').annotate(male_count=Count("id")) 相当于group by department,对过滤出的男员工按照部门分组,然后聚合出每个部门内的男员工数赋值给字段male_count
# 3、跟在annotate后的filter(male_count__gt=2) 相当于 having male_count > 2,会过滤出男员工数超过2人的部门
# 4、最后的values('department')代表从最终的结果中只取部门名

总结:

1、values()在annotate()前表示group by的字段,在后表示取值
1、filter()在annotate()前表示where条件,在后表示having

需要注意的是,如果我们在annotate前没有指定values(),那默认用表中的id字段作为分组依据,而id各不相同,如此分组是没有意义的,如下

res=Employee.objects.annotate(Count('name')) # 每条记录都是一个分组
res=Employee.objects.all().annotate(Count('name')) # 同上

2.2.3 修改记录

2.2.3.1 直接修改单条记录

可以修改记录对象属性的值,然后执行save方法从而完成对单条记录的直接修改

# 1、获取记录对象
obj=Employee.objects.filter(name='Egon')[0]
# 2、修改记录对象属性的值
obj.name='EGON'
obj.gender=1
# 3、重新保存
obj.save()
2.2.3.2 修改QuerySet中的所有记录对象

QuerySet对象下的update()方法可以更QuerySet中包含的所有对象,该方法会返回一个整型数值,表示受影响的记录条数(相当于sql语句执行结果的rows)

queryset_obj=Employee.objects.filter(id__gt=5)
rows=queryset_obj.update(name='EGON',gender=1)

2.2.4 删除记录

2.2.4.1 直接删除单条记录

可以直接调用记录对象下的delete方法,该方法运行时立即删除本条记录而不返回任何值,如下

obj=Employee.objects.first()
obj.delete()
2.2.4.2 删除QuerySet中的所有记录对象

每个 QuerySet下也都有一个 delete() 方法,它一次性删除 QuerySet 中所有的对象(如果QuerySet对象中只有一个记录对象,那也就只删一条),如下

queryset_obj=Employee.objects.filter(id__gt=5)
rows=queryset_obj.delete()

需要强调的是管理objects下并没有delete方法,这是一种保护机制,是为了避免意外地调用 Employee.objects.delete() 方法导致所有的记录被误删除从而跑路。但如果你确认要删除所有的记录,那么你必须显式地调用管理器下的all方法,拿到一个QuerySet对象后才能调用delete方法删除所有

Employee.objects.all().delete()

三、多表操作

1 创建模型

模型类如下

from django.db import models

# 表app01_publish
class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=20)


# 表app01_book
class Book(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=20)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    pub_date = models.DateField()

    # 表app01_book多对一表app01_publish,参数to指定模型名,参数to_field指定要关联的那个字段
    publish = models.ForeignKey(to='Publish',to_field='nid',on_delete=models.CASCADE)

    # 我们自己写sql时,针对书籍表与作者表的多对关系,需要自己创建新表,而基于django的orm,下面这一行代码可以帮我们自动创建那张关系表
    authors=models.ManyToManyField(to='Author') 
    # 变量名为authors,则新表名为app01_book_authors,若变量名为xxx,则新表名为app01_book_xxx


# 表app01_author
class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=20)
    age = models.IntegerField()

    # 表app01_author一对一表app01_authordetail
    author_detail = models.OneToOneField(to='AuthorDetail',to_field='nid',unique=True,on_delete=models.CASCADE)


# 表app01_authordetail
class AuthorDetail(models.Model):
    nid = models.AutoField(primary_key=True)
    tel = models.CharField(max_length=20)

强调:

在创建关联时,针对参数to,如果传入的是字符串(to="模型名"),则模型类的定义不区分先后顺序,如果传入的是模型名(to=Author),则Author类必须事先定义

注意事项:

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

2 添加、删除、修改记录

2.1 添加记录

插图5

!!强调!!:上图所示的表名、字段名都是mysql中的真实表/物理表,而我们下述所示所有操作,都是通过模型类来操作物理表,例如无论增删改查,所使用的字段名都模型类中的字段

按照上图所示,由于foreign key的关系,我们需要事先往app01_publish与app01_authordetail里插入记录

# 1、需求:通过模型Publish往表app01_publish里插入三家出版社
Publish.objects.create(name='北京出版社')
Publish.objects.create(name='长春出版社')
Publish.objects.create(name='大连出版社')

# 2、需求:通过模型AuthorDetail往表app01_authordetail里插入三条作者详情
AuthorDetail.objects.create(tel='18611312331')
AuthorDetail.objects.create(tel='15033413881')
AuthorDetail.objects.create(tel='13011453220')

按照上图所示,插入时会涉及到多张表,我们同样分三种情况来介绍

2.2 多对一:app01_book与app01_publish

# 需求:书籍(葵花宝典、菊花宝典、桃花宝典)都是在北京出版社出版的
# 1、先通过模型Publish从出版社表app01_publish查出北京出版社
publish_obj=Publish.objects.filter(name='北京出版社').first()
# 上述代码也可以简写为:publish_obj=Publish.objects.get(name='北京出版社')

# 2、再通过模型Book往书籍表app01_book里插入三本书籍与出版社的对应关系
# 方式一:使用publish参数指定关联
book_obj1=Book.objects.create(title='葵花宝典',price=2000,pub_date='1985-3-11',publish=publish_obj)

book_obj2=Book.objects.create(title='菊花宝典',price=3000,pub_date='1990-1-21',publish=publish_obj)

book_obj3=Book.objects.create(title='桃花宝典',price=4000,pub_date='1991-1-23',publish=publish_obj)

# 方式二:使用publish_id参数指定关联
book_obj1=Book.objects.create(title='葵花宝典',price=2000,pub_date='1985-3-11',publish_id=publish_obj.nid)

book_obj2=Book.objects.create(title='菊花宝典',price=3000,pub_date='1990-1-21',publish_id=1) # 在已经出版社id的情况下,可以直接指定

book_obj3=Book.objects.create(title='桃花宝典',price=4000,pub_date='1991-1-23',publish_id=1)

# 注意:无论方式一还是方式二得到的书籍对象book_obj1、book_obj2、book_obj3
#      都可以调用publish字段来访问关联的那一个出版社对象
#      都可以调用publish_id来访问关联的那一个出版社对象的nid
print(book_obj1.publish,book_obj1.publish_id) 
print(book_obj2.publish,book_obj2.publish_id) 
print(book_obj3.publish,book_obj3.publish_id)
# 三本书关联的是同一个出版社,所以输出结果均相同

参照上述步骤,把剩余三本书与出版社的对应关系也插入

book_obj1 = Book.objects.create(title='玉女心经', price=5000, pub_date='1988-3-24', publish_id=2)

book_obj2 = Book.objects.create(title='玉男心经', price=3000, pub_date='1985-6-17', publish_id=2)

book_obj3 = Book.objects.create(title='九阴真经', price=6000, pub_date='1983-8-17', publish_id=3)

2.3 一对一:app01_author与app01_authordetail

# 需求:插入三个作者,并与作者详情表一一对应
# 由于作者详情表我们已经事先创建好记录,所以只需要往作者表插入记录即可
# 方式一:需要事先过滤出作者详情的对象,然后通过模型Author的字段author来指定要关联的作者详情对象(略)

# 方式二:确定作者详情对象的id,然后通过模型Author通过字段author_id来指定关联关系,
Author.objects.create(name='egon',age=18,author_detail_id=1)
Author.objects.create(name='kevin',age=38,author_detail_id=2)
Author.objects.create(name='rose',age=28,author_detail_id=3)

2.4 多对多:app01_book与app01_author

# 我们参照物理表app01_book_authors制定需求,需要创建如下关系
# 1、葵花宝典的作者为:egon、kevin
# 2、菊花宝典的作者为:egon、kevin、rose
# 3、桃花宝典的作者为:egon、kevin
# 4、玉女心经的作者为:kevin、rose
# 5、玉男心经的作者为:kevin
# 6、九阴真经的作者为:egon、rose

# 需要创建出上述关系,具体做法如下
# 1、先获取书籍对象
book_obj1=Book.objects.get(title='葵花宝典')
book_obj2=Book.objects.get(title='菊花宝典')
book_obj3=Book.objects.get(title='桃花宝典')
book_obj4=Book.objects.get(title='玉女心经')
book_obj5=Book.objects.get(title='玉男心经')
book_obj6=Book.objects.get(title='九阴真经')

# 2、然后获取作者对象
egon=Author.objects.get(name='egon')
kevin=Author.objects.get(name='kevin')
rose=Author.objects.get(name='rose')

# 3、最后依次创建上述关系:在原生SQL中多对多关系涉及到操作第三张关系表,但是在ORM中我们只需要操作模型类Book下的字段author即可
book_obj1.authors.add(egon,kevin)
book_obj2.authors.add(egon,kevin,rose)
book_obj3.authors.add(egon,kevin)
book_obj4.authors.add(kevin,rose)
book_obj5.authors.add(kevin)
book_obj6.authors.add(egon,rose)

可以通过书籍对象下的authors字段获取其所关联的所有作者对象

book_obj1.authors.all() # 返回一个存有多个作者的queryset

多对多关系其它常用API:

book_obj.authors.remove()      # 将某个特定的对象从被关联对象集合中去除。    ======   book_obj.authors.remove(*[])
book_obj.authors.clear()       #清空被关联对象集合
book_obj.authors.set()         #先清空再设置 

2.5 删除、修改记录

# 1、book_obj.authors.remove() :将某个特定的对象从被关联对象集合中去除
# 从菊花宝典的作者集合中去掉作者rose
rose = Author.objects.get(name='rose')
book_obj2 = Book.objects.get(title='菊花宝典')
book_obj2.authors.remove(rose)

# 2、book_obj.authors.clear():清空被关联对象集合
# 清空菊花宝典所关联的所有作者
book_obj2 = Book.objects.get(title='菊花宝典')
book_obj2.authors.clear()

# 3、book_obj.authors.set():先清空再重新设置 
# 玉男心经的作者原来为kevin,要求设置为egon、rose
egon=Author.objects.get(name='egon')
rose=Author.objects.get(name='rose')

book_obj5 = Book.objects.get(title='玉男心经')

book_obj5.authors.set([egon,rose]) # 多个作者对象放到列表里

3 查询记录

数据库操作最常用的还是查询操作,在介绍ORM下多表关联查询时,需要事先记住关于ORM模型的一个非常重要的概念:在使用模型类进行多表关联查询时,如果确定两张表存在关联关系,那么在选取一个表作为起始(为了后续描述方便,我们将其简称为"基表")后,可以跨表引用来自另外一张中的字段值,这存在正向与反向之分

如果关联字段存在于基表中,称之为正向查询,否则,称之为反向查询

例如表模Book与Publish,关联字段存在于Book中

# 当以Book为基表时,称之为正向查询
Book(基表)-------正向---------->Publish

# 当以Publish为基表时,称之为反向查询
Book<-------反向----------Publish(基表)

使用原生sql进行多表关联查询时无非两种方式:子查询、join连表查询,ORM里同样有两种查询方式(严格依赖正向、反向的概念)

3.1 基于对象的跨表查询

1、跨两张表查询

1.1、一对一查询(模型类Author与AuthorDetail)

正向查询,按关联字段:author_detail

# 需求:查询作者egon的手机号
# 1、先取出作者对象
egon=Author.objects.filter(name='egon').first() 
# 2、正向查询:根据作者对象下的关联字段author_detail取到作者详情
print(egon.author_detail.tel) # 输出:18611312331

反向查询,按模型名(小写):author

# 需求:查询手机号为'18611312331'的作者名
# 1、先取出作者的详情对象
tel=AuthorDetail.objects.filter(tel='18611312331').first()
# 2、反向查询:根据小写的模型名author取到作者对象
print(tel.author.name) # 输出:egon
1.2、一对多查询(模型类Book与Publish)

正向查询,按关联字段:publish

# 需求:查询葵花宝典的出版社名字
# 1、先取书籍对象
book_obj=Book.objects.filter(title='葵花宝典').first()
# 2、正向查询:根据书籍对象下的关联字段publish取到出版社
print(book_obj.publish.name) # 输出:北京出版社

反向查询,按模型名(小写)_set:book_set

# 需求:查询北京出版社都出版的所有书籍名字
# 1、先取出出版社对象
publish_obj=Publish.objects.filter(name='北京出版社').first()
# 2、反向查询:根据book_set取到所有的书籍
book_objs=publish_obj.book_set.all()
print([book_obj.title for book_obj in book_objs]) # 输出:['葵花宝典', '菊花宝典', '桃花宝典']
1.3、多对多查询(模型类Book与Author)

正向查询,按关联字段,如authors

# 需求:查询葵花宝典的所有作者
# 1、先取出书籍对象
book_obj=Book.objects.filter(title='葵花宝典').first()
# 2、正向查询:根据书籍对象下的关联字段authors取到所有作者
author_objs=book_obj.authors.all()
print([obj.name for obj in author_objs]) # 输出:['egon', 'kevin']

反向查询,按模型名(小写)_set:如author_set

# 需求:查询作者rose出版的所有书籍
# 1、先取出作者对象
egon=Author.objects.filter(name='rose').first() 
# 2、反向查询:根据book_set取到作者对象
book_objs=egon.book_set.all()
print([book_obj.title for book_obj in book_objs]) # 输出:['玉女心经', '九阴真经', '玉男心经']

注意:

你可以通过在 ForeignKey() 和ManyToManyField的定义中设置 related_name 的值来覆写 FOO_set 的名称。例如,如果 Article model 中做一下更改:

publish = ForeignKey(Book, related_name='bookList')

那么接下来就会如我们看到这般:

# 查询 人民出版社出版过的所有书籍
 
publish=Publish.objects.get(name="人民出版社")
book_list=publish.bookList.all()  # 与人民出版社关联的所有书籍对象集合
2、连续跨>2张表查询

连续跨>2张表的操作的套路与上面的案例都是一样的

# 需求:查询葵花宝典的作者们的手机号

book_obj=Book.objects.filter(title='葵花宝典').first()
author_objs=book_obj.authors.all()
print([author_obj.author_detail.tel for author_obj in author_objs]) 
# 输出:['18611312331', '15033413881']

3.2 基于双下划线的跨表查询

Django 还提供了一种直观而高效的方式在查询(lookups)中表示关联关系,它能自动确认 SQL JOIN 联系。要做跨关系查询,就使用两个下划线来链接模型(model)间关联字段的名称,直到最终链接到你想要的model 为止。

'''
    正向查询按字段,反向查询按表名小写用来告诉ORM引擎join哪张表
'''

1、跨两张表查询

1.1、一对一查询(模型类Author与AuthorDetail)

正向查询,按关联字段+双下划线:author_detail__

# 需求:查询作者egon的手机号
# 注意values()中的参数是:关联字段名__要取的那张被关联表中的字段
res = Author.objects.filter(name='egon').values('author_detail__tel').first()
print(res['author_detail__tel']) # {'author_detail__tel': '18611312331'}

# 注意:基于双下划线的跨表查询会被django的orm识别为join操作,所以上述代码相当于如下sql,后续案例均是相同原理,我们不再累述
select app01_authordetail.tel from app01_author inner join app01_authordetail on app01_author.author_detail_id = app01_authordetail.nid where app01_author.name = 'egon';

反向查询,按模型名(小写)+双下划线:author__

# 需求:查询手机号为'18611312331'的作者名
# 注意values()中的参数是:小写的模型名__要取的那张被关联表中的字段
res=AuthorDetail.objects.filter(tel='18611312331').values('author__name').first()
print(res) # {'author__name': 'egon'}

补充:基表决定了正向还是反向

# 1、针对上例中正向查询的需求:查询作者egon的手机号,如果我们选取的基表是AuthorDetail,那么就成了反向查询,应该用反向查询的语法
res = AuthorDetail.objects.filter(author__name='egon').values('tel').first()
print(res)  # {'tel': '18611312331'}

# 2、针对上例中反向查询的需求:查询手机号为'18611312331'的作者名,如果我们选取的基表是Author,那么就成了正向查询,应该用正向查询的语法
res=Author.objects.filter(author_detail__tel='18611312331').values('name').first()
print(res) # {'name': 'egon'}
1.2、多对一查询(模型类Book与Publish)

正向查询,按关联字段+双下划线:publish__

# 需求:查询葵花宝典的出版社名字
# 注意values()中的参数是:关联字段名__要取的那张被关联表中的字段
res=Book.objects.filter(title='葵花宝典').values('publish__name').first()
print(res['publish__name']) # {'publish__name': '北京出版社'}

反向查询,按模型名(小写)+双下划线:book__

# 需求:查询北京出版社都出版的所有书籍名字
# 注意values()中的参数是:小写的模型名__要取的那张被关联表中的字段
res = Publish.objects.filter(name='北京出版社').values('book__title')
print(res)  # <QuerySet [{'book__title': '葵花宝典'}, {'book__title': '菊花宝典'}, {'book__title': '桃花宝典'}]>

补充:基表决定了正向还是反向

# 1、针对上例中正向查询的需求:查询葵花宝典的出版社名字,如果我们选取的基表是Publish,那么就成了反向查询,应该用反向查询的语法
res = Publish.objects.filter(book__title='葵花宝典').values('name').first()
print(res)  # {'name': '北京出版社'}

# 2、针对上例中反向查询的需求:查询北京出版社都出版的所有书籍名字,如果我们选取的基表是Book,那么就成了正向查询,应该用正向查询的语法
res=Book.objects.filter(publish__name='北京出版社').values('title')
print(res) # <QuerySet [{'title': '葵花宝典'}, {'title': '菊花宝典'}, {'title': '桃花宝典'}]>
1.3、多对多查询(模型类Book与Author)

正向查询,按关联字段+双下划线:authors__

# 需求:查询葵花宝典的所有作者
# 注意values()中的参数是:关联字段名__要取的那张被关联表中的字段
res=Book.objects.filter(title='葵花宝典').values('authors__name')
print(res) # <QuerySet [{'authors__name': 'egon'}, {'authors__name': 'kevin'}]> 

反向查询,按模型名(小写)+双下划线:如book__

# 需求:查询作者rose出版的所有书籍
# 注意values()中的参数是:小写的模型名__要取的那张被关联表中的字段
res = Author.objects.filter(name='rose').values('book__title')
print(res) # <QuerySet [{'book__title': '玉女心经'}, {'book__title': '九阴真经'}, {'book__title': '玉男心经'}]>

补充:基表决定了正向还是反向

# 1、针对上例中正向查询的需求:查询葵花宝典的所有作者,如果我们选取的基表是authors,那么就成了反向查询,应该用反向查询的语法
res=Author.objects.filter(book__title='葵花宝典').values('name')
print(res) # <QuerySet [{'name': 'egon'}, {'name': 'kevin'}]>

# 2、针对上例中反向查询的需求:查询作者rose出版的所有书籍,如果我们选取的基表是Book,那么就成了正向查询,应该用正向查询的语法
res=Book.objects.filter(authors__name='rose').values('title')
print(res) # <QuerySet [{'title': '玉女心经'}, {'title': '九阴真经'}, {'title': '玉男心经'}]>

2、连续跨>2张表查询

连续跨>2张表的操作的套路与上面的案例都是一样的,可以连续接n个双下划线,只需要在每次连双下划线时,确定是正向还是反向即可

# 需求1:查询北京出版社出版过的所有书籍的名字以及作者的姓名、手机号
# 方式一:基表为Publish
res=Publish.objects.filter(name='北京出版社').values_list('book__title','book__authors__name','book__authors__author_detail__tel')

# 方式二:基表为Book
res=Book.objects.filter(publish__name='北京出版社').values_list('title','authors__name','authors__author_detail__tel')

# 循环打印结果均为
for obj in res:
    print(obj)
'''
输出:
('葵花宝典', 'egon', '18611312331')
('菊花宝典', 'egon', '18611312331')
('桃花宝典', 'egon', '18611312331')
('葵花宝典', 'kevin', '15033413881')
('菊花宝典', 'kevin', '15033413881')
('桃花宝典', 'kevin', '15033413881')
('菊花宝典', 'rose', '13011453220')
'''


# 需求2:查询手机号以186开头的作者出版过的所有书籍名称以及出版社名称
# 方式一:基表为AuthorDetail
res=AuthorDetail.objects.filter(tel__startswith='186').values_list('author__book__title','author__book__publish__name')

# 方式二:基表为Book
res=Book.objects.filter(authors__author_detail__tel__startswith='186').values_list('title','publish__name')

# 方式三:基表为Publish
res=Publish.objects.filter(book__authors__author_detail__tel__startswith='186').values_list('book__title','name')

# 循环打印结果均为
for obj in res:
    print(obj)
'''
输出:
('葵花宝典', '北京出版社')
('菊花宝典', '北京出版社')
('桃花宝典', '北京出版社')
('九阴真经', '大连出版社')
'''
publish = ForeignKey(Blog, related_name='bookList')
# 练习: 查询人民出版社出版过的所有书籍的名字与价格(一对多)

# 反向查询 不再按表名:book,而是related_name:bookList


    queryResult=Publish.objects
              .filter(name="人民出版社")
              .values_list("bookList__title","bookList__price") 

反向查询时,如果定义了related_name ,则用related_name替换表名,例如:

4 F与Q查询

F查询

在上面所有的例子中,我们在进行条件过滤时,都只是用某个字段与某个具体的值做比较。如果我们要对两个字段的值做比较,那该怎么做呢?

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

# 一张书籍表中包含字段:评论数commentNum、收藏数keepNum,要求查询:评论数大于收藏数的书籍
from django.db.models import F
Book.objects.filter(commnetNum__lt=F('keepNum'))

Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作

# 查询评论数大于收藏数2倍的书籍
from django.db.models import F
Book.objects.filter(commnetNum__lt=F('keepNum')*2)

修改操作也可以使用F函数,比如将每一本书的价格提高30元:

Book.objects.all().update(price=F("price")+30) 

Q查询

img

filter() 等方法中逗号分隔开的多个关键字参数都是逻辑与(AND) 的关系。 如果我们需要使用逻辑或(OR)来连接多个条件,就用到了Django的Q对象

可以将条件传给类Q来实例化出一个对象,Q的对象可以使用&| 操作符组合起来,&等同于and,|等同于or

from django.db.models import Q
Employee.objects.filter(Q(id__gt=5) | Q(name="Egon"))

# 等同于sql:select * from app01_employee where id < 5 or name = 'Egon';

Q 对象可以使用~ 操作符取反,相当于NOT

from django.db.models import Q
Employee.objects.filter(~Q(id__gt=5) | Q(name="Egon"))

# 等同于sql:select * from app01_employee where not (id < 5) or name = 'Egon';

当我们的过滤条件中既有or又有and,则需要混用Q对象与关键字参数,但Q 对象必须位于所有关键字参数的前面

from django.db.models import Q
Employee.objects.filter(Q(id__gt=5) | Q(name="Egon"),salary__lt=100)

# 等同于sql:select * from app01_employee where (id < 5 or name = 'Egon') and salary < 100;

5 聚合查询与分组查询

5.1 聚合查询

聚合查询aggregate()是把所有查询出的记录对象整体当做一个组,我们可以搭配聚合函数来对整体进行一个聚合操作

from django.db.models import Avg, Max, Sum, Min, Max, Count # 导入聚合函数

# 1. 调用objects下的aggregate()方法,会把表中所有记录对象整体当做一组进行聚合
res1=Employee.objects.aggregate(Avg("salary")) # select avg(salary) as salary__avg from app01_employee;
print(res1) # 输出:{'salary__avg': 70.73}

# 2、aggregate()会把QuerySet对象中包含的所有记录对象当成一组进行聚合
res2=Employee.objects.all().aggregate(Avg("salary")) # select avg(salary) as salary__avg from app01_employee;
print(res2) # 输出:{'salary__avg': 70.73}

res3=Employee.objects.filter(id__gt=3).aggregate(Avg("salary")) # select avg(salary) as salary__avg from app01_employee where id > 3;
print(res3) # 输出:{'salary__avg': 71.0}

aggregate()的返回值为字典类型,字典的key是由”聚合字段的名称___聚合函数的名称”合成的,例如

Avg("salary") 合成的名字为 'salary__avg'

若我们想定制字典的key名,我们可以指定关键参数,如下

res1=Employee.objects.all().aggregate(avg_sal=Avg('salary')) # select avg(salary) as avg_sal from app01_employee;

print(res1) # 输出:{'avg_sal': 70.73} # 关键字参数名就会被当做字典的key

如果我们想得到多个聚合结果,那就需要为aggregate传入多个参数

res1=Employee.objects.all().aggregate(nums=Count('id'),avg_sal=Avg('salary'),max_sal=Max('salary')) 
# 相当于SQL:select count(id) as nums,avg(salary) as avg_sal,max(salary) as max_sal from app01_employee;

print(res1) # 输出:{'nums': 10, 'avg_sal': 70.73, 'max_sal': Decimal('200.3')}

5.2 分组查询

分组查询annotate()相当于sql语句中的group by,是在分组后,对每个组进行单独的聚合,需要强调的是,在进行单表查询时,annotate()必须搭配values()使用:values("分组字段").annotate(聚合函数),如下

# 表中记录
mysql> select * from app01_employee;
+----+-------+--------+------------+------------+--------+
| id | name  | gender | birth      | department | salary |
+----+-------+--------+------------+------------+--------+
|  1 | Egon  |      0 | 1997-01-27 | 财务部     |  100.1 |
|  2 | Kevin |      1 | 1998-02-27 | 技术部     |   10.1 |
|  3 | Lili  |      0 | 1990-02-27 | 运营部     |   20.1 |
|  4 | Tom   |      1 | 1991-02-27 | 运营部     |   30.1 |
|  5 | Jack  |      1 | 1992-02-27 | 技术部     |   11.2 |
|  6 | Robin |      1 | 1988-02-27 | 技术部     |  200.3 |
|  7 | Rose  |      0 | 1989-02-27 | 财务部     |   35.1 |
+----+-------+--------+------------+------------+--------+

# 查询每个部门下的员工数
res=Employee.objects.values('department').annotate(num=Count('id')) 
# 相当于sql:
# select department,count(id) as num from app01_employee group by department;

print(res) 
# 输出:<QuerySet [{'department': '财务部', 'num': 2}, {'department': '技术部', 'num': 3}, {'department': '运营部', 'num': 2}]>

跟在annotate前的values方法,是用来指定分组字段,即group by后的字段,而跟在annotate后的values方法,则是用来指定分组后要查询的字段,即select 后跟的字段

res=Employee.objects.values('department').annotate(num=Count('id')).values('num')
# 相当于sql:
# select count(id) as num from app01_employee group by department;

print(res)
# 输出:<QuerySet [{'num': 2}, {'num': 3}, {'num': 2}]>

跟在annotate前的filter方法表示where条件,跟在annotate后的filter方法表示having条件,如下

# 查询男员工数超过2人的部门名
res=Employee.objects.filter(gender=1).values('department').annotate(male_count=Count("id")).filter(male_count__gt=2).values('department')

print(res) # 输出:<QuerySet [{'department': '技术部'}]>

# 解析:
# 1、跟在annotate前的filter(gender=1) 相当于 where gender = 1,先过滤出所有男员工信息
# 2、values('department').annotate(male_count=Count("id")) 相当于group by department,对过滤出的男员工按照部门分组,然后聚合出每个部门内的男员工数赋值给字段male_count
# 3、跟在annotate后的filter(male_count__gt=2) 相当于 having male_count > 2,会过滤出男员工数超过2人的部门
# 4、最后的values('department')代表从最终的结果中只取部门名

总结:

1、values()在annotate()前表示group by的字段,在后表示取值
1、filter()在annotate()前表示where条件,在后表示having

需要注意的是,如果我们在annotate前没有指定values(),那默认用表中的id字段作为分组依据,而id各不相同,如此分组是没有意义的,如下

res=Employee.objects.annotate(Count('name')) # 每条记录都是一个分组
res=Employee.objects.all().annotate(Count('name')) # 同上

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

总结 :

跨表分组查询本质就是将关联表join成一张表,再按单表的思路进行分组查询。

四、ORM批量操作

数据模型定义

from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=200)
    price = models.DecimalField(max_digits=10, decimal_places=2)

批量插入数据

批量插入数据的时候,首先要创建一个对象的列表,然后调用bulk_create方法,一次将列表中的数据插入到数据库中。

product_list_to_insert = list()
for x in range(10):
    product_list_to_insert.append(Product(name='product name ' + str(x), price=x))
Product.objects.bulk_create(product_list_to_insert)

批量更新数据

批量更新数据时,先进行数据过滤,然后再调用update方法进行一次性地更新。下面的语句将生成类似update....frrom....的SQL语句。

Product.objects.filter(name__contains='name').update(name='new name')

批量删除数据

批量更新数据时,先是进行数据过滤,然后再调用delete方法进行一次性删除。下面的语句讲生成类似delete from ... where ... 的SQL语句。

Product.objects.filter(name__contains='name query').delete()

查询练习

练习:统计每一本书作者个数

from django.db.models import Avg, Max, Sum, Min, Max, Count
book_list = models.Book.objects.all().annotate(author_num=Count("authors"))
for book in book_list:
     print(book.name)
     print(book.author_num)
book_list = models.Book.objects.all().annotate(author_num=Count("authors")).values('name','author_num')
print(book_list)

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

publishList=Publish.objects.annotate(MinPrice=Min("book__price"))
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)

练习:统计每一本以py开头的书籍的作者个数:

 queryResult=Book.objects.filter(title__startswith="Py").annotate(num_authors=Count('authors'))

练习:统计不止一个作者的图书:(作者数量大于一)

ret=models.Book.objects.annotate(author_num=Count("authors")).filter(author_num__gt=1).values('name','author_num')
print(ret)

练习:根据一本图书作者数量的多少对查询集 QuerySet进行排序:

Book.objects.annotate(num_authors=Count('authors')).order_by('num_authors')

练习:查询各个作者出的书的总价格:

ret=models.Author.objects.annotate(sum_price=Sum("book__price")).values("name", "sum_price")
print(ret)

练习:查询每个出版社的名称和书籍个数

ret=models.Publish.objects.all().annotate(c=Count('book__name')).values('name','c')
print(ret)

单表下的分组查询

    '''
    查询每一个部门名称以及对应的员工数
    emp:
    id  name age   salary    dep
    1   alex  12   2000     销售部
    2   egon  22   3000     人事部
    3   wen   22   5000     人事部
    '''
    # select count(id) from emp group by dep
    # 示例一:查询每一个部门的名称,以及平均薪水
    # select dep,Avg(salary) from app01_emp group by dep
    from django.db.models import Avg, Count, Max, Min
    ret=Emp.objects.values('dep').annotate(Avg('salary'))
    # 重新命名
    ret=Emp.objects.values('dep').annotate(avg_salary=Avg('salary'))
    print(ret)
    # ---*******单表分组查询ORM总结:表名.objects.values('group by 的字段').annotate(聚合函数('统计的字段'))
    # 示例2 查询每个省份对应的员工数
    ret=Emp.objects.values('province').annotate(Count('id'))
    ret=Emp.objects.values('province').annotate(c=Count('id'))
    print(ret)
    # 补充知识点:
    ret=Emp.objects.all()
    # select * from emp
    ret=Emp.objects.values('name')
    # select name from emp
    # ****单表下,按照id进行分组是没有任何意义的
    ret=Emp.objects.all().annotate(Avg('salary'))
    print(ret)
    # ******多表分组查询
    # 查询每一个出版社出版的书籍个数
    ret=Book.objects.values('publish_id').annotate(Count('nid'))
    print(ret)
    # 查询每个出版社的名称以及出版社书的个数(先join在跨表分组)
    # 正向
    ret=Publish.objects.values('name').annotate(Count('book__name'))
    ret=Publish.objects.values('nid').annotate(c=Count('book__name')).values('name','c')
    print(ret)
    # 反向
    ret=Book.objects.values('publish__name').annotate(Count('name'))
    ret=Book.objects.values('publish__name').annotate(c=Count('name')).values('publish__name','c')
    print(ret)
    # 查询每个作者的名字,以及出版过书籍的最高价格
    ret=Author.objects.values('pk').annotate(c=Max('book__price')).values('name','c')
    print(ret)
    # 跨表查询的模型:每一个后表模型.objects.value('pk').annotate(聚合函数('关联表__统计字段')).values()

    # 查询每一个书籍的名称,以及对应的作者个数
    ret=Book.objects.values('pk').annotate(c=Count('authors__name')).values('name','c')
    print(ret)
    # 统计不止一个作者的图书
    ret=Book.objects.values('pk').annotate(c=Count('authors__name')).filter(c__gt=1).values('name','c')
    print(ret)
posted @ 2019-10-24 19:29  Thousand_Mesh  阅读(125)  评论(0编辑  收藏  举报