当前位置:首页 > 技术 > 正文内容

Django ORM 数据查询与操作指南

访客 技术 2026年6月24日 3

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 是否包含任何结果,返回 TrueFalse。通常比 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) # 也可以添加ID
            
  • create(**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 使得自关联关系具有方向性,更适合表示"关注"、"导师-学生"这类不对等的关系。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。