在Postgres DB中使用大量id的Django查询过滤器

时间:2017-10-13 16:50:17

标签: arrays django postgresql parameter-passing

我想将Django中的查询传递给我的PostgreSQL数据库。当我使用大量的id过滤我的查询时,查询非常慢并且最多可达70秒。

在找到答案后,我看到this post为我的问题提供了解决方案,只需在ARRAY [ids]中更改IN语句中的VALUES (id1), (id2), ...

我在pgadmin中使用原始查询测试了解决方案,查询从70秒到300毫秒......

如何在Django中执行相同的命令(即不使用id数组,但使用VALUES查询)?

4 个答案:

答案 0 :(得分:5)

我发现了使用custom lookup

建立在@ erwin-brandstetter答案上的解决方案
from django.db.models import Lookup
from django.db.models.fields import Field

@Field.register_lookup
class EfficientInLookup(Lookup):

    lookup_name = "ineff"

    def as_sql(self, compiler, connection):
        lhs, lhs_params = self.process_lhs(compiler, connection)
        rhs, rhs_params = self.process_rhs(compiler, connection)
        params = lhs_params + rhs_params
        return "%s IN (SELECT unnest(%s))" % (lhs, rhs), params

这允许像这样进行过滤:

MyModel.objects.filter(id__ineff=<list-of-values>)

答案 1 :(得分:2)

诀窍是将 数组 转换为 以某种方式

而不是(这种形式只适用于短阵列):

SELECT *
FROM   tbl t
WHERE  t.tbl_id = ANY($1);
-- WHERE  t.tbl_id IN($1);  -- equivalent

$1是数组参数。

你仍然可以像你拥有的那样传递 数组 ,但是不需要和加入。像:

SELECT *
FROM   tbl t
JOIN   unnest($1) arr(id) ON arr.id = t.tbl_id;

或者你也可以保留你的查询,但用不需要它的子查询替换数组:

SELECT * FROM tbl t
WHERE  t.tbl_id = ANY (SELECT unnest($1));

或者:

SELECT * FROM tbl t
WHERE  t.tbl_id IN    (SELECT unnest($1));

与使用VALUES表达式传递 相同的效果。但是传递数组通常要简单得多。

详细说明:

答案 2 :(得分:1)

这是你问的第一件事吗?

relation_list = list(ModelA.objects.filter(id__gt=100))
obj_query = ModelB.objects.filter(a_relation__in=relation_list)

那将是&#34; IN&#34;命令,因为您首先通过将relation_list投射到list来评估obj_query.query,然后在第二次查询中使用它。

如果你做的完全相同,Django只会进行一次查询,并为你做SQL优化。所以它应该更有效率。

如果您对在幕后发生的事情感到好奇,您始终可以看到您将使用{{1}}执行的SQL命令。

希望回答这个问题,对不起,如果它没有。

答案 3 :(得分:0)

要使自定义查找“ ineff”工作非常麻烦。 我可能已经解决了,但希望得到Django和Postgres专家的一些验证。

1)在ForeignKey字段(模型B)上“直接”使用

ModelA.objects.filter(ModelB__ineff=queryset_ModelB)

引发以下异常: “相关字段的查询无效:无效”

ForeignKey字段不能与自定义查找一起使用 这里报告了类似的问题: Custom lookup is not being registered in Django

2)在相关模型(ModelB.id)的pk字段上“间接”使用它

ModelA.objects.filter(ModelB__id__ineff=queryset_ModelB.values_list('id', flat=True))

引发以下异常: “只能将列表(而不是“元组”)连接到列表”

看着Django Traceback,我注意到rhs_params是一个元组。 但是,我们尝试在自定义查找中将其添加到lhs_params(列表)中。

因此我改变了:

params = lhs_params + rhs_params

进入:

params = lhs_params + list(rhs_params)

3)然后我收到了Postgres错误(至少我已经通过了Django ORM) “函数unnest(uuid)不存在” “提示:没有函数与给定的名称和参数类型匹配。您可能需要添加显式类型转换。”

我显然是通过更改sql来解决的:

来自:

return "%s IN (SELECT unnest(%s))" % (lhs, rhs), params

收件人:

return "%s IN (SELECT unnest(ARRAY(%s)))" % (lhs, rhs), params    

因此,我的最终as_sql方法如下所示:

def as_sql(self, compiler, connection):
    lhs, lhs_params = self.process_lhs(compiler, connection)
    rhs, rhs_params = self.process_rhs(compiler, connection)
    params = lhs_params + list(rhs_params)
    return "%s IN (SELECT unnest(ARRAY(%s)))" % (lhs, rhs), params

它似乎有效,并且确实比in__更快(已在Postgres中使用EXPLAIN ANALYZE测试)。 但是我想得到专家的验证,也许是欧文·布兰德斯特(Erwin Brandstetter)? 感谢您的输入。