我正在尝试使用 Django ORM 语法编写基于元组的搜索。
最终的sql语句应该是这样的:
SELECT * FROM mytable WHERE (field_a,field_b) IN ((1,2),(3,4));
我知道我可以使用 extra 关键字在 django 中实现这一点:
MyModel.objects.extra(
where=["(field_a, field_b) IN %s"],
params=[((1,2),(3,4))]
)
但是“extra”关键字在 django 的某个时候会被弃用,所以我想要一个纯 ORM/django 解决方案。
在网上搜索,我发现https://code.djangoproject.com/ticket/33015 和 Simon Charette 的评论,类似下面的代码片段可能没问题,但我无法让它工作。
from django.db.models import Func, lookups
class ExpressionTuple(Func):
template = '(%(expressions)s)'
arg_joiner = ","
MyModel.objects.filter(lookups.In(
ExpressionTuple('field_a', 'field_b'),
((1,2),(3,4)),
))
我正在使用 Django 3.2,但我不希望 Django 4.x 在这里有很大的不同。我的数据库后端是 posgresql,以防万一。
我可以想到一个解决方案,它将使用 Q
q = Q()
for (item1, item2) in [(1,2),(3,4)]:
q |= Q(field_one=item1, field_two=item2)
Mymodel.objects.filter(q)
另一个更强大的解决方案是这样的:
q = Q()
fields = ['field_one', 'field_two']
for item in [(1,2),(3,4)]:
q |= Q(**dict(zip(fields, item)))
在这里,我从项目列表中压缩字段和项目,然后将其作为解压字典传递给
Q
。它的实现与前面的示例类似,但这里的字段数可以很多,但不会增加代码中的行数。
from django.db.models import Func, lookups
class Tuple(Func):
function = '(%s)'
def as_sql(self, compiler, connection):
sql, params = super().as_sql(compiler, connection)
if sql.endswith(',)'):
sql = sql[:-2] + ')'
return sql, params
MyModel.objects.filter((Func('field_a', function='(%s)'), Func('field_b', function='(%s)'))__in=[(1,2),(3,4)])
有了这些更改,生成的 SQL 查询应该类似于:
SELECT * FROM mytable WHERE (field_a, field_b) IN ((1, 2), (3, 4))
为了参考和从 akshay-jain 提案中得到启发,我设法写了一些有用的东西:
from django.db.models import Func,Value
def ValueTuple(items):
return tuple(Value(i) for i in items)
class Tuple(Func):
function = ''
qs = (
MyModel.objects
.alias(a=Tuple('field_a', 'field_b'))
.filter(a__in=ValueTuple([(1, 2), (3, 4)])
)
它确实产生了一个像
这样的sql查询SELECT * FROM table WHERE (field_a,field_b) IN ((1,2),(3,4));
并且可以扩展到更多的领域,而不仅仅是两个。
虽然我没有做任何基准测试来将它与 Q 对象过滤进行比较。