实际上,项目开发中写的SQL有80%属于查询,即SELECT语句,而查询语句中最重大的就是条件查询,即WHERE子句。在Django的ORM框架中,WHERE子句一般由字段查询(Field lookups)来实现,这些查询以K-V对的形式作为参数被传入查询方法中,如filter、get等,然后,由框架根据模型和参数将方法翻译为SQL查询语句。
在正式开讲之前,依旧以博客代码为例,模型代码如下:
from datetime import date
from django.db import models
# 博客类
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
# 作者类
class Author(models.Model):
name = models.CharField(max_length=200)
age = models.IntegerField()
email = models.EmailField()
# 具体条目
class Entry(models.Model):
# 外键关联博客类
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField(default=date.today)
# 多对多关联作者
authors = models.ManyToManyField(Author)
number_of_comments = models.IntegerField(default=0)
number_of_pingbacks = models.IntegerField(default=0)
rating = models.IntegerField(default=5)
字段查询的基本形式为field__lookuptype = value,其中,field为模型中的参数,如果模型中不存在该参数,则会抛出TypeError异常。需要注意的是,这里存在一个特例,那就是当field为模型中指定的外键时,需要添加后缀“_id”,列如:
Entry.objects.filter(blog_id = 1)
在这个例子中,blog属于Entry模型中的外键字段,此时,需要添加后缘“_id”告知框架blog是外键字段。lookuptype为查询类型,Django ORM中支持大约24种类型,下文会详细介绍,field和lookuptype由“__”连接,注意,这里是双下划线,这也是字段命名不要使用双下划线的缘由之一。先以一个简单示例说明Django ORM的工作机制:
Entry.objects.filter(pub_date__lte = '2025-12-1')
这行代码会被框架翻译为:
SELECT * FROM entry WHERE pub_date <= '2025-12-1';
这里参数中lookuptype为lte,实际上就是less than or equal to的缩写。接下来,将介绍常用的lookuptype:
1、exact
表明准确匹配,这也是默认查询方式。如果值为None,则查询条件被翻译为NULL。
# 以下两行代码效果一样,被翻译为:SELECT ... WHERE id = 1;
# 注,... 表明省略的字段和表名,本文重点关键条件部分,下同。
Entry.objects.get(id__exact = 1)
Entry.objects.get(id = 1)
# SELECT ... WHERE id IS NULL;
Entry.objects.get(id = None)
2、iexact
表明大小写不敏感的准确匹配(case-insensitive),这种情况下在查询时被翻译为“LIKE”子句。
# SELECT ... WHERE name ILIKE 'jack';
Author.objects.filter(name__iexact = 'jack')
在这个示例中,名为‘Jack'、’JACK'、或'JacK'的作者都将被查询命中。
3、contains
表明大小写敏感的包含查询,在查询时被翻译为“LIKE”子句。
# SELECT ... WHERE name LIKE '%Li%';
Author.objects.filter(name__contains = 'Li')
在这个示例中,名字中包含'Li’的作者名字都会被匹配到,如'Li Lei'、'Zhao Li'。但是,'Zhao Xueli'就不会被匹配到,由于这个名字中是小写字母'li'。
4、icontains
表明大小写不敏感的包含查询,用法同上,只是大小写不敏感。
# SELECT ... WHERE name ILIKE '%Li%';
Author.objects.filter(name__icontains = 'Li')
与上例不同的是,'Zhao Xueli'也会被此查询命中。
5、in
查询字段属于给定的序列中的数据,可以是列表List、集合Set、查询集QuerySet,甚至是字符串。如:
# SELECT ... WHERE age IN (18, 28, 38);
Author.objects.filter(age__in = [18, 28, 38])
这个例子将查询所有年龄为18、28和38的作者。还可以将QuerySet作为字段查询的值,构成嵌套
查询:
# SELECT ... WHERE blog_id IN (SELECT id FROM ... WHERE name LIKE 'happy');
Entry.objects.filter(blog__in = Blog.objects.filter(name__contain = 'happy'))
在这个例子中,第一通过子查询找到名字中包含'happy'的博客,然后查询属于这些博客的条目。
6、gt
Great than,表明大于,被翻译为“>”。示例如下:
# SELECT ... WHERE age > 18;
Author.objects.filter(age__gt = 18)
在这个例子中,年龄大于18岁的作者将被查询命中。
7、gte
Great than or equal to,表明大于或等于,被翻译为“≥”。示例如下:
# SELECT ... WHERE age ≥ 18;
Author.objects.filter(age__gte = 18)
在这个例子中,年龄大于或等于18岁的作者都将被查询命中。
8、lt
Less than,表明小于,被翻译为“<”。
9、lte
Less than or equal to,表明小于或等于,被翻译为“≤”。
10、startswith
查询以指定字符开头的数据,大小写敏感,被翻译为“LIKE”子句,示例如下:
# SELECT ... WHERE name LIKE 'Li%';
Author.objects.filter(name__startswith = 'Li')
在这个例子中,”Li”姓作者将被查询命中,需要注意的是,大小写敏感设置下,“Li Lei”被命中,而“li lei”不会被命中。
此外,需要注意数据库特性,如SQLite数据库不支持大小写敏感匹配,此时,startswith将被翻译为ILIKE,和istartswith功能一样。
11、istartswith
查询以指定字符开头的数据,大小写不敏感,示例如下:
# SELECT ... WHERE name ILIKE 'Li%';
Author.objects.filter(name__istartswith = 'Li')
12、endswith
查询以指定字符结尾的数据,大小写敏感,被翻译为“LIKE”子句,示例如下:
# SELECT ... WHERE name ILIKE '%Lei';
Author.objects.filter(name__endswith = 'Lei')
13、iendswith
查询以指定字符结尾的数据,大小写不敏感,被翻译为ILIKE子句,
14、range
查询指定字段在给定范围内的数据,被翻译为“BETWEEN … AND …”子句,示例如下:
# SELECT ... WHERE age BETWEEN 18 AND 35;
Author.objects.filter(age__range = (18, 35))
除数值类型外,range也可用于字符和日期类型。
15、date
用于时间日期字段,转化为日期进行查询,示例如下:
Entry.objects.filter(pub_date__date = datetime.date(2025, 1, 1)
Entry.objects.filter(pub_date__date__gt = datetime.date(2025, 1, 1)
需要注意的是,这个例子中的查询在不同的数据库引擎下会翻译成不同的SQL。
16、year
用于日期或时间日期字段中年份的准确匹配,示例如下:
# SELECT ... WHERE pub_date BETWEEN '2025-01-01' AND '2025-12-31';
Entry.objects.filter(pub_date__year = 2025)
# SELECT ... WHERE pub_date >= 2023;
Entry.objects.filter(pub_date__year__gte = 2023)
具体的SQL查询语句可能随数据库引擎的不同而变化。
17、month
用于日期或时间日期中月份的准确匹配,示例如下:
# SELECT ... WHERE EXTRACT('month' FROM pub_date) = 12;
Entry.objects.filter(pub_date__month = 12)
# SELECT ... WHERE EXTRACT('month' FROM pub_date) >= 5;
Entry.objects.filter(pub_date__month__gte = 5)
在这个例子中,所有发布于12月的记录均会被命中,而不管是哪一年,具体的SQL查询语句可能随数据库引擎的不同而变化。
18、day
用于日期或时间日期字段,与month用法基本类似,无需赘述。
19、week
用于日期或时间日期字段,周,一年共53个周,与day用法基本类似,无需赘述。
20、week_day
用于日期或时间日期字段,day of the week,即,星期,取值范围为1-7,需要注意的是从周日开始,即,1表明周日。
21、quarter
用于日期或时间日期字段,quarter of the year,即,季度,取值范围为1-4。
22、time
用于时间日期字段,转化为时间进行查询,示例如下:
Entry.objects.filter(pub_date__time = datetime.time(14, 30))
Entry.objects.filter(pub_date__time__range = (datetime.time(8), datetime.time(17)))
23、hour
用于日期时间字段,表明小时匹配,取值范围为0-23,示例如下:
# SELECT ... WHERE EXTRACT('hour' FROM pub_date) = 12;
Entry.objects.filter(pub_date__hour = 12)
# SELECT ... WHERE EXTRACT ('hour' FROM pub_date) >= 12;
Entry.objects.filter(pub_date__hour__gte = 12)
24、minute
用于日期时间字段,表明分钟匹配,取值范围为0-59,用法类似hour,不再赘述。
25、second
用于日期时间字段,表明秒级匹配,取值范围为0-59,用法类似hour,不再赘述。
26、isnull
用于查询字段为空的数据,被翻译为“IS NULL”或“IS NOT NULL”,取值为True或False,示例如下:
# SELECT ... WHERE pub_date IS NULL;
Entry.objects.filter(pub_date__isnull = True)
# SELECT ... WHERE pub_date IS NOT NULL;
Entry.objects.filter(pub_date__isnull = False)