Django ORM 查询数据怎么写过滤条件

文章导读
Django ORM 写过滤条件,核心是用 queryset.filter() 和 exclude() 把 Python 表达式翻译成 SQL 的 WHERE 子句,字段名后缀决定比较运算符,链式调用决定条件之间是 AND 还是 OR。动手之前先确认两件事:你查的是哪个模型、字段类型是什么;条件之间是“同时满足”还是“满足其一”。这两点判断错,写得再顺手也会查错数据。
📋 目录
  1. 壹 先确认当前场景和目标
  2. 贰 按这个顺序处理
  3. 叁 改完后怎么验证
  4. 肆 容易忽略的边界
A A

Django ORM 写过滤条件,核心是用 queryset.filter() 和 exclude() 把 Python 表达式翻译成 SQL 的 WHERE 子句,字段名后缀决定比较运算符,链式调用决定条件之间是 AND 还是 OR。动手之前先确认两件事:你查的是哪个模型、字段类型是什么;条件之间是“同时满足”还是“满足其一”。这两点判断错,写得再顺手也会查错数据。

过滤条件建议按“模型字段 → 运算符后缀 → 条件组合方式 → 是否触发额外查询”的顺序写。普通等值用 filter(field=value),范围、模糊、空值分别用 __gte、__icontains、__isnull 等后缀;多条件链式默认为 AND,OR 需要用 Q 对象。写完后用 str(queryset.query) 看生成的 SQL,再用 .count() 或实际返回行数比对预期,确认没有漏掉空值、类型转换和跨关联表重复行的问题。

先确认当前场景和目标

在写过滤条件前,需要先把问题说清楚,否则容易写出“能跑但结果不对”的查询。可以先确认以下几项:

  • 查哪个模型、哪些字段:字段是 CharField、IntegerField、DateTimeField 还是外键,直接决定能用哪些后缀。对整数列用 __icontains 通常不会报错但语义不对。
  • 条件之间是什么关系:是“用户 ID 等于 3 且状态为已支付”,还是“状态为已支付或已退款”。前者用连续 filter() 即可,后者必须用 Q 组合。
  • 是否涉及关联表:跨外键或 ManyToMany 过滤时,结果可能出现重复行,需要判断是否要加 distinct()。
  • 空值和默认值怎么算:数据库里 NULL 不参与普通等值比较,filter(x=0) 不会匹配 NULL,这类业务空值要单独用 __isnull 或 __isnull=False 处理。

如果这些还没确认,建议先打印模型定义或看 Model._meta.fields,比凭印象拼字段名可靠。

Django ORM 查询数据怎么写过滤条件

按这个顺序处理

第一步,用最基本的等值条件把查询跑通,再逐步加后缀。下面这段是常见骨架,字段名和值按实际模型替换:

from django.db.models import Q
from datetime import timedelta
from django.utils import timezone

# 1. 基础等值 + 范围 + 模糊
qs = Order.objects.filter(
    status="paid",
    amount__gte=100,
    created_at__gte=timezone.now() - timedelta(days=7),
)

# 2. 排除某类记录,exclude 也是条件
qs = qs.exclude(note__isnull=True)

# 3. OR 条件用 Q,最后传给 filter
qs = Order.objects.filter(
    Q(status="paid") | Q(status="refunded"),
    user_id=3,
)

# 4. 跨关联表过滤
qs = Order.objects.filter(items__sku="A-001").distinct()

# 5. 看实际生成的 SQL
print(qs.query)

第二步,判断条件组合方式。链式 filter(a=1).filter(b=2) 和 filter(a=1, b=2) 在单表普通条件下结果一致,但涉及到多值关联表时,分开写会变成两次 JOIN,语义可能与预期不同。需要“同一关联对象同时满足多个条件”时,建议合并到同一个 filter() 里,或明确用 Q 构造。

Django ORM 查询数据怎么写过滤条件

第三步,注意 exclude() 和 filter() 混合时的逻辑。对可空外键使用 exclude(rel__field=value),某些版本的 Django 生成的 SQL 会把 NULL 行排除掉,通常需要显式补 Q(rel__isnull=True)。这类细节建议直接用 print(qs.query) 核对,不要只看 Python 层判断。

第四步,控制返回列和数量。只需要少数字段时用 .values() 或 .only(),但要清楚 .values() 返回的是字典而不是模型实例,后续属性访问方式要相应调整。

Django ORM 查询数据怎么写过滤条件

改完后怎么验证

验证动作建议按下面顺序做,每一步都有可观察的信号:

  1. 打印 SQL:执行 print(qs.query),确认 WHERE 子句里出现了预期的字段和运算符,参数占位符数量与传入值一致。
  2. 比对行数:用 qs.count() 和手工用管理后台或另一条简单查询得到的结果对一下。如果数量明显偏大,优先怀疑跨关联表导致的重复行。
  3. 检查空值记录:拿一条字段为 NULL 的记录,看它是否被正确包含或排除。这是过滤条件最容易出偏差的地方。
  4. 检查 OR 边界:把 Q 条件中仅满足一项的数据拿出来,确认它们确实出现在结果里,而不是被外层 AND 条件悄悄过滤掉。

如果查询和写入在同一业务流里,建议在测试数据库中用固定的几条样本记录跑一遍,预期信号是返回的记录集合与手工构造的预期集合完全一致,包括顺序无关、数量一致、边界记录在内。

容易忽略的边界

  • 字符串大小写与排序规则:__contains 和 __icontains 在不同数据库上的行为不完全一致,SQLite 对大小写不敏感,部分配置下的 MySQL 或 PostgreSQL 则取决于排序规则,需要结合环境确认。
  • 时区:对 DateTimeField 用日期范围过滤时,USE_TZ 配置会影响比较结果。通常建议传入带时区的 datetime,不要手写无时区的字符串。
  • 索引与字段类型:对文本字段做模糊匹配可能不走索引,过滤条件写对了但查询变慢是另一类问题,不要把它和条件正确性混在一起判断。
  • 版本差异:exclude() 对 NULL 的处理、Q 对象嵌套写法在不同 Django 大版本上存在细微差别,升级后建议重跑关键查询。不确定时先回退到显式 Q 组合,通常更容易看懂。
  • 不适用条件:需要复杂聚合或窗口函数时,单靠 filter() 表达会变得难读,这时更适合用 annotate 配 FilteredRelation 或直接写原生 SQL,不要硬塞进一个过滤链里。