目录
一、参考
Django 09 Django进阶-ORM模型-查询方法详解
二、QuerySet
从数据库中查询出来的结果一般是一个集合,这个集合叫做 QuerySet
1. 获取全部对象
all_entries = Entry.objects.all()
返回一个QuerySet对象
要访问的话,需要这样来访问:
all_entries[0].id
不能使用all_entries[0]['id']来访问
2. 条件查询
除了从数据库获取全部对象使用all之外,一般常用的查询有两种:
- filter:返回新的QuerySet,包含的对象满足给定查询参数
- exclude:返回新的QuerySet,包含的对象不满足给定的查询参数
比如:要包含获取 2006 年的博客条目(entries blog):
Entry.objects.filter(pub_date__year=2006)
也可以这样:
Entry.objects.all().filter(pub_date__year=2006)
3. 链式查询
QuerySet是支持链式查询的
Entry.objects.filter(...).exclude(...).filter(...)
比如:
# 找出名称含有abc, 但是排除年龄是23岁的
Person.objects.filter(name__contains="abc").exclude(age=23)
4. QuerySet是唯一的
每个QuerySet都是唯一的,每次精炼一个 QuerySet,你就会获得一个全新的 QuerySet,后者与前者毫无关联。每次精炼都会创建一个单独的、不同的 QuerySet,能被存储,使用和复用。
比如:
q1 = Entry.objects.filter(headline__startswith="What")
q2 = q1.exclude(pub_date__gte=datetime.date.today())
q3 = q1.filter(pub_date__gte=datetime.date.today())
这三个 QuerySets 是独立的。第一个是基础 QuerySet,包含了所有标题以 "What" 开头的条目。第二个是第一个的子集,带有额外条件,排除了 pub_date 是今天和今天之后的所有记录。第三个是第一个的子集,带有额外条件,只筛选 pub_date 是今天或未来的所有记录。最初的 QuerySet (q1) 不受筛选操作影响。
5. QuerySet是惰性的
创建 QuerySet 并不会引发任何数据库活动。Django 只会在 QuerySet 被 计算 时执行查询操作。
比如:
q = Entry.objects.filter(headline__startswith="What")
q = q.filter(pub_date__lte=datetime.date.today())
q = q.exclude(body_text__icontains="food")
print(q)
虽然这看起来像是三次数据库操作,实际上只在最后一行 (print(q)) 做了真正的数据库查询。
6. get查询
filter() 总是返回一个 QuerySet,即便只有一个对象满足查询条
这种情况下, QuerySet 只包含了一个元素。也就是说,filter查询出来的只有一个,也是需要使用 切片[0] 来获取这个对象
若只有一个对象满足查询条件,可以使用get方法,会直接返回这个对象
one_entry = Entry.objects.get(pk=1)
访问的时候,这样子访问:
one_entry.id
注意:
如果没有满足查询条件的结果, get() 会抛出一个 DoesNotExist 异常
如果存在多个满足查询条件的结果,也会报错,Django 会抛出 MultipleObjectsReturned
也就是说,get只能获取一条数据,没有数据或者是有多条数据都是会报错的
7. 限制QuerySet的条数
利用 Python 的数组切片语法将 QuerySet 切成指定长度。这等价于 SQL 的 LIMIT 和 OFFSET 子句。
比如:
# 返回前 5 个对象 (LIMIT 5):
Entry.objects.all()[:5]
# 返回第 6 至第 10 个对象 (OFFSET 5 LIMIT 5):
Entry.objects.all()[5:10]
# 返回前10个对象中的偶数:
Entry.objects.all()[:10:2]
# 获取id最大的20条
Author.objects.order_by('-id')[:20]
注意:不支持负索引 (例如 Entry.objects.all()[-1])
8. 排序
QuerySet可以使用order_by来进行排序,默认是正序的,前面加一个负号,可以实现倒序
比如:
Author.objects.all().order_by('name')
Author.objects.all().order_by('-name')
并且支持多个字段排序:
Publisher.objects.order_by("state_province", "address")
9. QuerySet是可以迭代的
比如:
all_entries = Entry.objects.all()
for entry in all_entries:
print(entry.name)
也可以使用list方法将QuerySet强行转换为列表
10. 获取特定字段
获取特定的字段有两种方式:
- values():括号里面可以指定参数,不指定的话则返回全部字段,返回的是dict字典,更推荐使用
- values_list():括号里面可以指定参数,不指定的话则返回全部字段,返回的是list列表
比如:
# 获取全部字段
res = AlarmEvent.objects.filter(id=1)
print(res[0].content)
# 使用values获取全部字段
res1 = AlarmEvent.objects.filter(id=1).values()
print(res1[0]['content'])
# 使用values_list获取全部字段
res2 = AlarmEvent.objects.filter(id=1).values_list()
print(res1[0][1])
# 使用values获取特定字段
res3 = AlarmEvent.objects.values('content', 'id').get(pk=1)
print(res3['content'])
11. 统计
如果想要统计符合条件的数量,有两种方法:
- len:len(Entry.objects.all())
- count:Entry.objects.count()
用 len(es) 可以得到Entry的数量,但是推荐用 Entry.objects.count()来查询数量,后者用的是SQL:SELECT COUNT(*)
12. 聚合函数
如果想要再Django中使用聚合函数比如Sum、Count、Max、Avg等,需要使用aggregate或者是annotate
- aggregate:aggregate 是全部结果集的查询
- annotate:annotate则是分组查询的
比如:
from django.db.models import Avg,Max,Min,Count,Sum
# 相当于:SELECT source_type, COUNT(source_type) AS SUM FROM events GROUP BY source_type;
res = AlarmEvent.objects.values('source_type').all().annotate(num=Count('source_type'))
# 返回:<QuerySet [{'source_type': 'Cat', 'num': 26}, {'source_type': 'Cloud', 'num': 1}, {'source_type': 'Others', 'num': 1}, {'source_type': 'Scripts', 'num': 17}]>
# 相当于:SELECT COUNT('id') AS SUM FROM `z_alarm_events`;
res = AlarmEvent.objects.values('source_type').all().aggregate(sum=Count('id'))
# 返回:{'sum': 45}
- annotate 查询的是每一种类型source_type的总数,对QuerySet 集合使用Group By 根据类型source_type来分组
- aggregate 则是总和了所有的数量,相当于在SQL中使用了count
三、查询
字段查询即 SQL WHERE 子句。它们以关键字参数的形式传递给 QuerySet 方法 filter(), exclude() 和 get()。
基本的查询关键字参数遵照 field__lookuptype=value
例子:
# 获取所有记录
all = Question.objects.all()
# 小于等于查询,相当于:SELECT id, content FROM blog_entry WHERE pub_date <= '2006-01-01';
Entry.objects.filter(pub_date__lte='2006-01-01').values('id', 'content')
# 获取满足条件的数量
Entry.objects.filter(pub_date__lte='2006-01-01').count()
# 等于查询,相当于:SELECT * WHERE headline = 'Cat bites dog';
Entry.objects.filter(headline__exact="Cat bites dog")
# 等于查询,如果关键字参数没有包含双下划线,默认指定为exact,即以下两条语句是等价的:
Blog.objects.filter(id__exact=14)
Blog.objects.filter(id=14)
# 等于查询-不区分大小写:
Blog.objects.filter(name__iexact="beatles blog")
# like查询,相当于:SELECT * WHERE headline LIKE '%Lennon%';
Entry.objects.filter(headline__contains='Lennon')
# not like查询,相当于:SELECT * WHERE headline NOT LIKE '%Lennon%';
Entry.objects.exclude(headline__contains='Lennon')
# like查询,不区分大小写:icontains
Entry.objects.filter(headline__icontains='Lennon')
# like查询,startswith:以……开头,endswith:以……结尾,以下语句相当于 SELECT * WHERE headline LIKE 'Lennon%';
# 大小写不敏感的版本,istartswith 和 iendswith
Entry.objects.filter(headline__startswith='Lennon')
# in查询
res = Question.objects.filter(id__in=[3, 5])
# not in查询
res = Question.objects.exclude(id__in=[3, 5])
# 正则表达式查询,如果是想不区分大小写,可以使用iregex
Person.objects.filter(name__regex="^abc")
# 找出名称含有abc, 但是排除年龄是23岁的
Person.objects.filter(name__contains="abc").exclude(age=23)
# 获取最近的5条记录 相当于 select * from question order by pub_date desc limit 5
latest_question_list = Question.objects.order_by('-pub_date')[:5]
# 获取id最大的20条
Author.objects.order_by('-id')[:20]
# 多个字段排序
Publisher.objects.order_by("state_province", "address")
# 连锁查询,相当于 SELECT * FROM books_publisher WHERE country = 'U.S.A' ORDER BY name DESC;
Question.objects.filter(country="U.S.A.").order_by("-name")
# 相当于:SELECT source_type, COUNT(source_type) AS SUM FROM events GROUP BY source_type;
res = AlarmEvent.objects.values('source_type').all().annotate(num=Count('source_type'))
# 相当于:SELECT COUNT('id') AS SUM FROM `z_alarm_events`;
res = AlarmEvent.objects.values('source_type').all().aggregate(sum=Count('id'))
# 范围查询,相当于between
AlarmEvent.objects.filter(id__range=[1, 9])
四、Q对象查询
在类似 filter() 中,查询使用的关键字参数是通过 "AND" 连接起来的。
如果想执行更复杂的查询(例如,由 OR 语句连接的查询),你可以使用 Q 对象。
比如:
Q对象压缩了like查询:
from django.db.models import Q
Q(question__startswith='What')
Q 对象能通过 & 和 | 操作符连接起来。当操作符被用于两个 Q 对象之间时会生成一个新的 Q 对象。
比如:
该语句生成一个 Q 对象,表示两个 "question_startswith" 查询语句之间的 "OR" 关系:
Q(question__startswith='Who') | Q(question__startswith='What')
等价于:WHERE question LIKE 'Who%' OR question LIKE 'What%'
每个接受关键字参数的查询函数 (例如 filter(), exclude(), get()) 也同时接受一个或多个 Q 对象作为位置(未命名的)参数。
若为查询函数提供了多个 Q 对象参数,这些参数会通过 "AND" 连接。
比如:
Poll.objects.get(
Q(question__startswith='Who'),
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
相当于:
SELECT * from polls WHERE question LIKE 'Who%' AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
查询函数能混合使用 Q 对象和关键字参数。所有提供给查询函数的参数(即关键字参数或 Q 对象)均通过 "AND" 连接。然而,若提供了 Q 对象,那么它必须位于所有关键字参数之前。
比如:
Poll.objects.get(
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
question__startswith='Who',
)
但是,如果是下面的例子,则是无效的:
Poll.objects.get(
question__startswith='Who',
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
不等于的查询,相当于:select * from poll where task_code = 'aaa' and status != 'success'
Poll.objects.filter(task_code='aaa').filter(~Q(status='success'))
具体的一个复杂查询:
con = Q()
# ID条件搜索
if 'id' in data_filter:
con.children.append(('id', data_filter['id']))
# 事件内容模糊搜索,相当于content like '%{content}%'
if 'content' in data_filter:
con.children.append(('content__contains', data_filter['content']))
# 主机名模糊搜索(单个主机或多个主机模糊搜索)
if 'name' in data_filter:
con.children.append(('name__contains', data_filter['name']))
elif 'name_list' in data_filter:
if data_filter['name_list']:
q1 = Q()
q1.connector = 'OR'
for single_name in data_filter['name_list']:
q1.children.append(('name__contains', single_name))
con.add(q1, 'AND')
# 事件时间筛选,筛选出时间>=from的
if 'time_from' in data_filter:
con.children.append(('created_at__gte', data_filter['time_from']))
# 事件时间筛选,筛选出时间<to的
if 'time_to' in data_filter:
con.children.append(('created_at__lt', data_filter['time_to']))
res = AlarmEvent.objects.filter(con)
五、F对象查询
如果想要在查询中将模型字段值与同一模型中的另一字段做比较,可以使用F表达式,F() 的实例充当查询字段的引用。这些引用可在查询过滤器中用于在同一模型实例中比较两个不同的字段。
比如:要查出所有评论数大于 pingbacks 的博客条目,构建了一个 F() 对象,指代 pingback 的数量,然后在查询中使用该 F() 对象:
from django.db.models import F
Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
Django 支持对 F() 对象进行加、减、乘、除、求余和次方,另一操作数既可以是常量,也可以是其它 F() 对象。
比如:
要找到那些评论数两倍于 pingbacks 的博客条目,修改查询条件:
Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)
要找出所有评分低于 pingback 和评论总数之和的条目,修改查询条件:
Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))
对于 date 和 date/time 字段,可以加上或减去一个 timedelta 对象。以下会返回所有发布 3 天后被修改的条目:
from datetime import timedelta
Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
六、原生查询
相比直接使用SQL而言,QuerySet可以防止大部分SQL注入,而且提高代码可读性。
Django允许两种方式进行原生SQL查询:
- Manager.raw
- 自定义SQL
1. raw方法
Manager.raw只能执行select查询,不能使用其他的比如update等语句
语法:
Manager.raw(raw_query, params=None, translations=None)
该方法接受一个原生 SQL 查询语句,执行它,并返回一个 django.db.models.query.RawQuerySet 实例。这个 RawQuerySet 能像普通的 QuerySet 一样被迭代获取对象实例
params 是一个参数字典。你将用一个列表替换查询字符串中 %s 占位符,或用字典替换 %(key)s 占位符(其中, key 理所应当由字典 key 替换)
all查询
raw = AlarmEvent.objects.raw('''select * from z_alarm_events''')
print(raw[0].content)
切片查询
raw = AlarmEvent.objects.raw('''select id,content from z_alarm_events''')[-1]
print(raw.content)
注意:select的时候,一定要有id主键字段,如果上面的改成select content from ... 会报错
传递参数
raw = AlarmEvent.objects.raw('''select id,content from z_alarm_events where id = %s''', [4])[0]
print(raw.id)
注意:一定要过滤参数,防止SQL注入
2. 自定义SQL
有时候,甚至 Manager.raw() 都无法满足需求:可能要执行不明确映射至模型的查询语句,或者就是直接执行 UPDATE, INSERT 或 DELETE 语句
这些情况下,可以使用自定义SQL直接访问数据库,完全绕过模型层。
对象 django.db.connection 代表默认数据库连接。要使用这个数据库连接,调用 connection.cursor() 来获取一个指针对象。然后,调用 cursor.execute(sql, [params]) 来执行该 SQL 和 cursor.fetchone(),或 cursor.fetchall() 获取结果数据。
比如:
from django.db import connection
def my_custom_sql(self):
with connection.cursor() as cursor:
cursor.execute("UPDATE bar SET foo = 1 WHERE baz = %s", [self.baz])
cursor.execute("SELECT foo FROM bar WHERE baz = %s", [self.baz])
row = cursor.fetchone()
return row
复杂的SQL查询:
from django.db import connection
def get_event_range(start, end):
"""
获取某个时间范围的事件统计
"""
table_name = 'z_alarm_events'
cursor = connection.cursor()
sql = '''select DATE_FORMAT(created_at,"%%Y-%%m-%%d") as created_at ,count(id) as num from %s where created_at>="%s" and created_at<"%s" group by DATE_FORMAT(created_at,"%%Y-%%m-%%d") ''' % (table_name, start.strftime("%Y-%m-%d %H:%M:%S"), end.strftime("%Y-%m-%d %H:%M:%S"))
cursor.execute(sql)
res = cursor.fetchall()
return res
七、新建
1. 创建单个对象
创建对象有四种方法:
# 方法 1
Author.objects.create(name="WeizhongTu", email="[email protected]")
# 方法 2
twz = Author(name="WeizhongTu", email="[email protected]")
twz.save()
# 方法 3
twz = Author()
twz.name="WeizhongTu"
twz.email="[email protected]"
twz.save()
# 方法 4,首先尝试获取,不存在就创建,可以防止重复
Author.objects.get_or_create(name="WeizhongTu", email="[email protected]")
# 返回值(object, True/False)
注意:前三种方法返回的都是对应的 object,最后一种方法返回的是一个元组,(object, True/False),创建时返回 True, 已经存在时返回 False
最后一种方法get_or_create,是非常实用的一种方法
比如:
try:
obj = Person.objects.get(first_name='John', last_name='Lennon')
except Person.DoesNotExist:
obj = Person(first_name='John', last_name='Lennon', birthday=date(1940, 10, 9))
obj.save()
可以使用get_or_create来简化:
obj, created = Person.objects.get_or_create(
first_name='John',
last_name='Lennon',
defaults={'birthday': date(1940, 10, 9)},
)
2. 批量创建
可以使用bulk_create来实现批量创建
比如:
def bulk_create(data_list):
"""
批量插入数据
:param data_list:
:return:
"""
info_list = []
for value in data_list:
info = BusinessKeys(key=value['key'], app_name=value['app_name'], cat_key=value['cat_key'])
info_list.append(info)
return BusinessKeys.objects.bulk_create(info_list)
八、更新
1. 单个更新
单个 object 更新,适合于 .get(), get_or_create(), update_or_create() 等得到的 obj,和新建很类似
twz = Author.objects.get(name="Weizho")
twz.name="WeizhongTu"
twz.email="[email protected]"
twz.save()
update_or_create:存在则更新,不存在则新建
比如:
先判断是否存在first_name='John', last_name='Lennon',如果存在的话,则把first_name更新为Bob,如果不存在,则新建:
defaults = {'first_name': 'Bob'}
try:
obj = Person.objects.get(first_name='John', last_name='Lennon')
for key, value in defaults.items():
setattr(obj, key, value)
obj.save()
except Person.DoesNotExist:
new_values = {'first_name': 'John', 'last_name': 'Lennon'}
new_values.update(defaults)
obj = Person(**new_values)
obj.save()
可以使用update_or_create简化:
obj, created = Person.objects.update_or_create(
first_name='John', last_name='Lennon',
defaults={'first_name': 'Bob'},
)
再比如:
data = {
'start_time': start_time,
'end_time': end_time,
'key': 'key',
'value': 'new_value'
}
res = DailyTrading.objects.update_or_create(start_time=data['start_time'], key=data['key'], defaults=data)
2. 批量更新
批量更新,适用于 .all() .filter() .exclude() 等后面 (危险操作,正式场合操作务必谨慎)
# 名称中包含 "abc"的人 都改成 xxx
Person.objects.filter(name__contains="abc").update(name='xxx')
九、删除
和查询类似,获取到满足条件的结果,然后使用delete就可以了 (危险操作,正式场合操作务必谨慎):
# 删除 名称中包含 "abc"的人
Person.objects.filter(name__contains="abc").delete()
# 或者是
people = Person.objects.filter(name__contains="abc")
people.delete()