一开始使用Django的QuerySet获取数据不太习惯。简单的条件可以通过get或filter获取,但复杂的我总是用SQL语句查询得到。随着接触QuerySet越来越多,发现它相当锋利、健全。
QuerySet是Django的查询集,可以通过QuerySet条件查询得到对应模型的对象集合。
相对直接使用SQL而言,QuerySet可以防止大部分SQL注入,而且提高代码可读性。毕竟Django的模型和具体表名不太一样,需要查阅才得知表名,而且写一大串SQL代码可能直接把人看晕。假如碰到模型变动(改名、增删字段等),SQL可能又要重新调整。所以,能使用QuerySet的情况下,最好使用QuerySet。
还有重要一点,QuerySet是懒惰的。创建一个QuerySet对象,它不会直接返回数据集。等到使用它的时候,才解析该对象得到数据集。而且解析过一次会被缓存起来,下次使用时直接返回缓存中的数据,缓存的使用提高多次查询的效率。
先假设有如下模型,以供下面的QuerySet查询使用:
#coding:utf-8
from django.db import models
from django.contrib.auth.models import User
#博客模型
class Blog(models.Model):
#标题
caption = models.CharField(max_length=50)
#作者(外键关联User模型)
author = models.ForeignKey(User)
#内容
content = models.TextField()
#分类(和Tag模型关联,多对多)
tags = models.ManyToManyField(Tag,blank=True)
#发表时间
publish_time = models.DateTimeField(auto_now_add=True)
#博客分类标签
class Tag(models.Model):
#分类名
tag_name = models.CharField(max_length=20,blank=True)
1、QuerySet和SQL语句
首先讲如何将SQL语句解析成RawQuerySet对象。先让不会使用QuerySet的同学顺利使用SQL语句查询。
不熟悉SQL的可以跳过该部分,如下代码:
sql = 'select * from blog_blog' #需要查询数据库具体Blog对应表名 qs = Blog.objects.raw(sql) #将sql语句转成RawQuerySet对象
该SQL是获取全部记录,相当于QuerySet如下查询:
qs = Blog.objects.all()
以上的方法可将SQL语句转成Blog模型的查询集。
不过这个RawQuerySet和QuerySet稍微有些不同,属性和方法没有QuerySet多。例如我想知道这个查询有多少条记录。QuerySet可以直接使用count方法或者len方法获取,而RawQuerySet没有。这个也有方法处理,如下代码:
#给rawqueryset对象加上len()方法
def add_len_to_raw_query(query):
from django.db.models.query import RawQuerySet
def __len__(self):
from django.db import connection
sql = 'select count(*) from (%s) as newsql' % query.raw_query
with connection.cursor() as cursor:
cursor.execute(sql)
row = cursor.fetchone()
return row[0]
setattr(RawQuerySet, '__len__', __len__)
sql = 'select * from blog_blog'
qs = Blog.objects.raw(sql)
add_len_to_raw_query(qs) #给qs加上len()方法
print(len(qs))
我们可通过该方法设置并获取相关对象。另外,使用SQL查询,我们还可以通过SQL语句给RawQuerySet对象添加额外的属性。raw方法会解析SQL语句中的字段,将字段转成属性的方式方便调用。例如:
sql = '''
Select blog_blog.id, blog_blog.caption, count(blog_blog_tags.id) as tag_count
From blog_blog
Left join blog_blog_tags on blog_blog.id = blog_blog_tags.blog_id
Group by blog_blog.id, blog_blog.caption
'''
qs = Blog.objects.raw(sql)
print(qs[0].tag_count)
上面SQL语句是得到每篇Blog对应分类标签的个数,是不是觉得SQL很复杂了。
不过,我们可以在查询结果直接使用tag_count属性。
另外,为了方便测试,我们可以输出QuerySet对象的SQL语句:
qs = Blog.objects.filter(id=1)print(qs.query)
2、filter和get方法
上面使用了filter。filter是筛选的意思,通过filter筛选得到符合条件的数据集。
例如我分别获取如下情况的数据集。
#获取blog id为1的数据集
qs1 = Blog.objects.filter(id=1)
#获取作者user所发表的博客
user = request.user #先使用当前登录的用户判断
qs2 = Blog.objects.filter(author=user)
#多个条件用逗号隔开
qs3 = Blog.objects.filter(id=1, author=user)
Blog id为1相当于SQL:
select * from blog_blog where id = 1
这里你会发现只有一条记录,而filter返回是一个只有一条记录的数据集。获取该数据还需要进一步获取:
q = qs1[0]
这时可以使用get方法直接获取该条记录:
q = Blog.objects.get(id=1)
不过,若符合条件的没有记录或多条记录会抛出异常。我们可以利用这个特点做一些操作:
try:
q = Blog.objects.get(caption='test') #找标题为test的博客
except Blog.DoesNotExist:
q = Blog(caption='test') #创建标题为test的博客
#...
q.save()
当然,django有个get_or_create方法可以简化该过程:
#获取标题为test的博客,不存在则创建q = Blog.objects.get_or_create(caption='test')
3、不等于条件
filter筛选的条件都是符合的条件,也就是我们SQL语句中的等于。有时候,我们希望排除一些条件,也就是不等于或者not条件:
select * from blog_blog where id <> 1select * from blog_blog where not(id = 1)
这种类型的条件,需要使用exclude方法。例如:
qs = Blog.objects.exclude(id=1)
如果我要实现复杂一些的查询,获取当前登录用户发表的博客,并排除id为1的博客。查询如下:
user = request.userqs = Blog.objects.filter(author=user).exclude(id=1)
这里延伸QuerySet另一特性,可以在QuerySet上继续查询,即QuerySet支持链式查询。
4、大于和小于条件
继续延伸,既然QuerySet可以实现等于和不等于。那么大于和小于应当如何实现?
这种filter和exclude都无能为力。需要借助字段条件修饰,例如:
#查询id大于10的记录 qs1 = Blog.objects.filter(id__gt=10) #查询id大于等于10的记录 qs2 = Blog.objects.filter(id_gte=10) #排除id小于10的记录 qs3 = Blog.objects.exclude(id__lt=10) #排除id小于等于10的记录 qs4 = Blog.objects.exclude(id__lte=10)
从这里我们可以总结一下,objects查询基本就3种方法:get、filter、exclude。
条件再用字段修饰拓展,字段修饰是两个下划线加修饰码。
有个方法可以帮助记忆大于、小于的修饰码。
gt 是 greater than 的缩写,或者你熟悉html可以记忆为">",">"是大于号的转义。
gte 后面的 e 是等于equal单词的第1个字母。
lt 和 lte 是同样方法,less than 的缩写;"<"小于号的转义。
这里还有两个拓展。
1)范围查询
有时候,不单单是大于或小于。可能是一个范围,例如 1<=id<=9。你可以用两个条件组合。
qs = Blog.objects.filter(id__gte=1).filter(id__lte=9)
或者
qs = Blog.objects.filter(id__gte=1, id__lte=9)
可以使用__range范围修饰:
qs = Blog.objects.filter(id__range=[1, 9])
2)in条件查询
有时候不是简单大于或小于,需要从一个列表的值获取。in条件查询用__in修饰,例如:
#获取id为 1、3、6、7、9的记录qs = Blog.objects.filter(id__in=[1, 3, 6, 7, 9])
5、字符串模糊匹配
数值有大于小于等判断,那么字符串有模糊匹配判断的需求。例如开头是什么,结尾是什么,包含什么字符等等。这些同样需要借助字段条件修饰。例如:
#标题包含django,若忽略大小写使用__icontains qs1 = Blog.objects.filter(caption__contains='django') #标题是django开头的,若忽略大小写使用__istartswith qs2 = Blog.objects.filter(caption__startswith='django') #标题是django结尾的,若忽略大小写使用__iendswith qs2 = Blog.objects.filter(caption__endswith='django')
那"不包含"、"开头不是"、"结尾不是"用什么?当然用exclude啦。
6、日期时间处理
上面Blog模型有个发表日期publish_time字段。该字段是datetime类型。
日期时间类型要不数值、字符串复杂得多。同样使用字段条件修饰实现相应查询:
import datetime
#获取今天的datetime
now = datetime.datetime.now()
#找今年发布的博客
qs1 = Blog.objects.filter(publish_time__year=now.year)
#找本月发布的博客
qs2 = Blog.objects.filter(publish_time__year=now.year,publish_time__month=now.month)
#找到每月1号发布的博客
qs3 = Blog.objects.filter(publish_time__day=1)
我们还可以使用使用__range获取某范围内的数据:
import datetime
#获取前7天发表的博客
now = datetime.datetime.now()
end_date = datetime.datetime(now.year, now.month, now.day, 0, 0)
start_date = end_date - datetime.timedelta(7)
#找今年发布的博客
qs = Blog.objects.filter(publish_time__range=[start_date, end_date])
7、or条件
上面多个条件出现大多是and逻辑。那我可能使用or逻辑。例如我想模糊匹配标题为"django" 或 "python"。这时只能采用or逻辑处理。如下代码:
qs = Blog.objects.filter(caption__icontains='django')|Blog.objects.filter(caption__icontains='python')
若你嫌弃代码过程,可以使用Q函数:
from django.models import Q
qs = Blog.objects.filter(Q(caption__icontains='django')|Q(caption__icontains='python'))
8、limit获取前几条
有时页面显示只需要显示前10条或前20条记录。这个在SQL语句中可以用limit限制返回的记录数。同样,在QuerySet查询中也可以实现该功能。
qs = Blog.objects.all()[:10]
上面代码是获取全部博客的前10条记录,和切片一样,但索引不能为负数。
另外,这个切片器也不会让QuerySet直接执行,直到使用该查询。
9、外键查找
我上面写的示例模型,有两个外键。
假如我需要获取某一篇博客有什么分类标签,或分类标签下有哪些博客,应当如何获取?
获取某一篇博客有什么分类标签,由于Blog有个tags外键关联Tag模型。可直接获取:
blog = Blog.objects.all()[0]tags = blog.tags.all() #获取第1篇博客全部标签
而分类标签没有直接写一个外键和Blog模型关联,但可以用set通过tag获取全部blog:
tag = Tag.objects.all()[0]blogs = tag.blog_set.all() #获取第1个分类标签全部博客
django默认情况下每一个主表的对象都有一个是外键的属性,可以通过它查询到所有关于子表的信息,这个属性的名字就是子表的名称小写加上_set。
使用related_name属性定义名称(related_name是关联对象反向引用描述符)。
在model显示使用related_name就不能使用表名_set这种方式
10、排序
SQL语句可以使用Order by排序。QuerySet查询也有order_by与之对应:
#按照日期正序排序qs1 = Blog.objects.all().order_by('publish_time')
#安装日期倒序排序(all可以省略)qs2 = Blog.objects.order_by('-publish_time')
针对哪个排序,就写该字段名称即可。默认正序排序,若加个负号表示倒序排序。
正序一般是数值由小到大,日期由远到近。
排序还有一个神奇的用法,在SQL我还没见过:随机排序。通过该功能,我们可以随机获取记录:
#随机获取前10条记录qs = Blog.objects.order_by('?')[:10]
11、其他
有些知识过于零碎,没有和上面放在一起讲。随便也罗列出来:
1)获取值为null的数据
qs = Blog.objects.filter(content__isnull=True)
2)去重
qs = Tag.objects.filter(id__in=[1, 3]).blog_set().distinct()
因为这里通过Tag获取的博客可能重复,可以用distinct去重。
3)反向排序
qs = Blog.objects.filter().reverse()
4) 返回第一条记录
qs = Blog.objects.filter().first()
5)返回最后一条记录
qs = Blog.objects.filter().last()
6)exists()
如果QuerySet包含数据,就返回True,否则返回False
7) 特殊的QuerySet
values() 返回一个可迭代的字典序列
values_list() 返回一个可迭代的元祖序列
其他查询方法还有
exact # 完全匹配
iexact # 不区分大小写的完全匹配
contains # 包含
icontains # 不区分大小写的包含
in # 被包含在给定的集合中,如 Post.objects.filter(id__in=[2, 3, 4])
gt # 大于,如 Post.objects.filter(id__gt=3)
gte # 大于或等于
lt # 小于
lte # 小于或等于
startswith # 以 xx 开头
istartswith # 不区分大小写的以 xx 开头
endswith
iendswith
range # 在范围内
date # 日期字段使用,如 Post.objects.filter(created__date=datetime.date(2020, 1, 1))
year, month, day...
isnull
regex # 匹配正则
iregex
12.鲜为人知的操作
在模型查询API不够用的情况下,我们还可以使用原始的SQL语句进行查询。
Django 提供两种方法使用原始SQL进行查询:一种是使用raw()方法,进行原始SQL查询并返回模型实例;另一种是完全避开模型层,直接执行自定义的SQL语句。
执行原生查询
raw()管理器方法用于原始的SQL查询,并返回模型的实例:
注意:raw()语法查询必须包含主键。
这个方法执行原始的SQL查询,并返回一个django.db.models.query.RawQuerySet 实例。 这个RawQuerySet 实例可以像一般的QuerySet那样,通过迭代来提供对象实例。
原生SQL还可以使用参数,注意不要自己使用字符串格式化拼接SQL语句,防止SQL注入!
直接执行自定义SQL
有时候raw()方法并不十分好用,很多情况下我们不需要将查询结果映射成模型,或者我们需要执行DELETE、 INSERT以及UPDATE操作。在这些情况下,我们可以直接访问数据库,完全避开模型层。
我们可以直接从django提供的接口中获取数据库连接,然后像使用pymysql模块一样操作数据库。
from django.db import connection, connections
cursor = connection.cursor() # cursor = connections['default'].cursor()
cursor.execute("""SELECT * from auth_user where id = %s""", [1])
ret = cursor.fetchone()
QuerySet方法大全
##################################################################
# PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET #
##################################################################
def all(self)
# 获取所有的数据对象
def filter(self, *args, **kwargs)
# 条件查询
# 条件可以是:参数,字典,Q
def exclude(self, *args, **kwargs)
# 条件查询
# 条件可以是:参数,字典,Q
def select_related(self, *fields)
性能相关:表之间进行join连表操作,一次性获取关联的数据。
总结:
1. select_related主要针一对一和多对一关系进行优化。
2. select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能。
def prefetch_related(self, *lookups)
性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。
总结:
1. 对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化。
2. prefetch_related()的优化方式是分别查询每个表,然后用Python处理他们之间的关系。
def annotate(self, *args, **kwargs)
# 用于实现聚合group by查询
from django.db.models import Count, Avg, Max, Min, Sum
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id'))
# SELECT u_id, COUNT(ui) AS `uid` FROM UserInfo GROUP BY u_id
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')).filter(uid__gt=1)
# SELECT u_id, COUNT(ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1)
# SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1
def distinct(self, *field_names)
# 用于distinct去重
models.UserInfo.objects.values('nid').distinct()
# select distinct nid from userinfo
注:只有在PostgreSQL中才能使用distinct进行去重
def order_by(self, *field_names)
# 用于排序
models.UserInfo.objects.all().order_by('-id','age')
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'])
def reverse(self):
# 倒序
models.UserInfo.objects.all().order_by('-nid').reverse()
# 注:如果存在order_by,reverse则是倒序,如果多个排序则一一倒序
def defer(self, *fields):
models.UserInfo.objects.defer('username','id')
或
models.UserInfo.objects.filter(...).defer('username','id')
#映射中排除某列数据
def only(self, *fields):
#仅取某个表中的数据
models.UserInfo.objects.only('username','id')
或
models.UserInfo.objects.filter(...).only('username','id')
def using(self, alias):
指定使用的数据库,参数为别名(setting中的设置)
##################################################
# PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
##################################################
def raw(self, raw_query, params=None, translations=None, using=None):
# 执行原生SQL
models.UserInfo.objects.raw('select * from userinfo')
# 如果SQL是其他表时,必须将名字设置为当前UserInfo对象的主键列名
models.UserInfo.objects.raw('select id as nid from 其他表')
# 为原生SQL设置参数
models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,])
# 将获取的到列名转换为指定列名
name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)
# 指定数据库
models.UserInfo.objects.raw('select * from userinfo', using="default")
################### 原生SQL ###################
from django.db import connection, connections
cursor = connection.cursor() # cursor = connections['default'].cursor()
cursor.execute("""SELECT * from auth_user where id = %s""", [1])
row = cursor.fetchone() # fetchall()/fetchmany(..)
def values(self, *fields):
# 获取每行数据为字典格式
def values_list(self, *fields, **kwargs):
# 获取每行数据为元祖
def dates(self, field_name, kind, order='ASC'):
# 根据时间进行某一部分进行去重查找并截取指定内容
# kind只能是:"year"(年), "month"(年-月), "day"(年-月-日)
# order只能是:"ASC" "DESC"
# 并获取转换后的时间
- year : 年-01-01
- month: 年-月-01
- day : 年-月-日
models.DatePlus.objects.dates('ctime','day','DESC')
def datetimes(self, field_name, kind, order='ASC', tzinfo=None):
# 根据时间进行某一部分进行去重查找并截取指定内容,将时间转换为指定时区时间
# kind只能是 "year", "month", "day", "hour", "minute", "second"
# order只能是:"ASC" "DESC"
# tzinfo时区对象
models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.UTC)
models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.timezone('Asia/Shanghai'))
"""
pip3 install pytz
import pytz
pytz.all_timezones
pytz.timezone(‘Asia/Shanghai’)
"""
def none(self):
# 空QuerySet对象
####################################
# METHODS THAT DO DATABASE QUERIES #
####################################
def aggregate(self, *args, **kwargs):
# 聚合函数,获取字典类型聚合结果
from django.db.models import Count, Avg, Max, Min, Sum
result = models.UserInfo.objects.aggregate(k=Count('u_id', distinct=True), n=Count('nid'))
===> {'k': 3, 'n': 4}
def count(self):
# 获取个数
def get(self, *args, **kwargs):
# 获取单个对象
def create(self, **kwargs):
# 创建对象
def bulk_create(self, objs, batch_size=None):
# 批量插入
# batch_size表示一次插入的个数
objs = [
models.DDD(name='r11'),
models.DDD(name='r22')
]
models.DDD.objects.bulk_create(objs, 10)
def get_or_create(self, defaults=None, **kwargs):
# 如果存在,则获取,否则,创建
# defaults 指定创建时,其他字段的值
obj, created = models.UserInfo.objects.get_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 2})
def update_or_create(self, defaults=None, **kwargs):
# 如果存在,则更新,否则,创建
# defaults 指定创建时或更新时的其他字段
obj, created = models.UserInfo.objects.update_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 1})
def first(self):
# 获取第一个
def last(self):
# 获取最后一个
def in_bulk(self, id_list=None):
# 根据主键ID进行查找
id_list = [11,21,31]
models.DDD.objects.in_bulk(id_list)
def delete(self):
# 删除
def update(self, **kwargs):
# 更新
def exists(self):
# 是否有结果
QuerySet方法大全
Django终端打印SQL语句
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True, # 开启日志打印
'level':'DEBUG',
},
}
}
部分内容转载自:http://yshblog.com/blog/157







Comments | NOTHING