Django ORM:按额外属性过滤

13 投票
3 回答
26002 浏览
提问于 2025-04-16 07:59

我想通过一个拼接的字符串来过滤一些数据库对象。

正常的SQL查询应该是这样的:

SELECT concat(firstName, ' ', name) FROM person WHERE CONCAT(firstName, ' ', name) LIKE "a%";

在我的模型中,我创建了一个叫做PersonObjects的管理器:

class PersonObjects(Manager):
    attrs = { 
        'fullName': "CONCAT(firstName, ' ', name)"
    }   

    def get_query_set(self):
        return super(PersonObjects, self).get_query_set().extra(
            select=self.attrs)

我在我的模型中也进行了这样的配置:

objects = managers.PersonObjects()

现在,访问fullName对于单个对象是可以正常工作的:

>>> p = models.Person.objects.get(pk=4)
>>> p.fullName
u'Fred Borminski'

但是在过滤的时候就不行了:

>>> p = models.Person.objects.filter(fullName__startswith='Alexei')
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/usr/lib/python2.7/site-packages/django/db/models/manager.py", line 141, in filter
    return self.get_query_set().filter(*args, **kwargs)
  File "/usr/lib/python2.7/site-packages/django/db/models/query.py", line 550, in filter
    return self._filter_or_exclude(False, *args, **kwargs)
  File "/usr/lib/python2.7/site-packages/django/db/models/query.py", line 568, in _filter_or_exclude
    clone.query.add_q(Q(*args, **kwargs))
  File "/usr/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1128, in add_q
    can_reuse=used_aliases)
  File "/usr/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1026, in add_filter
    negate=negate, process_extras=process_extras)
  File "/usr/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1191, in setup_joins
    "Choices are: %s" % (name, ", ".join(names)))
FieldError: Cannot resolve keyword 'fullName' into field. Choices are: firstName, gender, name, (...)

这是个bug还是一个功能?我该怎么解决这个问题呢?

谢谢。

3 个回答

0

这个提议的解决方案在下面的代码中与postgresql和JSONB字段配合得很好。只有那些在'key'这个jsonb字段下有'partner'这个键的记录会被返回:

query_partner = "select key->>'partner' from accounting_subaccount " \
                "where accounting_subaccount.id = subaccount_id and key ? 'partner'"
qs = queryset.extra(select={'partner': query_partner}, where=["key ? 'partner'"])
1

我通过实现一个自定义的聚合函数解决了这个问题。在这个情况下,我需要把单独的字段连接成一个街道地址,以便能够进行过滤或搜索匹配的内容。

下面这个聚合函数可以让你指定一个字段和一个或多个其他字段,来执行一个SQL的CONCAT_WS操作。

2015年8月3日更新:

这是一个更好的实现,细节来自于这个链接。之前的实现如果在子查询中使用会失败。现在表名是正确的,不过我注意到这个方法只适用于连接同一张表中的列。

from django.db.models import Aggregate
from django.db.models.sql.aggregates import Aggregate as SQLAggregate

class SqlAggregate(SQLAggregate):
    sql_function = 'CONCAT_WS'
    sql_template = u'%(function)s(" ", %(field)s, %(columns_to_concatenate)s)'

    def as_sql(self, qn, connection):
        self.extra['columns_to_concatenate'] = ', '.join(
        ['.'.join([qn(self.col[0]), qn(c.strip())]) for c in self.extra['with_columns'].split(',')])
        return super(SqlAggregate, self).as_sql(qn, connection)

class Concatenate(Aggregate):
    sql = SqlAggregate

    def __init__(self, expression, **extra):
        super(Concatenate, self).__init__(
            expression,
            **extra)

    def add_to_query(self, query, alias, col, source, is_summary):

        aggregate = self.sql(col,
                         source=source,
                         is_summary=is_summary,
                         **self.extra)

        query.aggregates[alias] = aggregate
24

这不是一个错误。filter() 这个函数只会检查模型的定义,所以它不把 fullName 当作一个已声明的字段来看待(因为它其实不是,它只是查询中的一个额外参数)。

你可以通过 extra()fullName 加到 WHERE 里:

Person.objects.extra(where=["fullName LIKE %s"], params=["Alexei%"])

撰写回答