Django ORM 数据查询与操作指南
Django 的对象关系映射(ORM)提供了一种强大且直观的方式来与数据库进行交互,它将数据库表映射为 Python 类,将表记录映射为 Python 对象,并通过高层 API 执行数据库操作。本文将深入探讨 Django ORM 的常用查询方法、高级特性以及跨模型关联查询。
基础查询操作
QuerySet 是 Django ORM 的核心。当你使用 Manager(通常是 objects)进行查询时,你会得到一个 QuerySet 对象,它代表着数据库中满足查询条件的对象的集合。QuerySet 是惰性求值的,只有在真正需要数据时(例如迭代、切片或转换为列表)才会执行数据库查询。
QuerySet 方法概览
以下是一些常用的 QuerySet 方法,用于从数据库中检索数据:
all(): 返回此 Manager 下所有对象的 QuerySet。filter(**kwargs): 返回一个包含与给定筛选条件匹配的对象的 QuerySet。get(**kwargs): 返回与给定筛选条件匹配的唯一对象。如果未找到对象或找到多个对象,将抛出异常。exclude(**kwargs): 返回一个包含与给定筛选条件不匹配的对象的 QuerySet。values(*fields): 返回一个 ValueQuerySet,其中每个对象都是一个字典,包含指定字段的键值对。values_list(*fields): 与values()类似,但返回一个元组序列。可以通过flat=True参数在只选择一个字段时直接返回一个值列表。order_by(*fields): 根据指定字段对 QuerySet 进行排序。默认升序,使用-前缀表示降序。reverse(): 反转 QuerySet 中元素的顺序。通常在已排序的 QuerySet 上调用。distinct(): 从返回结果中去除重复记录。在跨多表查询时尤其有用,但按字段去重的功能通常只在 PostgreSQL 中完全支持。count(): 返回 QuerySet 中匹配对象的数量。first(): 返回 QuerySet 中的第一个对象。如果 QuerySet 为空,则返回None。last(): 返回 QuerySet 中的最后一个对象。如果 QuerySet 为空,则返回None。exists(): 检查 QuerySet 是否包含任何结果,返回True或False。通常比count() > 0更高效。
方法返回类型归纳
- 返回 QuerySet 对象(可能包含多条记录):
all(),filter(),exclude(),order_by(),reverse(),distinct(),values(),values_list()。 - 返回具体对象(单条记录):
get(),first(),last()。 - 返回布尔值:
exists()。 - 返回数字:
count()。
高级字段查找:双下划线语法
在 filter()、exclude() 和 get() 等方法中,可以使用双下划线(__)进行高级字段查找。这允许你执行各种复杂的条件查询。
以下是一些常见的双下划线查询示例:
__lt(less than): 小于__gt(greater than): 大于__lte(less than or equal): 小于等于__gte(greater than or equal): 大于等于__in: 包含在给定列表或元组中__contains: 包含指定字符串(区分大小写)__icontains: 包含指定字符串(不区分大小写)__startswith: 以指定字符串开头(区分大小写)__istartswith: 以指定字符串开头(不区分大小写)__endswith: 以指定字符串结尾(区分大小写)__iendswith: 以指定字符串结尾(不区分大小写)__range: 处于指定范围之间(包含两端值)- 日期/时间字段查找:
__year,__month,__day,__week_day,__hour,__minute,__second
假设我们有以下模型定义:
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Product(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
stock_quantity = models.IntegerField(default=0)
release_date = models.DateField(auto_now_add=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products')
def __str__(self):
return self.name
高级字段查找示例:
# 查询库存量大于50的产品
products_high_stock = Product.objects.filter(stock_quantity__gt=50)
# 查询价格在100到500之间的产品
products_price_range = Product.objects.filter(price__range=(100, 500))
# 查询名称中包含"Book"(不区分大小写)的产品
products_containing_book = Product.objects.filter(name__icontains="book")
# 查询id为1、5、10的产品
products_by_ids = Product.objects.filter(id__in=[1, 5, 10])
# 查询发布年份为2023年的产品
products_2023 = Product.objects.filter(release_date__year=2023)
# 组合条件:库存量大于20且名称以"Smart"开头的商品
smart_products_in_stock = Product.objects.filter(stock_quantity__gt=20, name__startswith="Smart")
聚合与分组查询
Django ORM 提供了强大的聚合和分组功能,用于对 QuerySet 中的数据进行统计分析。
aggregate() 函数详解
aggregate() 是 QuerySet 的一个终止子句,它返回一个字典,其中包含计算出的聚合值。你可以使用 Django 内置的聚合函数,如 Avg (平均值)、Max (最大值)、Min (最小值)、Sum (总和)、Count (计数)等。
from django.db.models import Avg, Max, Min, Sum, Count
# 统计所有产品的平均价格
avg_price = Product.objects.aggregate(Avg('price'))
# {'price__avg': 150.75}
# 为聚合结果指定自定义名称
stats = Product.objects.aggregate(
average_price=Avg('price'),
max_price=Max('price'),
total_products=Count('id')
)
# {'average_price': 150.75, 'max_price': Decimal('999.99'), 'total_products': 50}
annotate() 实现分组统计
annotate() 用于为 QuerySet 中的每个对象添加一个计算出的新字段。当与分组操作结合使用时,它能够统计每个分组的聚合值。
示例1:统计每个分类的产品数量
# 假设我们有 Category 和 Product 模型
# 为每个分类(Category)对象添加一个 `product_count` 字段
category_with_counts = Category.objects.annotate(product_count=Count('products'))
for category in category_with_counts:
print(f"分类: {category.name}, 产品数量: {category.product_count}")
# 也可以通过 Product 模型进行反向分组
# 返回一个字典列表,表示每个分类的名称及其产品数量
product_counts_by_category = Product.objects.values('category__name').annotate(
item_count=Count('id')
)
for entry in product_counts_by_category:
print(f"分类名称: {entry['category__name']}, 产品总数: {entry['item_count']}")
示例2:统计每个分类中最便宜产品的价格
min_prices_by_category = Category.objects.annotate(
cheapest_product_price=Min('products__price')
)
for category in min_prices_by_category:
print(f"分类: {category.name}, 最便宜产品价格: {category.cheapest_product_price}")
示例3:找出产品数量超过5个的分类
categories_with_many_products = Category.objects.annotate(
product_count=Count('products')
).filter(product_count__gt=5)
for category in categories_with_many_products:
print(f"产品数量超过5个的分类: {category.name}")
示例4:根据产品数量对分类进行排序
sorted_categories = Category.objects.annotate(
product_count=Count('products')
).order_by('-product_count') # 降序排列
for category in sorted_categories:
print(f"分类: {category.name}, 产品数量: {category.product_count}")
F对象与Q对象
Django ORM 提供了 F 对象和 Q 对象,用于处理更复杂的查询逻辑。
F对象:字段间比较与更新
F 对象允许你在查询中引用模型字段的值,而不是一个固定的常量。这使得比较同一模型实例中两个不同字段的值成为可能,也支持字段间的算术运算。
示例1:查询库存量低于售价的产品
from django.db.models import F
# 查询库存数量小于价格值的产品 (假设价格和库存量可比)
# 实际场景中,价格和库存量直接比较意义不大,但展示F对象的用法
products_special_condition = Product.objects.filter(stock_quantity__lt=F('price'))
示例2:查询库存量是价格两倍以上的产品
products_double_stock = Product.objects.filter(stock_quantity__gt=F('price') * 2)
F 对象也可以用于更新操作,实现批量修改字段值:
# 将所有产品的价格提高10%
Product.objects.all().update(price=F('price') * 1.1)
# 将所有产品的库存量减少50
Product.objects.filter(stock_quantity__gte=50).update(stock_quantity=F('stock_quantity') - 50)
F 对象还支持字符串拼接等高级操作:
from django.db.models.functions import Concat
from django.db.models import Value
# 将所有产品名称末尾添加 "(新品)"
Product.objects.all().update(name=Concat(F('name'), Value(' (新品)')))
Q对象:复杂逻辑查询(OR, NOT)
filter() 等方法的关键字参数默认使用"AND"逻辑组合。当你需要执行"OR"逻辑或更复杂的组合查询时,可以使用 Q 对象。
示例1:查询分类名称为"电子产品"或价格低于100的产品
from django.db.models import Q
# 使用Q对象进行OR查询
products_or_condition = Product.objects.filter(
Q(category__name="电子产品") | Q(price__lt=100)
)
你可以使用 & (AND)、| (OR) 和 ~ (NOT) 操作符组合 Q 对象,并使用括号进行分组,构建任意复杂的查询。
示例2:查询价格高于200但不是"图书"分类的产品
expensive_non_book_products = Product.objects.filter(
Q(price__gt=200) & ~Q(category__name="图书")
)
Q 对象可以与关键字参数混合使用,但所有 Q 对象必须位于所有关键字参数之前。
示例3:查询发布年份是2022或2023年,并且名称中包含"Pro"的产品
recent_pro_products = Product.objects.filter(
Q(release_date__year=2022) | Q(release_date__year=2023),
name__icontains="Pro"
)
跨模型关联查询
Django ORM 使得跨模型关联查询变得非常简单,无论是外键关系(一对多)还是多对多关系。
一对多关系查询(ForeignKey)
假设我们的 Product 模型通过 ForeignKey 关联到 Category 模型。
class Category(models.Model):
name = models.CharField(max_length=50)
class Product(models.Model):
# ... 其他字段
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products')
正向查询
从"一"的一方(Product)查询"多"的一方(Category)。
- 对象查找: 通过实例属性直接访问关联对象。
# 获取一个产品对象 product_obj = Product.objects.first() # 访问关联的分类对象 category_obj = product_obj.category print(f"产品名称: {product_obj.name}, 所属分类: {category_obj.name}") - 字段查找: 在 QuerySet 中使用双下划线
__访问关联字段的属性。# 查询所有产品的分类名称 category_names = Product.objects.values_list("category__name", flat=True) print(list(category_names)) # 筛选属于"电子产品"分类的所有产品 electronics_products = Product.objects.filter(category__name="电子产品")
反向查询
从"多"的一方(Category)查询"一"的一方(Product)。 Django 会自动为反向关系创建 _set 管理器(除非你在 ForeignKey 中指定了 related_name)。这里我们指定了 related_name='products'。
- 对象查找: 通过关联管理器访问关联对象集合。
# 获取一个分类对象 category_obj = Category.objects.get(name="电子产品") # 获取该分类下的所有产品 products_in_category = category_obj.products.all() # 使用 related_name 'products' for product in products_in_category: print(f"分类 '{category_obj.name}' 下的产品: {product.name}") - 字段查找: 在 QuerySet 中使用
related_name__field进行反向查找。# 查询所有分类以及它们关联的产品名称 category_product_titles = Category.objects.values_list("name", "products__name") print(list(category_product_titles)) # 找出包含名称中带有 "无线" 产品的分类 wireless_product_categories = Category.objects.filter(products__name__icontains="无线").distinct()
多对多关系查询(ManyToManyField)
假设我们为 Product 模型添加了一个 Tag (标签)的 ManyToManyField:
class Tag(models.Model):
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return self.name
class Product(models.Model):
# ... 其他字段
tags = models.ManyToManyField('Tag', related_name='products') # Many-to-many
关联管理器 (RelatedManager)
对于多对多关系,Django 会在关联的两端提供一个 RelatedManager。它允许你直接对关联对象集进行添加、移除、创建等操作。
常用操作方法:
add(*objects | *ids): 将一个或多个对象或其 ID 添加到关联对象集中。product_item = Product.objects.get(name="智能手机") tag_tech = Tag.objects.get(name="科技") tag_new = Tag.objects.create(name="新品") # 直接创建并添加 product_item.tags.add(tag_tech, tag_new) # 添加多个对象 # product_item.tags.add(1, 5) # 也可以添加IDcreate(**kwargs): 创建一个新的关联对象,并将其添加到关联对象集中,返回新创建的对象。product_item = Product.objects.get(name="智能手机") # 为 '智能手机' 创建一个新的标签 '畅销',并立即关联 new_tag = product_item.tags.create(name="畅销") print(f"为 '{product_item.name}' 添加了新标签: {new_tag.name}")set(objects | ids, *, clear=False): 替换关联对象集。传入的可以是对象列表或 ID 列表。如果clear=True,则先移除所有现有关系再添加新关系。product_item = Product.objects.get(name="智能手机") tag_electronics = Tag.objects.get(name="电子") tag_mobile = Tag.objects.get(name="移动") # 将 '智能手机' 的标签设置为 '电子' 和 '移动',覆盖之前的标签 product_item.tags.set([tag_electronics.id, tag_mobile.id])remove(*objects | *ids): 从关联对象集中移除一个或多个指定的对象或其 ID。product_item = Product.objects.get(name="智能手机") tag_mobile = Tag.objects.get(name="移动") # 移除 '移动' 标签与 '智能手机' 的关联 product_item.tags.remove(tag_mobile)clear(): 从关联对象集中移除所有对象。product_item = Product.objects.get(name="智能手机") product_item.tags.clear() # 清除 '智能手机' 的所有标签
注意: 对于 ForeignKey 字段,clear() 和 remove() 方法仅在 ForeignKey 字段设置 null=True 时可用。对于 ManyToManyField,这些方法始终可用。
所有 add(), create(), remove(), clear(), set() 方法都会立即更新数据库,无需再调用 save() 方法。
自关联与 symmetrical 参数
ManyToManyField 也可以实现自关联,即模型与自身的关联。例如,一个人可以有多个朋友。
class Person(models.Model):
name = models.CharField(max_length=100)
# 默认 symmetrical=True,表示 A 是 B 的朋友,则 B 也是 A 的朋友
# symmetrical=False 表示关系可以是非对称的,例如 A 关注 B,但 B 不一定关注 A
connections = models.ManyToManyField('self', symmetrical=False, related_name='related_connections')
def __str__(self):
return self.name
当 symmetrical=False 时,关联管理器将同时存在于正向和反向关系中,你可以通过 related_name 进行反向查找。
person_a = Person.objects.create(name="张三")
person_b = Person.objects.create(name="李四")
person_c = Person.objects.create(name="王五")
# 张三与李四建立连接
person_a.connections.add(person_b)
# 查询张三连接的人
print(f"{person_a.name} 的连接: {[p.name for p in person_a.connections.all()]}") # 输出 ['李四']
# 查询与李四连接的人(通过 related_name 'related_connections')
print(f"与 {person_b.name} 相关联的人: {[p.name for p in person_b.related_connections.all()]}") # 输出 ['张三']
# 如果 symmetrical=True (默认值),则 person_b.connections.all() 也会包含 person_a
# 但因为 symmetrical=False,person_b.connections.all() 此时是空的,除非李四主动添加连接
print(f"{person_b.name} 主动连接的人: {[p.name for p in person_b.connections.all()]}") # 输出 []
symmetrical=False 使得自关联关系具有方向性,更适合表示"关注"、"导师-学生"这类不对等的关系。