4. Django orm的基本使用
django orm的基本用法:
1
2
from characters.models import Character
query = Character.objects.all()
通过直接调用模型类的objects成员的对应方法即可创建查询对象
所创建的查询对象是延迟加载的, 以此来方便进行符合查询,如:
1
Character.objects.all().order_by('level')
只有当确定读取数据的时候才会真正进行数据库查询, 如
1
2
list(query)
query[0:5]
即可以减少和服务器交互的次数, 也可以减少内存占用.
查询对象
get()
1
character = Character.objects.get(pk=1)
如果id不存在, 则会引发异常, 因此需要为其进行异常处理
1
2
3
4
5
6
from django.core.exceptions import ObjectDoesNotExist
try:
obj = Character.objects.get(pk=pk)
except ObjectDoesNotExist:
pass
filter()
1
2
# 作用于get()想通, 区别在于不需要异常处理
Character.objects.filter(pk=pk).first()
filter()方法可以接收模型类所包含字段的过滤器, 其格式为:
1
filedname__lookup=value
比如name
字段包含woo
的行,就可以写成
1
2
3
query_set = Character.objects.filter(name__contains="woo")
# __contains代表包含, __icontains代表忽略大小写包含
常用过滤器lookup
range
: 查询范围, __range=(10,20)contains
,icontains
:包含关键字in
,gt
,gte
,lt
,lte
: 在范围内, >,>=,<,<=start/endwith
:以xxx开始或结尾的,同样可以使用i
前缀忽略大小写isnull
: 空值判断regex
:正则
等等, 详细文档连线:QuerySet API
复合查询
可以向filter()传递多个关键字参数,以实现符合查询,如:
1
2
3
4
5
6
# health > 50k and ap > 10k
query_set = Character.objects.filter(health__gt=50000,
attack_power__gt=10000)
# 也可以写成
query_set = Character.objects.filter(health__gt=50000).filter(attack_power__gt=100000)
对应的SQL:
1
2
3
SELECT *
FROM `characters_character`
WHERE (`characters_character`.`attack_power` > 10000 AND `characters_character`.`health` > 50000)
使用Q
进行复合逻辑查询
django提供一个专用来处理查询条件的类Q
, 每一个Q
对象对应一个查询条件, 在多个Q
对象之间可以使用逻辑运算符进行链接, 通常用来处理OR
和NOT
关键字的查询, 如
1
2
query_set = Character.objects.filter(Q(health__gt=50000) &
Q(health__lt=70000))
对应的SQL
1
2
3
SELECT *
FROM `characters_character`
WHERE (`characters_character`.`health` > 50000 AND `characters_character`.`health` < 70000)
或者:
1
2
query_set = Character.objects.filter(Q(attack_power__lt=50000) |
~Q(health__gt=50000))
对应的SQL:
1
2
3
SELECT *
FROM `characters_character`
WHERE (`characters_character`.`attack_power` < 50000 OR NOT (`characters_character`.`health` > 50000))
使用F对象进行查询
F代表字段引用, 在查询条件为表中某个字段的值时使用, 如:
1
query_set = Character.objects.filter(attack_power__gt=F('defense'))
对应的SQL:
1
2
3
SELECT *
FROM `characters_character`
WHERE `characters_character`.`attack_power` > (`characters_character`.`defense`)
在关联字段查询时经常用到F
对象
排序
通过order_by
方法来对结果集进行排序, 直接输入字段名为正序, 添加-
为倒序, 并且可以依据多个字段进行排序, 如:
1
2
# 基于等级倒序, 按名称正序排列
query_set = Character.objects.order_by('-level', 'name')
对应的SQL
1
2
3
4
SELECT *
FROM `characters_character`
ORDER BY `characters_character`.`level` DESC,
`characters_character`.`name` ASC
reverse()
反转排序条件
分页
django中的分页十分方便, 直接使用python的切片语法即可, 如:
1
2
3
def characters_ap_ranking_list(page, page_count=10):
query_set = Character.objects.order_by('-attack_power')
return query_set[page_count*(page-1):page_count * page]
对应的SQL:
1
2
3
4
5
SELECT *
FROM `characters_character`
ORDER BY `characters_character`.`attack_power` DESC
LIMIT 10
OFFSET 20
指定数据与关联查询
通过values()
方法可以指定要进行查询的字段, 而非获取所有字段的数据以提升效率, 如
1
query_set = Character.objects.filter(id__lt=10).values('name', 'level')
对应SQL
1
2
3
4
SELECT `characters_character`.`name`,
`characters_character`.`level`
FROM `characters_character`
WHERE `characters_character`.`id` < 10
同时可以进行关联查询, 在model1中存在外键关联到model2, 则可以直接通过改外键查询到关联表的字段值,如:
1
2
3
4
5
# 其中 Character 模型存在一个外键, 对应 Club 类
# club = models.ForeignKey(Club, on_delete=models.SET_NULL, null=True)
query_set = Character.objects.filter(id__lt=10)
query_set = query_set.values('name', 'level', 'club__name')
对应SQL
1
2
3
4
5
6
7
SELECT `characters_character`.`name`,
`characters_character`.`level`,
`characters_club`.`name`
FROM `characters_character`
LEFT OUTER JOIN `characters_club`
ON (`characters_character`.`club_id` = `characters_club`.`id`)
WHERE `characters_character`.`id` < 10
values()
指定的字段名将直接作为查询结果ORM对象的字段名, 查询打印结果如下:
1
2
3
4
5
6
7
8
9
10
<QuerySet [
{'name': 'Alec Rois', 'level': 95, 'club__name': 'Harris-Towne'},
{'name': 'Fairlie Champagne', 'level': 219, 'club__name': 'Lesch-Jacobson'},
{'name': 'Gard Scargill', 'level': 218, 'club__name': 'Goldner-Kris'},
{'name': 'Bordie Domingues', 'level': 6, 'club__name': 'Harris-Towne'},
{'name': 'Renate Lamlin', 'level': 54, 'club__name': 'Huels, Wintheiser and Wisozk'},
{'name': 'Marsha Pellatt', 'level': 173, 'club__name': 'Skiles, Heathcote and Leannon'},
{'name': 'Luella Jakeway', 'level': 200, 'club__name': 'Skiles, Heathcote and Leannon'},
{'name': 'Gustavus Colliber', 'level': 80, 'club__name': 'Reinger LLC'},
{'name': 'Araldo Prosh', 'level': 193, 'club__name': 'Lesch-Jacobson'}]>
values()
方法返回的是词典集合, 而不是orm对象集合, 参数列表便是词典的key
values_list()
: 方法则是返回包含全部值的元组, 即没有字段名, 仅有数据
** only()
: 方法的效果与values()类似, 区别在于返回的不是词典, 而是模型对象, 也就具备后续操作的能力, 如果通过模型对象获取一个没有在only查询中列出的字段, 则会自动进行额外的数据库查询.
distinct去重
通过distinct()
方法可以去除重复的数据行, 如
1
query_set = Character.objects.values('level').distinct().order_by('level')
对应的SQL
1
2
3
SELECT DISTINCT `characters_character`.`level`
FROM `characters_character`
ORDER BY `characters_character`.`level` ASC
去重操作可以用来确定一个范围, 比如:
1
2
3
4
# 在人物表字段`club_id`上进行去重查询
query_set = Character.objects.values('club_id').distinct()
# 查询club表, 并以上述查询作为范围, 以获取包含任务的club
club_set = Club.objects.filter(id__in=query_set)
对应的SQL
1
2
3
4
5
6
SELECT *
FROM `characters_club`
WHERE `characters_club`.`id` IN (
SELECT DISTINCT U0.`club_id`
FROM `characters_character` U0
)
预加载数据
django在获取orm对象时, 默认只加载当前表的数据, 对于外链表的数据并不会载入, 但是依然可以通过数据模型直接进行关联查询, 如
1
2
# views.py
query_set = Character.objects.all()
在模板页面中通过orm对象获取关联表数据:
1
2
# template.html
<td></td>
此时, 代码不会出现任何问题, 但是会产生大量额外用来查询关联表的SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SELECT *
FROM `characters_character`
--由于关联查询而产生的额外sql
SELECT `characters_club`.`id`,
--fields ...
FROM `characters_club`
WHERE `characters_club`.`id` = 26
LIMIT 21
SELECT `characters_club`.`id`,
--fields ...
FROM `characters_club`
WHERE `characters_club`.`id` = 5
LIMIT 21
--...
为了解决这一问题, 可以使用预加载
1
query_set = Character.objects.select_related('club').all()
对应的SQL:
1
2
3
4
5
6
7
SELECT -- fields ...
`characters_club`.`id`,
`characters_club`.`name`,
-- fields ...
FROM `characters_character`
LEFT OUTER JOIN `characters_club`
ON (`characters_character`.`club_id` = `characters_club`.`id`)
如此渲染同样的模板便不会产生额外的sql查询了
select_related
: 用于加载多一或者一对一的关系
prefetch_related
:用于加载多对多
数据统计
通过aggreate
方法来进行数据统计
1
2
3
4
5
6
from django.db.models.aggregates import Count, Max, Min, Avg
result = Character.objects.aggregate(count=Count('name'),
avg_level=Avg('level'),
max_ap=Max('attack_power'),
min_health=Min('health'))
对应的SQL
1
2
3
4
5
SELECT COUNT(`characters_character`.`name`) AS `count`,
AVG(`characters_character`.`level`) AS `avg_level`,
MAX(`characters_character`.`attack_power`) AS `max_ap`,
MIN(`characters_character`.`health`) AS `min_health`
FROM `characters_character`
添加注解字段
可以通过annotate()
方法在结果集中加入自定义的字段,如
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from django.db.models import F, Value
# 注解字段的值不能使基本类型, 必须是表达式对象
# 可以是`Value`, `F`, `Func`, `Aggregate`
temp = Character.objects.filter(level=254).values(
'id', 'name', 'level'
).annotate(
max_level=Value(True)
).annotate(
test=Value("test")
).annotate(
club_owner_id=F('club__owner')
).annotate(
club_owner_name=F('club__owner__name')
).annotate(
ap_avg_dev=F('attack_power') - avg_attack_power['avg_ap']
)
对应的sql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
SELECT `characters_character`.`id`,
`characters_character`.`name`,
`characters_character`.`level`,
1 AS `max_level`,
'test' AS `test`,
`characters_club`.`owner_id` AS `club_owner_id`,
T3.`name` AS `club_owner_name`,
(`characters_character`.`attack_power` - 50049.574e0) AS `ap_avg_dev`
FROM `characters_character`
LEFT OUTER JOIN `characters_club`
ON (`characters_character`.`club_id` = `characters_club`.`id`)
LEFT OUTER JOIN `characters_character` T3
ON (`characters_club`.`owner_id` = T3.`id`)
WHERE `characters_character`.`level` = 254
添加数据
django的数据添加可以直接通过模型对象完成, 如:
1
2
3
4
5
p = Product()
p.name = "some product"
p.description = "description of this product..."
p.price = 0.99
p.save()
也可以通过模型类的构造函数或者使用create语句:
1
2
3
Product.objects.create(name="p1",
description="description of this product",
price=0.99)
但是通过关键字参数创建数据会存在一些问题:
- 在编码时没有代码提示,需要纯手动键入, 容易出现错误
- 在通过重命名进行重构字段时, 关键字参数不会被重命名, 导致引发异常
因此建议在任何时候都实用对象赋值的方式进行数据添加
更新数据
与添加数据的操作类似, 仅需要通过id
或者其他字段确定需要更新的数据, 然后修改其对应的数据并save()
即可, 如:
1
2
3
4
# 更新id位2的数据的name字段
p = Product(pk=2)
p.name = "update product name"
p.save()
但在实际运行中会出现问题, 因为p对象的其他字段的值皆为None
, 如果表存在非空约束的字段, 那么会直接报错, 如果没有非空约束, 那么原始数据会被更新为空.
因此, 正确的更新方式应该是优先通过ORM获取原始数据, 以保证所有的字段数据都在ORM对象之中, 然后更新其中的数据, 代码如下:
1
2
3
p = Product.objects.get(pk=2)
p.name = "update product name"
p.save()
对应的SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SELECT `playground_product`.`id`,
`playground_product`.`name`,
`playground_product`.`description`,
`playground_product`.`price`,
`playground_product`.`create_at`
FROM `playground_product`
WHERE `playground_product`.`id` = 2
LIMIT 21
UPDATE `playground_product`
SET `name` = 'update product name',
-- 以下为数据库中的原始数据
`description` = 'description of this product',
`price` = 0.99,
`create_at` = '2023-11-08 12:09:37.122549'
WHERE `playground_product`.`id` = 2
为了避免额外进行一次数据库读取的操作,可以通过update
方法直接进行更新
1
Product.objects.filter(pk=2).update(name="new name by update")
对应的SQL
1
2
3
UPDATE `playground_product`
SET `name` = 'new name by update'
WHERE `playground_product`.`id` = 2
没有前置的数据读取, 也没有额外的数据写入, 但是仅有一个问题. 就是在对字段名进行重命名重构时关键字参数不会被重构, 从而存在出现bug的风险.
删除数据
直接通过ORM对象删除或者通过查询删除皆可, 如:
1
2
3
Product(pk=1).delete()
Product.objects.filter(pk__lt=100).delete()
事务(Transection)管理
多条数据库操作一并执行, 要么全部成功, 要么全部失败
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from django.db import transaction
# 装饰函数的所有内容放入同一个事务中进行管理
@transaction.atomic()
def test_transaction():
p1 = Product()
p1.name = "new product"
p1.description = "insert in transaction"
p1.price = 1.2
p1.save()
p2 = Product(pk=3)
p2.name = "new product"
p2.description = 'already in database'
p2.price = 5
p2.save()
由于p2已经存在于数据库中, 相当于执行了update操作, 但是存在非空字段, 故而更新失败.
所以同属一个事务的p1也不会保存到数据库中
也可以对方法的部分内容进行事务管理, 如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def test_transaction():
# some code...
with transaction.atomic():
p1 = Product()
p1.name = "new product"
p1.description = "insert in transaction"
p1.price = 1.2
p1.save()
p2 = Product(pk=3)
p2.name = "new product"
p2.description = 'already in database'
p2.price = 5
p2.save()
执行原生SQL
可以通过ORM对象直接调用原生SQL语句
1
query_set = Product.objects.raw("select * from playground_product")
对应的SQL
1
2
select *
from playground_product
对于简单的SQL语句完全没有必要, Django orm可以很好的生成这些语句
但是某些情况下, 需要处理的查询比较复杂, 通过orm编写较为困难,或者容易产生性能瓶颈, 便可以直接通过原生SQL语句进行优化.
对于执行原生SQL的情况, ORM模型并非是必须的环节, 可以直接通过数据库直连来执行, 如下:
1
2
3
4
5
6
7
8
9
10
11
12
from django.db import connection
cursor = connection.cursor()
cursor.execute("select * from playground_product")
cursor.close()
# 由于cursor必须手动关闭, 通常结合with一同使用
with connection.cursor() as cursor:
# 执行sql语句
cursor.execute("select * from playground_product")
# 执行存储过程
cursor.callproc('get_product', [1, 2, 3])