34

假设我有一个带有邮政编码字段的地址模型。我可以使用以下行查找邮政编码以“123”开头的地址:

Address.objects.filter(postcode__startswith="123")

现在,我需要以“其他方式”进行搜索。我有一个带有 postcode_prefix 字段的地址模型,我需要检索所有 postcode_prefix 是给定代码前缀的地址,例如“12345”。因此,如果在我的数据库中我有 2 个带有 postcode_prefix = "123" 和 "234" 的地址,则只会返回第一个地址。

就像是:

Address.objects.filter("12345".startswith(postcode_prefix)) 

问题是这行不通。我能想出的唯一解决方案是对第一个字符执行过滤器,例如:

Address.objects.filter(postcode_prefix__startswith="12345"[0])

然后,当我得到结果时,制作一个正确过滤它们的列表理解,如下所示:

results = [r for r in results if "12345".startswith(r.postcode_prefix)]

有没有更好的方法在 django 中做到这一点?

4

6 回答 6

44

编辑:这并没有回答原始问题,而是如何用另一种方式表达查询。

我认为你试图用你的“类似”行做的事情正确地写成这样:

Address.objects.filter(postcode__startswith=postcode_prefix)
于 2012-08-10T15:59:46.903 回答
12

在 SQL 术语中,您想要实现的内容如下(“12345”是您要搜索的邮政编码):

SELECT *
FROM address
WHERE '12345' LIKE postcode_prefix||'%'

这不是一个真正的标准查询,我看不到在 Django 中仅使用 get()/filter() 实现这一目标的任何可能性。

但是,Django 提供了一种方法来提供额外的 SQL 子句extra()

postcode = '12345'
Address.objects.extra(where=["%s LIKE postcode_prefix||'%%'"], params=[postcode])

请参阅有关 extra() 的 Django 文档以获取更多参考。另请注意,extra 包含纯 SQL,因此您需要确保该子句对您的数据库有效。

希望这对你有用。

于 2012-08-10T16:53:03.397 回答
5

有点拗口,但您可以通过注释您的搜索值然后对其进行过滤来做到这一点。一切都在数据库中很快发生。

from django.db.models import Value as V, F, CharField

Address.objects.exclude(
    postcode_prefix=''
).annotate(
    postcode=Value('12345', output_field=CharField())
).filter(
    postcode__startswith=F('postcode_prefix')
)

exclude仅当可以postcode_prefix为空时才需要。这将产生一个 SQL like '%',它将匹配每个postcode.

我相信这些天你也可以通过一个很好的模板函数来做到这一点......但这对我来说已经足够干净了。

于 2018-06-06T14:06:54.257 回答
1

一个可能的替代方案。(不知道它与接受的解决方案相比如何,以列作为第二个参数,在执行时间)

q=reduce(lambda a,b:a|b, [Q(postcode__startswith=postcode[:i+1]) for i in range(len(postcode))])

因此,您生成所有前缀,或者它们一起生成......

于 2014-11-08T18:50:50.733 回答
1

您需要的原始 SQL 查询如下所示:

select * from postal_code_table where '1234567' like postal_code||'%'

此查询将从您的表中选择任何 postal_code,它是“1234567”的子字符串,并且必须从头开始,即:“123”、“1234”等。

现在要在 Django 中实现这一点,首选方法是使用自定义查找:

from django.db.models.fields import Field
from django.db.models import Lookup

@Field.register_lookup
class LowerStartswithContainedBy(Lookup):
    '''Postgres LIKE query statement'''
    lookup_name = 'istartswithcontainedby'

    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 f"LOWER({rhs}) LIKE LOWER({lhs}) || '%%'", params

现在您可以编写一个 django 查询,如下所示:

PostCode.objects.filter(code__istartswithcontainedby='1234567') 

同样,如果你只是寻找子字符串并且不需要startswith条件,只需将as_sql方法的返回行修改为以下内容:

return f"LOWER({rhs}) LIKE '%%' || LOWER({lhs}) || '%%'", params

更详细的解释见我的 git gist Django custom lookup

于 2021-06-28T01:19:00.920 回答
0

A. 如果不是问题https://code.djangoproject.com/ticket/13363,您可以这样做:

queryset.extra(select={'myconst': "'this superstring is myconst value'"}).filter(myconst__contains=F('myfield'))

也许,他们会解决一个问题并且它可以工作。

B. 如果不是问题 16731(对不起,没有提供完整的 url,没有足够的代表,请参阅上面的另一张票),您可以通过添加“.annotate”的字段进行过滤,并创建自定义聚合函数,例如: http:// coder.cl/2011/09/custom-aggregates-on-django/

C. 最后和成功。我已经设法使用以下猴子补丁来做到这一点:

  1. django.db.models.sql.Query.query_terms
  2. django.db.models.fields.Field.get_prep_lookup
  3. django.db.models.fields.Field.get_db_prep_lookup
  4. django.db.models.sql.where.WhereNode.make_atom

刚刚定义了自定义查找“_starts” ,它具有“ _startswith”的反向逻辑

于 2013-10-03T03:03:59.620 回答