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