bigywater.tech
Django · 32

Q() 对象与复杂查询条件

Q 对象看起来只是把 OR 写成了 |,但用起来有几个反直觉的地方:空 Q 怎么算,链式 filter 和一个 filter 有什么差别,exclude 到底排除了什么。这篇每个都用数据说话。

空 Q
Q() | Q(x) 得到的是 Q(x)(空 Q 被忽略),reduce(or_, [], Q()) 对空列表就是「不过滤」
同一个 filter
多值关系上 filter(a, b) 要求同一个关联对象同时满足;filter(a).filter(b) 不要求
exclude
exclude 多值关系用 NOT EXISTS 子查询,含义是「没有任何一个满足」

组合与 SQL

实测 · 本机执行输出
Q(price__gt=15)                                  ['ann1', 'ann2', 'bob0', 'bob1', 'cat0'] 
     WHERE "lab_book"."price" > 15
Q(price__gt=15) | Q(title__startswith="a")       ['ann0', 'ann1', 'ann2', 'bob0', 'bob1', 'cat0'] 
     WHERE ("lab_book"."price" > 15 OR "lab_book"."title" LIKE a% ESCAPE '\')
Q(a) & Q(b) | Q(c)                               ['ann0', 'ann1', 'ann2', 'bob0'] 
     WHERE (("lab_book"."price" > 15 AND "lab_book"."price" < 50) OR "lab_book"."title" = ann0)
~Q(price__gt=15)                                 ['ann0'] 
     WHERE NOT ("lab_book"."price" > 15)
~(Q(a) | Q(b))                                   [] 
     WHERE NOT ("lab_book"."price" > 15 OR "lab_book"."title" = ann0)
filter(Q(a), Q(b)) 两个位置参数                        ['ann1', 'ann2', 'bob0'] 
     WHERE ("lab_book"."price" > 15 AND "lab_book"."price" < 50)

数据:ann 三本书(10/20/30),bob 两本(40/50),cat 一本(60),dan 没有书。上面每一行都是结果加对应的 WHERE 子句。

动态拼条件

实测 · 本机执行输出
用 &= 逐个拼: (AND: ('title__startswith', 'a'), ('price__gte', 20), ('price__lte', 999))
结果: ['ann1', 'ann2']
Q() 是空条件: ['ann0', 'ann1', 'ann2', 'bob0', 'bob1', 'cat0'] (不过滤)
Q() | Q(price__gt=50): ['cat0'] (空 Q 在 | 里被忽略,而不是「永远为真」)
OR 拼接 Q(price>50) | Q(title="x"): ['cat0']
对空列表用 reduce(or_) 要小心:functools.reduce(operator.or_, [], Q()) → <Q: (AND: )>

按用户选择的过滤条件拼 Q 是最常见的用法。注意 Q() 的「空」在 AND 里是单位元(没影响),在 OR 里被忽略而不是「永真」——用户一个条件都没选时你拼出来的 OR 会是空 Q,结果是不过滤而不是无结果。

多值关系:一个 filter 还是两个

实测 · 本机执行输出
filter(books__price__gt=25, books__title="ann0")            → []   INNER JOIN 次数: 1
filter(books__price__gt=25).filter(books__title="ann0")     → ['ann']   INNER JOIN 次数: 2
  ann 的书:ann0(10) ann1(20) ann2(30)。前者要求「同一本书」价格>25 且书名 ann0(不存在);后者是「某本书价格>25(ann2)」且「某本书书名 ann0」(可以是不同的书)

这个差别文档里有专门一节。简单记:同一个 filter() 里的多个条件作用于「同一个关联对象」;分成两个 filter() 就成了两个独立的关联对象。

exclude 不是取反

实测 · 本机执行输出
exclude(books__price__gt=45): ['ann', 'dan']
    WHERE NOT (EXISTS(SELECT 1 AS "a" FROM "lab_book" "U1" WHERE ("U1"."price" > 45 AND "U1"."author_id" = ("lab_author"."id")) LIMIT 1))
filter(~Q(books__price__gt=45)): ['ann', 'dan']
对照 filter(books__price__lte=45).distinct(): ['ann', 'bob']  ← dan 没有书,不出现;bob 有 40 也出现了(与 exclude 不同)

对多值关系,exclude(books__price__gt=45) 要求「没有任何一本」满足条件。想要「至少有一本 ≤ 45」应当用 filter + distinct。

分步演示:Q 的四个坑

Q 对象
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。SQLite;str(qs.query) 里的参数是直接拼接的显示值
    • 没有验证:PostgreSQL / MySQL 上 exclude 子查询的性能、Q 与 F / 表达式混用、对 JSONField 路径的 Q、大量 OR 条件的编译代价
    京ICP备2026067502号-1京公网安备11010502063487号