Django 模型层
Django ORM
Django
模型层的功能就是与数据库打交道,其中最主要的框架为ORM
。
ORM
为对象关系映射,说白了就是将Python
中的类映射成数据表,将表中每一条记录映射成类的实例对象,将对象属性映射成表中的字段。
如果用原生的SQL
语句结合pymysql
来对数据库进行操作无疑是非常繁琐的,但是Django
提供了非常强大的ORM
框架来对数据库进行操作,在增删改查方面都有非常大的提升,学会使用ORM
十分的必要。
注意:尽管
ORM
十分方便,但是也请不要过分依赖它从而忘记原生SQL
命令。
ORM
作为Django
中最难的一章基础知识点,应该是很多初学者的第一道门槛。
那么在学习ORM
之前,我想写一些我的心得体会,ORM
的操作很方便,但是有些设计比较反人类,你可以使用原生SQL
进行代替。
条条大路通罗马,不一定非要在一棵树上吊死。
准备工作
原生语句
如果想在操作ORM
时还能看到原生的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',
},
}
}
测试脚本
Django
中允许对py
文件做单独的测试,而不用启动Django
项目,这是非常方便的。
注意:所有测试中的代码都必须在
if "__name__" == "__main__":
下进行,这意味着你的from xx import xx
不能放在顶行
from django.test import TestCase
# Create your tests here.
import os
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # 如果在pycharm中,这两句可以省略
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project01.settings")
import django
django.setup()
# 测试代码均在下面进行
from app01 import models
链接MYSQL
Django
中默认使用的数据库是sqlit3
,所以我们需要在配置文件中对其实行修改。
大体分为两个步骤
1.修改默认链接为
MySQL
2.链接声明,即声明链接
MySQL
的模块为pymysql
(默认是MySQLdb
)
修改链接
打开项目全局文件夹下的settings.py
,找到以下代码进行注释
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3', # 默认链接sqlite3
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
现在我们就要进行手动配置了,参照如下代码
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 默认链接sqlite3
'NAME': 'db1', # 你的数据库名称
'USER': 'root', # 你的登录用户名称
'PASSWORD': '123', # 你的登录密码,如果没有留空即可
'HOST': 'localhost', # 链接地址
'PORT': '3306', # MySQL服务端端口号
'CHARSET': 'utf8mb4', # 默认字符编码
}
}
现在,你的Django
会抛出一个异常,不管他,直接进入下一个步骤
django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module: No module named 'MySQLdb'.
Did you install mysqlclient or MySQL-python?
链接声明
由于我们要将默认链接MySQL
的模块从MySQLdb
修改为pymysql
,所以你要先安装pymysql
模块
pip install pymysql
安装完成后打开项目全局文件夹下的__init__
(实际上任意APP
下的__init__
都可以),添加代码如下:
import pymysql
pymysql.install_as_MySQLdb()
现在我们的Django
就可以使用MySQL
进行连接了。
数据库操作
ORM
为对象关系映射,类就相当于数据表,属性就相当于字段。
因此我们有两条很常见的命令用于操作:
python manage.py makemigrations # 创建模型映射表
python manage.py migrate # 同步模型表至数据库中
这两条命令在对数据库字段、数据表结构进行修改时都需要重新进行。
单表创建
在项目的APP
中,打开models.py
,开始创建表(该文件下可以建立一个表,也可以建立多个表)。
注意:
ORM
创建表时如果不指定主键字段,将会默认创建一个名为id
的主键字段。并且我们在使用时可以使用pk
来代指主键
from django.db import models
class User(models.Model): # 必须继承
# 自动创建 id 的主键
username = models.CharField(max_length=16,verbose_name="用户名",db_index=True)
age = models.IntegerField()
gender = models.BooleanField(choices=([0,"male"],[1,"famale"]),default=0)
接下来要运行创建模型映射表的命令,以及运行数据库同步命令。
python manage.py makemigrations # 创建模型映射表
python manage.py migrate # 同步模型表至数据库中
当创建模型映射表命令运行完成后,会发现APP
下的migrations
文件夹中多一一个文件,文件中就存放的刚刚建立好的模型表。
当数据库同步命令执行完成后,MySQL
中才会真正的创建出一张真实的物理表。
模型表信息:
# project01项目/app01/migrations
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2020-09-12 18:18
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='User',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('username', models.CharField(db_index=True, max_length=16, verbose_name='用户名')),
('age', models.IntegerField()),
('gender', models.BooleanField(choices=[[0, 'male'], [1, 'famale']], default=0)),
],
),
]
物理表(注意:物理表的命名会以APP
名称开始):
desc app01_user;
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(16) | NO | MUL | NULL | |
| age | int(11) | NO | | NULL | |
| gender | tinyint(1) | NO | | NULL | |
+----------+-------------+------+-----+---------+----------------+
字段操作
所有关于字段的增删改查都直接操纵对象属性即可,这里要提一下增加字段。
如果需要新增一个字段,而数据表中又有一些数据,此时
Django
会提醒你为原本的老数据设置一个默认值。它会提供给你2个选项,选项1:立即为所有老数据设置默认值。选项2:放弃本次新增字段的操作,重新新增字段并设置默认值(也可以设置
null=True
)
示例如下:我们现在表中创建一条信息。
from app01 import models
res = models.User.objects.create(
username= "云崖",
age=18,
gender=0,
)
print(res)
然后修改其字段,新增一个注册时间。
from django.db import models
class User(models.Model): # 必须继承
# 自动创建 id 的主键
username = models.CharField(max_length=16,verbose_name="用户名",db_index=True)
age = models.IntegerField()
gender = models.BooleanField(choices=([0,"male"],[1,"famale"]),default=0)
register_time = models.DateField(auto_now_add=True)
在执行修改模型映射表的结构命令时,会让你做出选择。此时我们选择1,并且给定一个默认值就好。
PS D:\project\project01> python manage.py makemigrations
You are trying to add the field 'register_time' with 'auto_now_add=True' to user without a default; the database needs something to populate existing rows.
1) Provide a one-off default now (will be set on all existing rows) # 1.新增一个默认值
2) Quit, and let me add a default in models.py # 2.退出,自己手动添加default参数设置默认值
Select an option: 1
Please enter the default value now, as valid Python
You can accept the default 'timezone.now' by pressing 'Enter' or you can provide another value.
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
[default: timezone.now] >>> "2020-01-28" # 选择新增,给定一个时间
Migrations for 'app01':
app01\migrations\0002_user_register_time.py
- Add field register_time to user
PS D:\project\project01>
记得最后运行数据库同步
python manage.py migrate
返回信息
其实在创建数据表时,我们都会为其加上__str__
方法。
这是为了方便查询操作时看到那一条数据。
如下,如果不加的话返回结果是QuerySet
中套上列表+对象,十分不利于查看信息。
from app01 import models
res = models.User.objects.all()
print(res) # <QuerySet [<User: User object>]>
如果我们加上__str__
方法,那么就方便我们进行查看,到底查询的有那些记录。
(只要不对模型表本身结构做出修改,都不用重新执行模型表和物理表的两条命令)
from django.db import models
class User(models.Model): # 必须继承
# 自动创建 id 的主键
username = models.CharField(max_length=16,verbose_name="用户名",db_index=True)
age = models.IntegerField()
gender = models.BooleanField(choices=([0,"male"],[1,"famale"]),default=0)
register_time = models.DateField(auto_now_add=True)
def __str__(self) -> str:
return "对象-%s"%self.username
# 每一个记录对象都是User的实例化对象,所以我们返回一下self的username即可
现在再来进行查询
from app01 import models
res = models.User.objects.all()
print(res) # <QuerySet [<User: 对象-云崖>]>
记录操作
新增记录
新增记录语法有两种,均可使用。
models.表名.objects.create(col='xxx', col='xxx') # 注意,返回值是创建好的对象记录本身
obj = models.表名(col='xxx', col='xxx')
obj.save()
每次的创建,都会返回一个创建成功的新对象。一个实例对象中包含字段及字段数据,换而言之就是一条记录。
示例演示:
from app01 import models
# 方式一:推荐使用
obj1 = models.User.objects.create(
# 主键自动添加,AUTOFILED
username="及时雨",
age=21,
gender=0,
# register_time 创建时自动添加
)
# 方式二:
obj2 = models.User(
# 主键自动添加,AUTOFILED
username="玉麒麟",
age=22,
gender=0,
# register_time 创建时自动添加
)
obj2.save()
print(obj1, obj2) # 对象-及时雨 对象-玉麒麟
修改记录
修改记录有两种方式,推荐使用第一种,因为第二种修改会将该条记录的所有字段都进行修改,无论数据是否有更新。
models.表名.objects.filter(col='旧数据').update(col='新数据')
# filter相当于where,不可以使用get,因为单一对象没有update方法,只有QuerySet对象才有。
obj = models.表名.objects.get(col='旧数据') # get() 只获取单个对象
obj.col = '新数据'
obj.save()
对于方式1而言,它可能修改多条记录。所以每次修改后,都会返回一个
int
类型的数字,这代表受到影响的行数
示例演示:
from app01 import models
# 方式一:推荐使用
num1 = models.User.objects.filter(pk=1).update(username="宋公明")
# 方式二:
obj = models.User.objects.get(pk=2)
obj.username="卢俊义"
obj.save()
print(num1) # 1
删除记录
语法介绍:
models.表名.objects.filter(col='xxx').delete()
# 删除指定条件的数据 filter相当于where,可以使用get,这代表仅仅删除一条记录。
返回值是一个元组,包含删除成功的行数。
示例如下
from app01 import models
num_obj = models.User.objects.filter(pk=2).delete()
print(num_obj) # (1, {'app01.User': 1})
批量增加
如果要插入的数据量很大,则可以使用bulk_create
来进行批量插入。
它比单纯的create
效率更高。
book_list = []
for i in range(100000):
book_obj = models.Book(title="第%s本书"%i)
book_list.append(book_obj)
models.Book.objects.bulk_create(book_list)
单表查询
truncate app01_user;
INSERT INTO app01_user(username, age, gender,register_time) VALUES
("云崖",18,0,now()),
("及时雨",21,0,now()),
("玉麒麟",22,0,now()),
("智多星",21,0,now()),
("入云龙",23,0,now()),
("大刀",22,0,now());
QuerySET
在查询时候,一定要区分开QuerySet
对象与单条记录对象的区别。
比如:我们的单条记录对象中保存有字段名等数据,均可进行.
出来。它的格式应该是这样的:obj[col1,col2,col3]
而QuerySet
对象是一个对象集合体,中间包含很多单条记录对象,它的格式是:QuerySet<[obj1,obj2,obj3]>
。
不同的对象有不同的方法,单条记录对象中能.
出字段,而QuerySet
对象中能点出filter()/values()
等方法。所以一定要区分开。
QuerySet
集合对象:暂时可以理解为一个列表,里面可以包含很多记录对象,但是不支持负向的索引取值,并且Django
不太希望你使用index
进行取值,而应该使用first()
以及last()
进行取出单条记录对象。
基本查询
语法介绍:
models.表名.objects.all() # 拿到当前模型类映射表中的所有记录,返回QuerySet集合对象
models.表名.objects.filter(col='xxx') # 相当于whlie条件过滤,可拿到多条,返回QuerySet集合对象
models.表名.objects.get(col='xxx') # 返回单个记录对象。注意区分与QuerySet的区别
示例演示:(仔细看结果,区分QuerySet
与单个对象的区别)
from app01 import models
all_queryset = models.User.objects.all()
filter_queryset = models.User.objects.filter(pk__gt=3)
obj = models.User.objects.get(pk=1)
print(all_queryset)
# <QuerySet [<User: 对象-云崖>, <User: 对象-及时雨>, <User: 对象-玉麒麟>, <User: 对象-智多星>, <User: 对象-入云龙>, <User: 对象-大刀>]>
print(filter_queryset)
# <QuerySet [<User: 对象-智多星>, <User: 对象-入云龙>, <User: 对象-大刀>]>
print(obj)
# 对象-云崖
filter过滤
filter()
相当于where
条件,那么在其中可以进行过滤操作
过滤使用
__
双下划线进行操作。注意:在
filter()
中,所有的条件都是AND
关系
条件 | 说明 |
---|---|
filter(col1=xxx,col2=xxx) | 查询符合多个条件的记录 |
col__lt = xxx | 字段数据小于xxx的记录 |
col__lte = xxx | 字段数据小于等于xxx的记录 |
col_gt = xxx | 字段数据大于xxx的记录 |
col_gte = xxx | 字段数据大于xxx的记录 |
col_in = [x,y,z] | 字段数据在[x,y,z]中的记录 |
col_range = [1,5] | 字段数据在1-5范围内的记录 |
col_startswith = "xxx" | 字段数据以"xxx"开头的记录,区分大小写 |
col_istartswith = "xxx" | 字段数据以"xxx"开头的记录,不区分大小写 |
col_endswith = "xxx" | 字段数据以"xxx"结尾的记录,区分大小写 |
col_iendswith = "xxx" | 字段数据以"xxx"结尾的记录,不区分大小写 |
col__contains = ”xxx" | 字段数据包含"xxx"的记录,区分大小写 |
col__icontains = ”xxx" | 字段数据包含"xxx"的记录,不区分大小写 |
col__year = 2020 | 日期字段在2020年中的记录 |
col__year__lt = 2020 | 日期字段在2020年之后的记录 |
col__date__gte = datetime.date(2017, 1, 1) | 日期字段在2017年1月1日之后的记录 |
col__month = 3 | 日期字段在3月的记录 |
col__day = 3 | 日期字段在3天的记录 |
col__hour = 3 | 日期字段在3小时的记录 |
col__minute = 3 | 日期字段在3分钟的记录 |
col__second = 3 | 日期字段在3秒钟的记录 |
User.objects.filter(pk__lt=2) # 查找id小于2的对象
User.objects.filter(pk__lte=2) # 查找id小于等于2的对象
User.objects.filter(pk__gt=2) # 查找id大于2的对象
User.objects.filter(pk__gte=2) # 查找id大于等于2的对象
User.objects.filter(pk__in=[1,2,5]) # 查找id为1,2,5的对象。
User.objects.filter(pk__range=[1,5]) # 查找id为1-5之间的对象。(左右都为闭区间)
User.objects.filter(name__contains="shawn") # 查找name中包含shawn的对象。类似于正则/%shawn%/
User.objects.filter(name__icontains="shawn") # 查找name中包含shawn的对象。(忽略大小写)
User.objects.filter(name__startswith="shawn") # 查找name以shawn开头的对象
User.objects.filter(name__istartswith="shawn") # 查找name以shawn开头的对象(忽略大小写)
User.objects.filter(name__endswith="shawn") # 查找name以shawn结尾的对象
User.objects.filter(name__iendswith="shawn") # 查找name以shawn结尾的对象 (忽略大小写)
User.objects.exclude(name__contains="shawn") # 查找name不包含shawn的所有对象。
xx__date类型字段可以根据年月日进行过滤
User.objects.filter(birthday__year=2012) # 查找出生在2012年的对象
User.objects.filter(birthday__year__gte=2012) # 查找出生在2012年之后对象
User.objects.filter(birthday__date__gte=datetime.date(2017, 1, 1)) # 查找在2017,1,1之后出生的对象
User.objects.filter(birthday__month=3) 查找出生在3月份的对象
User.objects.filter(birthday__week_day=3)
User.objects.filter(birthday__day=3)
User.objects.filter(birthday__hour=3)
User.objects.filter(birthday__minute=3)
User.objects.filter(birthday__second=3)
常用操作
下面将例举一些常用操作。
单词 | 查询操作 | 描述 |
---|---|---|
all() | models.表名.objects.all() | <QuerySet [obj obj obj]>,相当于查询该表所有记录。返回值相当于列表套对象 |
filter() | models.表名.objects.filter(col=xxx) | <QuerySet [obj obj objJ]>,过滤条件可通过逗号分割,等同于where查询。返回值相当于列表套对象 |
get() | models.表名.objects.get(col=xxx) | obj,单条记录,即一个类的实例化对象,直接返回对象 |
以下方法多为配合第一个或第二个方法使用 | ||
values() | models.表名.objects.values("col") | <QerySet [{col,x1},{col,x2}]>,返回记录中所有字段的值。返回值相当于列表套字典 |
values_list() | models.表名.objects.values_list("col") | <QerySet [(x1),(x2)]>,返回记录中所有字段的值。返回值相当于列表套元组 |
distinct() | models.表名.objects.values("col").distinct() | <QuerySet [{col,x1},{col,x2}]>,对指定字段去重。返回记录中所有字段的值。返回值相当于列表套字典 |
order_by() | models.类名.objects.order_by("-col") | <QuerySet [obj obj objJ]>,对指定字段排序,如直接写col是升序,-col则是降序。 |
reverse() | models.类名.objects.order_by("-col").reverse() | <QuerySet [obj obj obj]>,前置条件必须有order_by(col),对其进行反转操作 |
exclude() | models.表名.objects.exclude(col="xxx") | <QuerySet [obj obj obj]>,除开col=xxx的记录,其他记录都拿 |
count() | models.表名.objects.values("col").count() | int,返回该字段记录的个数 |
exists() | models.表名.objects.filter(col=“data”).exists() | bool,返回该记录是否存在 |
first() | models.表名.objects.first() | obj,单条记录,取QuerySet中第一条记录 |
last() | models.表名.objects.last() | obj,单条记录,取QuerySet中最后一条记录 |
注意:获取all()后想配合其他的一些方法,可直接使用models.类型.object.其他方法()。这等同于all() |
示例演示如下:
、
from app01 import models
values_queryset_dict = models.User.objects.values("username")
print(values_queryset_dict)
# <QuerySet [{'username': '云崖'}, {'username': '入云龙'}, {'username': '及时雨'}, {'username': '大刀'}, {'username': '智多星'}, {'username': '玉麒麟'}]>
values_list_queryset_tuple = models.User.objects.values_list("username")
print(values_list_queryset_tuple)
# <QuerySet [('云崖',), ('入云龙',), ('及时雨',), ('大刀',), ('智多星',), ('玉麒麟',)]>
distict_queryset = models.User.objects.values("age").distinct()
print(distict_queryset)
# <QuerySet [{'age': 18}, {'age': 21}, {'age': 22}, {'age': 23}]>
order_by_queryset = models.User.objects.order_by("-age")
print(order_by_queryset)
# <QuerySet [<User: 对象-入云龙>, <User: 对象-玉麒麟>, <User: 对象-大刀>, <User: 对象-及时雨>, <User: 对象-智多星>, <User: 对象-云崖>]>
reverse_queryset = models.User.objects.order_by("-age").reverse()
print(reverse_queryset)
# <QuerySet [<User: 对象-云崖>, <User: 对象-及时雨>, <User: 对象-智多星>, <User: 对象-玉麒麟>, <User: 对象-大刀>, <User: 对象-入云龙>]>
exclude_queryset = models.User.objects.exclude(pk__gt=3)
print(exclude_queryset)
# <QuerySet [<User: 对象-云崖>, <User: 对象-及时雨>, <User: 对象-玉麒麟>]>
num_pk = models.User.objects.values("pk").count()
print(num_pk)
# 6
bool_res = models.User.objects.filter(username="云崖").exists()
print(bool_res)
# True
first_obj = models.User.objects.first()
print(first_obj)
# 对象-云崖
last_obj = models.User.objects.last()
print(last_obj)
# 对象-大刀
多表关系
Django
中如果要实现多表查询,必须要建立外键,因为Django
的多表查询是建立在外键关系基础之上的。
但是如果使用原生的SQL
命令时我并不推荐使用外键做关联,因为这样会使得表与表之间的耦合度增加。
值得一提的是Django
中多对多关系创建则只需要两张表即可,因为它会自动创建第三张表(当然也可以手动创建,有两种手动创建的方式,下面也会进行介绍)。
一对一
作者与作者详情是一对一
关键字:OneToOneField
注意事项如下:
1.如不指定关联字段,自动关联主键
id
字段2.关联过后从表的关联字段会自动加上
_id
后缀3.一对一关系建立在任何一方均可,但是建议建立在查询使用多的一方,如作者一方
class Author(models.Model):
name = models.CharField(max_length=16, default="0")
age = models.IntegerField()
# 作者与作者详情一对一
author_detail = models.OneToOneField(
to="Authordetail", on_delete=models.CASCADE)
def __str__(self) -> str:
return "对象-%s" % self.name
class Authordetail(models.Model):
phone = models.BigIntegerField()
# 手机号,用BigIntegerField,
def __str__(self) -> str:
return "对象-%s" % self.phone
查看一对一关系(注意,外键上加了一个UNIQUE
约束):
desc app01_author;
+------------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| age | int(11) | NO | | NULL | |
| author_detail_id | int(11) | NO | UNI | NULL | |
| name | varchar(16) | NO | | NULL | |
+------------------+-------------+------+-----+---------+----------------+
一对多
书籍与出版社是一对多
关键字:ForeignKey
注意事项如下:
1.如不指定关联字段,自动关联主键
id
字段2.关联过后从表的关联字段会自动加上
_id
后缀3.一对多关系的
fk
应该建立在多的一方
class Publish(models.Model):
name = models.CharField(max_length=16)
addr = models.CharField(max_length=64)
email = models.EmailField()
def __str__(self) -> str:
return "对象-%s" % self.name
class Book(models.Model):
title = models.CharField(max_length=16)
price = models.DecimalField(max_digits=5, decimal_places=2)
publish_date = models.DateField(auto_now=False, auto_now_add=True)
publish = models.ForeignKey(to="Publish", on_delete=models.CASCADE)
def __str__(self) -> str:
return "对象-%s" % self.title
查看一对多关系:
decs app01_book;
+--------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| title | varchar(16) | NO | | NULL | |
| price | decimal(5,2) | NO | | NULL | |
| publish_date | date | NO | | NULL | |
| publish_id | int(11) | NO | MUL | NULL | |
+--------------+--------------+------+-----+---------+----------------+
多对多
书籍与作者是多对多
关键字:ManyToManyFiled
1.
Django
使用ManyToManyFiled
会自动创建第三张表,而该字段就相当于指向第三张表,并且第三张表默认关联其他两张表的pk
字段2.多对多关系建立在任何一方均可,但是建议建立在查询使用多的一方
3.多对多没有级联操作
class Book(models.Model):
title = models.CharField(max_length=16)
price = models.DecimalField(max_digits=5, decimal_places=2)
publish_date = models.DateField(auto_now=False, auto_now_add=True)
publish = models.ForeignKey(to="Publish", on_delete=models.CASCADE)
# 书籍和出版社是一对多/多对一
authors = models.ManyToManyField("Author")
# 书籍与作者是多对多关系
def __str__(self) -> str:
return "对象-%s" % self.title
查看多对多关系,即第三张表:
desc app01_book_authors;
+-----------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| book_id | int(11) | NO | MUL | NULL | |
| author_id | int(11) | NO | MUL | NULL | |
+-----------+---------+------+-----+---------+----------------+
自关联
自关联是一个很特殊的关系,如评论表。
一个文章下可以有很多评论,这些评论又分为根评论和子评论,那么这个时候就可以使用自关联建立关系。
#评论表
class Comment(models.Model):
#评论的内容字段
content=models.CharField(max_length=255)
#评论的发布时间
push_time=models.DateTimeField(auto_now_add=True)
#关联父评论的id,可以为空,一定要设置null=True
pcomment = models.ForeignKey(to='self',null=True)
def __str__(self):
return self.content
级联操作
一对一,一对多关系均可使用级联操作。
在Django
中,默认会开启级联更新,我们可自行指定级联删除on_delete
的操作方式。
级联删除选项 | 描述 |
---|---|
models.CASCADE | 删除主表数据时,从表关联数据也将进行删除 |
models.SET | 删除主表数据时,与之关联的值设置为指定值,设置:models.SET(值),或者运行一个可执行对象(函数)。 |
models.SET_NUL | 删除主表数据时,从表与之关联的值设置为null(前提FK字段需要设置为可空) |
models.SET_DEFAULT | 删除主表数据时,从表与之关联的值设置为默认值(前提FK字段需要设置默认值) |
models.DO_NOTHING | 删除主表数据时,如果从表中有与之关联的数据,引发错误IntegrityError |
models.PROTECT | 删除主表数据时,如果从表中有与之关联的数据,引发错误ProtectedError |
操作演示:
publish = models.ForeignKey(to="Publish",on_delete=models.CASCADE)
# 书籍和出版社是一对多/多对一
全部代码
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=16,default="0")
age = models.IntegerField()
# 作者与作者详情一对一
author_detail = models.OneToOneField(to="Authordetail",on_delete=models.CASCADE)
def __str__(self) -> str:
return "对象-%s" % self.name
class Authordetail(models.Model):
phone = models.BigIntegerField()
# 手机号,用BigIntegerField,
def __str__(self) -> str:
return "对象-%s" % self.phone
class Publish(models.Model):
name = models.CharField(max_length=16)
addr = models.CharField(max_length=64)
email = models.EmailField()
def __str__(self) -> str:
return "对象-%s" % self.name
class Book(models.Model):
title = models.CharField(max_length=16)
price = models.DecimalField(max_digits=5, decimal_places=2)
publish_date = models.DateField(auto_now=False, auto_now_add=True)
publish = models.ForeignKey(to="Publish",on_delete=models.CASCADE)
# 书籍和出版社是一对多/多对一
authors = models.ManyToManyField("Author")
# 书籍与作者是多对多关系
def __str__(self) -> str:
return "对象-%s" % self.title
多表操作
一对多
新增记录
对于增加操作而言有两种
1.使用
fk_id
进行增加,添加另一张表的id
号(因为外键会自动添加_id
前缀,所以我们可以直接使用)2.使用
fk
进行增加,添加另一张表的具体记录对象
第一种:
models.表名.objects.create(
col = xxx,
col = xxx,
外键_id = int,
)
第二种:
# 先获取对象
obj = models.表名.objects.get(col=xxx)
models.表名.objects.create(
col = xxx,
col = xxx,
外键 = obj,
)
示例操作:
from app01 import models
# 添加出版社
models.Publish.objects.create(
# id自动为1
name="北京出版社",
addr="北京市海淀区",
email="BeiJingPublish@gmail.com",
)
models.Publish.objects.create(
# id自动为2
name="上海出版社",
addr="上海市蒲东区",
email="ShangHaiPublish@gmail.com",
)
# 第一种:直接使用fk_id,添加id号
models.Book.objects.create(
title="Django入门",
price=99.50,
# auto_now_add,不用填
publish_id=1, # 填入出版设id号
)
# 第二种,使用fk,添加对象
# 2.1 先获取出版社对象
pub_obj = models.Publish.objects.get(pk=1)
# 2.2 添加对象
models.Book.objects.create(
title="CSS攻略",
price=81.50,
# auto_now_add,不用填
publish=pub_obj, # 填入出版社对象
)
修改记录
修改记录和增加记录一样,都是有两种
1.使用
fk_id
进行修改,改为另一张表的id
号(因为外键会自动添加_id
前缀,所以我们可以直接使用)2.使用
fk
进行修改,改为另一张表的具体记录对象
第一种:
models.表名.objects.filter(col=xxx).update(外键_id=int)
第二种:
# 先获取对象
obj = models.表名.objects.get(col=xxx)
models.表名.objects.filter(col=xxx).update(外键=obj)
示例操作:
from app01 import models
# 第一种,直接使用fk_id # 将第一本书改为上海出版社 # 注意,这里只能使用filter,因为QuerySet对象才具有update方法
models.Book.objects.filter(pk=1).update(publish_id=2,)
# 第二种,获取出版设对象,使用fk进行修改 # 将第二本书改为北京出版社
# 2.1获取出版设对象
pub_obj = models.Publish.objects.get(pk=2)
# 2.2使用fk进行修改
models.Book.objects.filter(pk=2).update(publish=pub_obj) # 注意,这里只能使用filter,因为QuerySet对象才具有update方法
删除记录
如果删除一个出版社对象,与其关联的所有书籍都将会被级联删除。
models.Publish.objects.filter(pk=1).delete()
多对多
多对多的外键,相当于第三张表,必须要拿到第三张表才能进行操作。
这一点只要能理解,那么对多对多的操作就会十分便捷。
增加记录
增加记录也是两种操作,拿到第三张表后可以增加对象,也可以增加id
。
注意:可以一次添加一个对象或
fk_id
,也可以添加多个
第一种:
obj = models.表名.objects.get(col=xxx)
obj.外键.add(id_1, id_2) # 可以增加一个,也可以增加多个
第二种:
# 先获取对象
obj1 = models.表名.objects.get(col=xxx)
obj2 = models.表名.objects.get(col=xxx)
# 拿到第三张表
obj = models.表名.objects.get(col=xxx)
obj.外键.add(obj1, obj2) # 可以增加一个,也可以增加多个
# obj.外键 就是第三张表
示例操作:
from app01 import models
# 创建作者详细
a1 = models.Authordetail.objects.create(
phone=12345678911
)
a2 = models.Authordetail.objects.create(
phone=15196317676
)
# 创建作者
models.Author.objects.create(
name="云崖",
age=18,
author_detail=a1, # a1是创建的记录对象本身
)
models.Author.objects.create(
name="杰克",
age=18,
author_detail=a2, # a2是创建的记录对象本身
)
# 为书籍添加作者
# 方式1 先拿到具体对象,通过外键字段拿到第三张表,添加作者的id
# 拿到书籍,通过书籍外键字段拿到第三张表(必须是具体对象,不是QuerySet)
book_obj = models.Book.objects.get(pk=1)
book_obj.authors.add(1, 2) # 为第一部书添加两个作者
# 方式2 先拿到作者的对象,再拿到第三张表,添加作者对象
author_1 = models.Author.objects.get(name="云崖")
author_2 = models.Author.objects.get(name="杰克")
# 拿到书籍,通过书籍外键字段拿到第三张表(必须是具体对象,不是QuerySet)
book_obj = models.Book.objects.get(pk=2)
book_obj.authors.add(author_1, author_2) # 为第一部书添加两个作者
修改记录
修改记录也是拿到第三张表进行修改,可以修改对象,也可以修改id
。
注意:可以一次设置一个对象或
id
,也可以添加多个,但是要使用[]
进行包裹
第一种:
obj = models.表名.objects.get(col=xxx)
book_obj.authors.set([1]) # 注意[]包裹,可以设置一个,也可以设置多个
第二种:
# 先获取对象
obj1 = models.表名.objects.get(col=xxx)
obj2 = models.表名.objects.get(col=xxx)
# 拿到第三张表
obj = models.表名.objects.get(col=xxx)
obj.表名.set([obj1, obj2]) # 注意[]包裹,可以设置一个,也可以设置多个
# obj.外键 就是第三张表
示例操作:
from app01 import models
# 用id进行设置
book_obj = models.Book.objects.get(pk=1)
book_obj.authors.set([1]) # 拿到第三张表,放入作者的id进行设置。注意[]包裹,可以设置一个,也可以设置多个
# 用对象进行设置
author_obj = models.Author.objects.first()
# 先获取作者对象
book_obj = models.Book.objects.get(pk=2)
book_obj.authors.set([author_obj]) # 拿出第三张表,放入作者对象,注意[]包裹,可以设置一个,也可以设置多个
删除记录
直接拿到第三张表进行删除即可。可以删除对象,也可以删除id
注意:可以一次性删除单个,也可以一次性删除多个
第一种:
obj = models.表名.objects.get(col=xxx)
obj.外键.remove(id_1,id_2) # 可以删除一个,也可以设置多个
第二种:
# 先获取对象
obj1 = models.表名.objects.get(col=xxx)
obj2 = models.表名.objects.get(col=xxx)
# 拿到第三张表
obj = models.表名.objects.get(col=xxx)
obj.外键.remove(obj1, obj2) # 可以删除一个,也可以设置多个
# obj.外键 就是第三张表
示例操作:
from app01 import models
# 用id进行删除
book_obj = models.Book.objects.get(pk=1)
book_obj.authors.remove(1) # 拿到第三张表,放入作者的id进行删除。可以删除一个,也可以删除多个
# 用对象进行删除
author_obj = models.Author.objects.last()
# 先获取作者对象
book_obj = models.Book.objects.get(pk=2)
book_obj.authors.remove(author_obj) # 拿出第三张表,放入作者对象,可以删除一个,也可以删除多个
清空记录
拿到第三张表,执行clear()
则是清空操作。
book_obj = models.Book.objects.get(pk=1) # 拿到具体对象
book_obj.authors.clear() # 删除其所有作者
多表查询
数据准备
还是用多表操作中的结构,数据有一些不同。
书籍与出版社是一对多
书籍与作者是多对多
作者与作者详情是一对一
select * from app01_publish; -- 出版社
+----+-----------------+-----------------------------+--------------------+
| id | name | addr | email |
+----+-----------------+-----------------------------+--------------------+
| 1 | 北京出版社 | 北京市海淀区 | BeiJing@gmail.com |
| 2 | 上海出版社 | 上海浦东区 | ShangHai@gmail.com |
| 3 | 西藏出版社 | 乌鲁木齐其其格乐区 | XiZang@gmail.com |
+----+-----------------+-----------------------------+--------------------+
select * from app01_author; -- 作者
+----+--------------+-----+------------------+
| id | name | age | author_detail_id |
+----+--------------+-----+------------------+
| 1 | 云崖 | 18 | 1 |
| 2 | 浪里白条 | 17 | 2 |
| 3 | 及时雨 | 21 | 3 |
| 4 | 玉麒麟 | 22 | 4 |
| 5 | 入云龙 | 21 | 5 |
+----+--------------+-----+------------------+
select * from app01_book; -- 书籍
+----+-------------------+--------+--------------+------------+
| id | title | price | publish_date | publish_id |
+----+-------------------+--------+--------------+------------+
| 1 | Django精讲 | 99.23 | 2020-09-11 | 1 |
| 2 | HTML入门 | 78.34 | 2020-09-07 | 2 |
| 3 | CSS入门 | 128.00 | 2020-07-15 | 3 |
| 4 | Js精讲 | 152.00 | 2020-06-09 | 1 |
| 5 | Python入门 | 18.00 | 2020-08-12 | 1 |
| 6 | PHP全套 | 73.53 | 2020-09-15 | 2 |
| 7 | GO入门到精通 | 166.00 | 2020-07-14 | 3 |
| 8 | JAVA入门 | 123.00 | 2020-04-22 | 2 |
| 9 | C语言入门 | 19.00 | 2020-02-03 | 3 |
| 10 | FLASK源码分析 | 225.00 | 2019-11-06 | 1 |
+----+-------------------+--------+--------------+------------+
select * from app01_book_authors; -- 作者书籍关系
+----+---------+-----------+
| id | book_id | author_id |
+----+---------+-----------+
| 1 | 1 | 1 |
| 2 | 2 | 1 |
| 3 | 2 | 2 |
| 4 | 2 | 3 |
| 5 | 2 | 5 |
| 6 | 3 | 4 |
| 7 | 3 | 5 |
| 8 | 4 | 2 |
| 9 | 5 | 2 |
| 10 | 6 | 2 |
| 11 | 7 | 1 |
| 12 | 7 | 2 |
| 13 | 7 | 3 |
| 14 | 8 | 4 |
| 15 | 9 | 1 |
| 16 | 9 | 5 |
| 17 | 10 | 5 |
+----+---------+-----------+
select * from app01_authordetail; -- 作者详情
+----+-------------+
| id | phone |
+----+-------------+
| 1 | 15163726474 |
| 2 | 17738234753 |
| 3 | 15327787382 |
| 4 | 13981080124 |
| 5 | 13273837482 |
+----+-------------+
正反向
正反向的概念就是看外键建立在那张表之上。
book(fk) ---> publish : 正向 publish ---> book(fk) : 反向
切记一句话:
正向查询用外键
反向查询用
表名_set
对象跨表
使用对象跨表语法如下:
正向:
book_obj = models.书籍.objects.get(col=xxx)
publish_obj = book_obj.fk_出版社 # 这里publish_obj就是与book_obj相关的出版社了
publish_obj.all()/.filter(col=xxx)/.get(col_xxx)
反向:
publish_obj = models.出版社.objects.get(col=xxx)
book_obj = publish_obj.book_set # 这里book_obj就是与publish_obj相关的书籍了
book_obj.all()/.filter(col=xxx)/.get(col_xxx)
查找id
为1的出版社出版的所有书籍,反向
from app01 import models
publish = models.Publish.objects.get(pk=1)
book_queryset = publish.book_set
res = book_queryset.all()
print(res)
# <QuerySet [<Book: 对象-Django精讲>, <Book: 对象-Js精讲>, <Book: 对象-Python入门>, <Book: 对象-FLASK源码分析>]>
查找id
为2的书籍作者,正向
from app01 import models
book = models.Book.objects.get(pk=2)
authors_queryset = book.authors
res = authors_queryset.all()
print(res)
双下跨表
双下划线也可以进行正向或者反向的跨表,并且支持跨多张表。
注意:双下划线能在
filter()
中使用,也能在values()
中使用。拿对象用filter()
,拿单一字段用values()
。
查找id
为1的出版社出版的所有书籍,反向,拿对象
from app01 import models
res_queryset = models.Book.objects.filter(publish__id=1)
print(res_queryset)
# <QuerySet [<Book: 对象-Django精讲>, <Book: 对象-Js精讲>, <Book: 对象-Python入门>, <Book: 对象-FLASK源码分析>]>
查找id
为2的书籍作者姓名,正向,拿字段
from app01 import models
res_queryset = models.Book.objects.filter(id=2).values("authors__name")
print(res_queryset)
# <QuerySet [{'authors__name': '云崖'}, {'authors__name': '浪里白条'}, {'authors__name': '及时雨'}, {'authors__name': '入云龙'}]>
查找入云龙出的所有书籍,正向,拿对象
from app01 import models
res_queryset = models.Book.objects.filter(authors__name="入云龙")
print(res_queryset)
# <QuerySet [<Book: 对象-HTML入门>, <Book: 对象-CSS入门>, <Book: 对象-C语言入门>, <Book: 对象-FLASK源码分析>]>
查找入云龙出的所有书籍,拿对象,反向查(这个需要配合对象跨表才行)
from app01 import models
res_queryset = models.Author.objects.filter(name="入云龙").first().book_set.all()
print(res_queryset)
# <QuerySet [<Book: 对象-HTML入门>, <Book: 对象-CSS入门>, <Book: 对象-C语言入门>, <Book: 对象-FLASK源码分析>]>
跨多表,从pk
为1的出版社中查到其所有作者的手机号,拿字段。
from app01 import models
res_queryset = models.Publish.objects.filter(pk=1).values("book__authors__author_detail__phone")
print(res_queryset)
# <QuerySet [{'book__authors__author_detail__phone': 15163726474}, {'book__authors__author_detail__phone': 17738234753}, {'book__authors__author_detail__phone': 17738234753}, {'book__authors__author_detail__phone': 13273837482}]>
聚合查询
聚合函数
使用聚合函数前应进行导入,在ORM
中,单独使用聚合查询的关键字是aggregate()
。
from django.db.models import Min,Max,Avg,Count,Sum
聚合函数应该配合分组一起使用,而不是单独使用。
单独使用一般都是对单表进行操作。
注意:单独使用聚合函数后,返回的并非一个
QuerySet
对象,这代表filter()/values()
等方法将无法继续使用。
查询书籍的平均价格:
from app01 import models
from django.db.models import Max,Min,Avg,Count,Sum
res = models.Book.objects.aggregate(Avg("price")) # 如不进行分组,则整张表为一组
print(res) # {'price__avg': 108.21} # 单独使用聚合,返回的是字典
查询最贵的书籍,以及最便宜的书籍。
from app01 import models
from django.db.models import Max, Min, Avg, Count, Sum
res = models.Book.objects.aggregate(Max("price"),Min("price"))
print(res) # {'price__max': Decimal('225.00'), 'price__min': Decimal('18.00')}
分组查询
在ORM
中,分组查询使用关键字annotate()
切记以下几点:
1.只要对表使用了
annotate()
,则按照pk
进行分组2.如果分组前指定
values()
,则按照该字段进行分组,如values("name").annotate()
3.分组函数中可以使用聚合函数,并且不同于使用
aggregate()
后的返回对象是一个字典,使用分组函数进行聚合查询后,返回依旧是一个QuerySet
。
统计每本书的作者个数
from app01 import models
from django.db.models import Max, Min, Avg, Count, Sum
# authors_num 别名
res = models.Book.objects.annotate(authors_num=Count("authors__pk"),).values('title','authors_num')
print(res)
# <QuerySet [{'title': 'Django精讲', 'authors_num': 1}, {'title': 'HTML入门', 'authors_num': 4}, {'title': 'CSS入门', 'authors_num': 2}, {'title': 'Js精讲', 'authors_num': 1}, {'title': 'Python入门', 'authors_num': 1}, {'title': 'PHP全套', 'authors_num': 1}, {'title': 'GO入门到精通', 'authors_num': 3}, {'title': 'JAVA入门', 'authors_num': 1}, {'title': 'C语言入门', 'authors_num': 2}, {'title': 'FLASK源码分析', 'authors_num': 1}]>
统计每个出版社中卖的最便宜的书籍
from app01 import models
from django.db.models import Max, Min, Avg, Count, Sum
# book_name 别名
res = models.Publish.objects.annotate(book_name=Min("book__price"),).values('name','book_name')
print(res)
# <QuerySet [{'name': '北京出版社', 'book_name': Decimal('18.00')}, {'name': '上海出版社', 'book_name': Decimal('73.53')}, {'name': '西藏出版社', 'book_name': Decimal('19.00')}]>
统计不止一个作者的图书
from app01 import models
from django.db.models import Max, Min, Avg, Count, Sum
# authors_num 别名 只要返回的QuerySet对象,就可以使用filter
res = models.Book.objects.annotate(authors_num=Count("authors__pk"),).values('title','authors_num').filter(authors_num__gt=1)
print(res)
# <QuerySet [{'title': 'HTML入门', 'authors_num': 4}, {'title': 'CSS入门', 'authors_num': 2}, {'title': 'GO入门到精通', 'authors_num': 3}, {'title': 'C语言入门', 'authors_num': 2}]>
查询每个作者出书的总价
from app01 import models
from django.db.models import Max, Min, Avg, Count, Sum
# sum_price 别名
res = models.Author.objects.annotate(sum_price=Sum("book__price"),).values('name','sum_price')
print(res)
# <QuerySet [{'name': '云崖', 'sum_price': Decimal('362.57')}, {'name': '浪里白条', 'sum_price': Decimal('487.87')}, {'name': '及时雨', 'sum_price': Decimal('244.34')}, {'name': '玉麒麟', 'sum_price': Decimal('251.00')}, {'name': '入云龙', 'sum_price': Decimal('450.34')}]>
F&Q过滤
F查询
F
查询能够拿到字段的数据。使用前应该先进行导入
form django.db.moduls import F
注意:如果要操纵的字段是字符类型,要对其进行拼接操作还需导入
Concat
进行拼接
将所有书籍的价格提升50元
from app01 import models
from django.db.models import Max, Min, Avg, Count, Sum
from django.db.models import F, Q
res = models.Book.objects.update(price=F("price")+50)
print(res) # 10
在每本书名字后面加上爆款二字(注意:操纵字符类型,需要用到Concat
与Value
函数)
from app01 import models
from django.db.models import Max, Min, Avg, Count, Sum
from django.db.models import F, Q
# 必须配合下面的两个进行使用
from django.db.models.functions import Concat
from django.db.models import Value
res = models.Book.objects.update(title=Concat(F("title"), Value("爆款")))
print(res) # 10
Q查询
在filter()
中,只要多字段筛选中用逗号进行分割都是AND
链接,如何进行OR
与NOT
呢?此时就要用到Q
,还是要先进行导入该功能。
form django.db.moduls import Q
符号 | 描述 |
---|---|
filter(Q(col=xxx),Q(col=xxx)) | AND关系,只要在filter()中用逗号分割,就是AND 关系 |
filter(Q(col=xxx)!Q(col=xxx)) | OR关系,用|号进行链接 |
filter(~Q(col=xxx)!Q(col=xxx)) | NO关系,用~号进行连接 |
查询书籍表中入云龙或云崖出版的书籍
from app01 import models
from django.db.models import Max, Min, Avg, Count, Sum
from django.db.models import F, Q
res = models.Book.objects.filter(Q(authors__name='云崖')|Q(authors__name="入云龙"))
print(res)
# <QuerySet [<Book: 对象-Django精讲爆款>, <Book: 对象-HTML入门爆款>, <Book: 对象-GO入门到精通爆款>, <Book: 对象-C语言入门爆款>, <Book: 对象-HTML入门爆款>, <Book: 对象-CSS入门爆款>, <Book: 对象-C语言入门爆款>, <Book: 对象-FLASK源码分析爆款>]>
查询书籍表中不是云崖和入云龙出版的书籍
from app01 import models
from django.db.models import Max, Min, Avg, Count, Sum
from django.db.models import F, Q
res = models.Book.objects.filter(~Q(authors__name='云崖'),Q(authors__name="入云龙"))
print(res)
# <QuerySet [<Book: 对象-CSS入门爆款>, <Book: 对象-FLASK源码分析爆款>]>
Q进阶
如果我们的Q
查询中,能去让用户来输入字符串指定字段进行查询,那么就非常厉害了。
其实这也是能够实现。
from app01 import models
from django.db.models import Max, Min, Avg, Count, Sum
from django.db.models import F, Q
q = Q() # 实例化出一个对象
q.connector = "and" # 指定q的关系
# 两个Q
q.children.append(("price__lt","120"))
q.children.append(("price__gt","50"))
res = models.Book.objects.filter(q)
# 相当于: models.Book.objects.filter(Q(price__lt=120),Q(price__gt=50))
print(res)
# <QuerySet [<Book: 对象-Python入门爆款>, <Book: 对象-C语言入门爆款>]>
查询优化
使用单表或多表时,可以使用以下方法进行查询优化。
only
only
取出的记录对象中只有指定的字段,没有其他字段。
因此查询速度会非常快。
默认会拿出所有字段。
from app01 import models
obj = models.Book.objects.all().first()
print(obj.title)
print(obj.price)
SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id` FROM `app01_book` ORDER BY `app01_book`.`id` ASC LIMIT 1;
使用only
只会拿出指定字段,但是如果要拿指定字段以外的字段。则会再次进行查询
from app01 import models
obj = models.Book.objects.only("title").first()
print(obj.title)
print(obj.price)
SELECT `app01_book`.`id`, `app01_book`.`title` FROM `app01_book` ORDER BY `app01_book`.`id` ASC LIMIT 1;
SELECT `app01_book`.`id`, `app01_book`.`price` FROM `app01_book` WHERE `app01_book`.`id` = 1;
defer
defer
与only
正好相反,拿除了指定字段以外的其他字段。
但是如果要拿指定字段的字段。则会再次进行查询
from app01 import models
obj = models.Book.objects.defer("title").first()
print(obj.title)
print(obj.price)
SELECT `app01_book`.`id`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id` FROM app01_book` ORDER BY `app01_book`.`id` ASC LIMIT 1;
SELECT `app01_book`.`id`, `app01_book`.`title` FROM `app01_book` WHERE `app01_book`.`id` = 1;
select_related
原本的跨表,尤其是使用对象跨表,都会查询两次。
但是select_related
指定连表,则只会查询一次。
注意!
select_related
中指定的连表,只能是外键名。并且不能是多对多关系
原本跨表,两次查询语句。
from app01 import models
obj = models.Book.objects.all().first()
obj_fk = obj.publish
print(obj)
print(obj_fk)
SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id` FROM `app01_book` ORDER BY `app01_book`.`id` ASC LIMIT 1;
SELECT `app01_publish`.`id`, `app01_publish`.`name`, `app01_publish`.`addr`, `app01_publish`.`email` FROM `app01_publish` WHERE `app01_publish`.`id` = 1;
如果是使用select_related
,则只需要查询一次。
from app01 import models
obj = models.Book.objects.select_related("publish").first()
obj_fk = obj.publish
print(obj)
print(obj_fk)
SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id`, `app01_publish`.`id`, `app01_publish`.`name`, `app01_publish`.`addr`, `app01_publish`.`email` FROM `app01_book` INNER JOIN `app01_publish` ON (`app01_book`.`publish_id` = `app01_publish`.`id`) ORDER BY `app01_book`.`id` ASC LIMIT 1;
prefetch_related
prefetch_related
是子查询。它会默认走两次SQL
语句,相比于select_related
,它的查询次数虽然多了一次,但是量级少了很多,不用拼两张表全部内容。
注意!
prefetch_related
中指定的连表,只能是外键名。并且不能是多对多关系
from app01 import models
obj = models.Book.objects.prefetch_related("publish").first()
obj_fk = obj.publish
print(obj)
print(obj_fk)
SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id` FROM `app01_book` ORDER BY `app01_book`.`id` ASC LIMIT 1;
SELECT `app01_publish`.`id`, `app01_publish`.`name`, `app01_publish`.`addr`, `app01_publish`.`email` FROM `app01_publish` WHERE `app01_publish`.`id` IN (1); # 注意in,只查这一条
如果要使用两张表中许多数据,则使用
select_related
拼出整表。如果连表需要的数据不多,可使用
prefetch_related
子查询。
原生SQL
如果你觉得ORM
有一些查询搞不定,就可以使用原生SQL
语句。
官方文档:https://docs.djangoproject.com/zh-hans/3.1/topics/db/sql/
connection
这和pymysql
的使用基本一致,但是并不提供返回dict
类型的数据。所以我们需要自定义一个函数来进行封装。
from app01 import models
from django.db import connection, connections
def dictfetchall(cursor):
"Return all rows from a cursor as a dict"
columns = [col[0] for col in cursor.description]
return [
dict(zip(columns, row))
for row in cursor.fetchall()
]
print(help(connection.cursor))
# 1.1 获取游标对象
cursor = connection.cursor() # 默认连接default数据库。
cursor.execute(
"""
SELECT * FROM app01_book INNER JOIN app01_publish
on app01_book.publish_id = app01_publish.id
where app01_book.id = 1;
"""
,()) #第二参数,字符串拼接。与pymysql使用相同,防止sql注入。
# 1.2 返回封装结果
row = dictfetchall(cursor)
print(row)
# [{'id': 1, 'title': 'Django精讲', 'price': Decimal('99.23'), 'publish_date': datetime.date(2020, 9, 11), 'publish_id': 1, 'name': '北京出版社', 'addr': '北京市海淀区', 'email': 'BeiJing@gmail.com'}]
raw
raw
是借用一个模型类,返回结果将返回至模型类中。
返回结果是一个
RawQuerySet
对象,这种用法必须将主键取出
from app01 import models
res_obj = models.Book.objects.raw("select id,name from app01_author",params=[]) # 必须取出其他表的主键
# params = 格式化。防止sql注入
for i in res_obj:
print(i.name) # 虽然返回的Book实例化,但是其中包含了作者的字段。可以通过属性点出来
# 云崖
# 浪里白条
# 及时雨
# 玉麒麟
# 入云龙
你可以用 raw()
的 translations
参数将查询语句中的字段映射至模型中的字段。这是一个字典,将查询语句中的字段名映射至模型中的字段名。说白了就是as
一个别名。
例如,上面的查询也能这样写:
from app01 import models
name_map = {'pk': 'aid', 'name': 'a_name',} # 不要使用id,使用pk。它知道是id
res_obj = models.Book.objects.raw("select * from app01_author", params=[],translations=name_map)
# params = 格式化。防止sql注入
for i in res_obj:
print(i.a_name)
# 云崖
# 浪里白条
# 及时雨
# 玉麒麟
# 入云龙
extra
原生与ORM
结合,这个可用于写子查询。
我没怎么用过这个,很少使用。
def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
# 构造额外的查询条件或者映射,如:子查询
Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,))
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid'])
开启事务
Django
中开启事务非常简单,因为又with()
上下文管理器的情况,所以我们只需要在没出错的时候提交,出错后回滚即可。
from django.db import transaction
def func_views(request):
try:
with transaction.atomic():
# 操作1
# 操作2
# 如果没发生异常自动提交事务
# raise DatabaseError 数据库异常,说明sql语句有问题
except DatabaseError: # 自动回滚,不需要任何操作
pass
详解QuerySet
惰性求值
QuerySet
的一大特性就是惰性求值,即你不使用数据时是不会进行数据库查询。
from app01 import models
res_queryset = models.Book.objects.all()
# 无打印SQL
缓存机制
QuerySet
具有缓存机制,下次再使用同一变量时,不会走数据库查询而是使用缓存。
from app01 import models
res_queryset = models.Book.objects.filter(pk=1)
for i in res_queryset:
print(i)
for i in res_queryset:
print(i)
# (0.001) SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id` FROM `app01_book` WHERE `app01_book`.`id` = 1; args=(1,)
# 对象-Django精讲
# 对象-Django精讲
避免脏数据
因为QuerySet
的缓存机制,所以使得可能出现脏数据。
即数据库中修改了某个数据,但是QuerySet
缓存中依旧是旧数据。
避免这种问题的解决方案有两种:
1.不要重复使用同一变量。每次使用都应该重新赋值(虽然增加查询次数,但是保证数据准确)
2.使用迭代器方法,不可重复用
from app01 import models
res_queryset = models.Book.objects.filter(pk=1)
for i in res_queryset.iterator(): # 迭代器
print(i)
for i in res_queryset: # 缓存没有,重新获取
print(i)
# (0.001) SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id` FROM `app01_book` WHERE `app01_book`.`id` = 1; args=(1,)
# 对象-Django精讲
# (0.003) SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`price`, `app01_book`.`publish_date`, `app01_book`.`publish_id` FROM `app01_book` WHERE `app01_book`.`id` = 1; args=(1,)
# 对象-Django精讲
常用字段及参数
mysql对照
Django | 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 |
DecimalField | numeric(%(max_digits)s, %(decimal_places)s) |
DurationField | bigint |
FileField | varchar(%(max_length)s) |
FilePathField | varchar(%(max_length)s) |
IntegerField | integer |
BigIntegerField | bigint |
IPAddressField | char(15) |
GenericIPAddressField | char(39) |
NullBooleanField | bool |
OneToOneField | integer |
PositiveIntegerField | nteger UNSIGNED |
SlugField | varchar(%(max_length)s) |
SmallIntegerField | smallint |
TextField | longtext |
TimeField | time |
UUIDField | char(32) |
数值类型
Django字段 | 描述 | 是否有注意事项 |
---|---|---|
AutoField | int自增列,必须填入参数 primary_key=True | 有,见描述 |
BigAutoField | bigint自增列,必须填入参数 primary_key=True | 有,见描述 |
SmallIntegerField | 小整数 -32768 ~ 32767 | |
PositiveSmallIntegerField | 正小整数 0 ~ 32767 | |
IntegerField | 整数列(有符号的) -2147483648 ~ 2147483647 | |
PositiveIntegerField | 正整数 0 ~ 2147483647 | |
BigIntegerField | 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807 | |
BooleanField | 布尔值类型 | |
NullBooleanField | 可以为空的布尔值 | |
FloatField | 浮点型 | |
DecimalField | 10进制小数 | 有,见说明 |
BinaryField | 二进制类型 |
说明补充:
DecimalField(Field)
- 10进制小数
- 参数:
max_digits,小数总长度
decimal_places,小数位所占据长度(用总长度减去小数位所占据长度,就是整数长度)
其他补充:自定义无符号整数类型
class UnsignedIntegerField(models.IntegerField): # 必须继承
def db_type(self, connection):
return 'integer UNSIGNED' # 返回真实存放的类型
字符类型
Django字段 | 描述 | 是否有注意事项 |
---|---|---|
CharField | 必须提供max_length参数, max_length表示字符长度 | 有,见描述 |
EmailField | 字符串类型,Django Admin以及ModelForm中提供验证机制 | |
IPAddressField | 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制 | |
GenericIPAddressField | 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6 | 有,见说明 |
URLField | 字符串类型,Django Admin以及ModelForm中提供验证 URL | |
SlugField | 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号) | |
CommaSeparatedIntegerField | 字符串类型,格式必须为逗号分割的数字 | |
UUIDField | 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证 | |
FilePathField | 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能 | 有,见说明 |
FileField | 字符串,路径保存在数据库,文件上传到指定目录 | 有,见说明 |
ImageField | 字符串,路径保存在数据库,文件上传到指定目录 | 有,见说明 |
说明补充:
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"
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 上传图片的宽度保存的数据库字段名(字符串)
时间类型
Django字段 | 描述 | 是否有注意事项 |
---|---|---|
DateTimeField | 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] | 有,见说明 |
DateField | 日期格式 YYYY-MM-DD | 有,见说明 |
TimeField | 时间格式 HH:MM[:ss[.uuuuuu]] | |
DurationField | 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型 |
说明补充:
时间类型的字段都有两个参数:
auto_now=False # 当记录更新时是否自动更新当前时间
auto_now_add=False # 当记录创建时是否自动更新当前时间
条件参数
条件参数 | 描述 |
---|---|
null | 数据库中字段是否可以为空,接受布尔值 |
db_column | 数据库中字段的列名,接受字符串 |
default | 数据库中字段的默认值,接受时间,数值,字符串 |
primary_key | 数据库中字段是否为主键,接受布尔值(一张表最多一个主键) |
db_index | 数据库中字段是否可以建立索引,接受布尔值 |
unique | 数据库中字段是否可以建立唯一索引,接受布尔值 |
unique_for_date | 数据库中字段【日期】部分是否可以建立唯一索引,接受布尔值 |
unique_for_month | 数据库中字段【月】部分是否可以建立唯一索引,接受布尔值 |
unique_for_year | 数据库中字段【年】部分是否可以建立唯一索引,接受布尔值 |
choices
choices
是一个非常好用的参数。它允许你数据库中存一个任意类型的值,但是需要使用时则是使用的它的描述。
如下:
gender = models.BooleanField(choices=((0,"male"),(1,"female")),default=0)
# 实际只存0和1,但是我们取的时候会取male或者female
取出方法:
get_col_display()
后端示例:
obj = models.User.objects.get(pk=1)
gender = obj.get_gender_display()
print(gender) # male
前端示例:
{{obj.get_gender_display}} <!-- 前端不用加括号,自己调用 -->
元信息
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 用这个来指定admin中指定的表名,不要用上面的。
verbose_name_plural
# 抽象表,只用于字段继承,不会生成该表至物理数据库中
abstract = True
多表关系参数
ForeignKey(ForeignObject) # ForeignObject(RelatedField)
to, # 要进行关联的表名
to_field=None, # 要关联的表中的字段名称
on_delete=None, # 当删除关联表中的数据时,当前表与其关联的行的行为
- models.CASCADE,删除关联数据,与之关联也删除
- models.DO_NOTHING,删除关联数据,引发错误IntegrityError
- models.PROTECT,删除关联数据,引发错误ProtectedError
- models.SET_NULL,删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
- models.SET_DEFAULT,删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)
- models.SET,删除关联数据,
a. 与之关联的值设置为指定值,设置:models.SET(值)
b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)
def func():
return 10
class MyModel(models.Model):
user = models.ForeignKey(
to="User",
to_field="id"
on_delete=models.SET(func),)
related_name=None, # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
related_query_name=None, # 反向操作时,使用的连接前缀,用于替换【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
limit_choices_to=None, # 在Admin或ModelForm中显示关联数据时,提供的条件:
# 如:
- limit_choices_to={'nid__gt': 5}
- limit_choices_to=lambda : {'nid__gt': 5}
from django.db.models import Q
- limit_choices_to=Q(nid__gt=10)
- limit_choices_to=Q(nid=8) | Q(nid__gt=10)
- limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
db_constraint=True # 是否在数据库中创建外键约束,重要,一般来说都会设置False,ORM依赖关系跨表查询,但我们只保留逻辑层的关系,真实表之间将不存在关系。
parent_link=False # 在Admin中是否显示关联数据
OneToOneField(ForeignKey)
to, # 要进行关联的表名
to_field=None # 要关联的表中的字段名称
on_delete=None, # 当删除关联表中的数据时,当前表与其关联的行的行为
###### 对于一对一 ######
# 1. 一对一其实就是 一对多 + 唯一索引
# 2.当两个类之间有继承关系时,默认会创建一个一对一字段
# 如下会在A表中额外增加一个c_ptr_id列且唯一:
class C(models.Model):
nid = models.AutoField(primary_key=True)
part = models.CharField(max_length=12)
class A(C):
id = models.AutoField(primary_key=True)
code = models.CharField(max_length=1)
ManyToManyField(RelatedField)
to, # 要进行关联的表名
related_name=None, # 反向操作时,使用的字段名,用于代替 【表名_set】 如: obj.表名_set.all()
related_query_name=None, # 反向操作时,使用的连接前缀,用于替换【表名】 如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
limit_choices_to=None, # 在Admin或ModelForm中显示关联数据时,提供的条件:
# 如:
- limit_choices_to={'nid__gt': 5}
- limit_choices_to=lambda : {'nid__gt': 5}
from django.db.models import Q
- limit_choices_to=Q(nid__gt=10)
- limit_choices_to=Q(nid=8) | Q(nid__gt=10)
- limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
symmetrical=None, # 仅用于多对多自关联时,symmetrical用于指定内部是否创建反向操作的字段
# 做如下操作时,不同的symmetrical会有不同的可选字段
models.BB.objects.filter(...)
# 可选字段有:code, id, m1
class BB(models.Model):
code = models.CharField(max_length=12)
m1 = models.ManyToManyField('self',symmetrical=True)
# 可选字段有: bb, code, id, m1
class BB(models.Model):
code = models.CharField(max_length=12)
m1 = models.ManyToManyField('self',symmetrical=False)
through=None, # 自定义第三张表时,使用字段用于指定关系表
through_fields=None, # 自定义第三张表时,使用字段用于指定关系表中那些字段做多对多关系表
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=50)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(
Person,
through='Membership',
through_fields=('group', 'person'),
)
class Membership(models.Model):
group = models.ForeignKey(Group, on_delete=models.CASCADE)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
inviter = models.ForeignKey(
Person,
on_delete=models.CASCADE,
related_name="membership_invites",
)
invite_reason = models.CharField(max_length=64)
db_constraint=True, # 是否在数据库中创建外键约束
db_table=None, # 默认创建第三张表时,数据库中表的名称字段以及参数
M2M创建
M2M
的创建方式有三种,分别是全自动,半自动,全手动。
全自动使用最方便,但是扩展性最差。
半自动介于全自动与全手动之间。
全手动使用最麻烦,但是扩展性最好。
全自动
自动创建的第三张表,即为全自动。
最上面多表关系中的多对多,使用的便是全自动创建。
全自动创建操纵及其方便,如add/set/remove/clear
。
半自动
手动创建第三张表,并指定此表中那两个字段是其他两张表的关联关系。
可使用__
跨表,正反向查询。
但是不可使用如add/set/remove/clear
。
在实际生产中,推荐使用半自动。它的扩展性最强
class Book(models.Model):
name = models.CharField(max_length=32)
authors = models.ManyToManyField(
to="Author", # 与作者表创建关系。
through="M2M_BookAuthor", # 使用自己创建的表
through_fields=('book','author'), # 这两个字段是关系 注意!那张表上创建多对多,就将字段放在前面
)
class Author(models.Model):
name = models.CharField(max_length=32)
# book = models.ManyToManyField(
# to="Book",
# through="M2M_BookAuthor",
# through_fields=('author','book'), Author创建,所以author放在前面
# )
class M2M_BookAuthor(models.Model):
book = models.ForeignKey(to="Book")
author = models.ForeignKey(to="Author")
# 两个fk,自动添加_id后缀。
class Meta:
unique_together = (("author", "book"),)
# 联合唯一索引
全手动
不可使用add/set/remove/clear
,以及__
跨表,正反向查询。
所有数据均手动录入。
class Book(models.Model):
name = models.CharField(max_length=32)
class Author(models.Model):
name = models.CharField(max_length=32)
class M2M_BookAuthor(models.Model):
book = models.ForeignKey(to="Book")
author = models.ForeignKey(to="Author")
# 两个fk,自动添加_id后缀。
class Meta:
unique_together = (("author", "book"),)
# 联合唯一索引