【问题标题】:Django ORM: Filter by extra attributeDjango ORM:按额外属性过滤
【发布时间】:2011-05-19 21:17:20
【问题描述】:

我想通过连接字符串过滤一些数据库对象。

正常的 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, (...)

这是错误还是功能?我该如何解决这个问题?

谢谢。

【问题讨论】:

    标签: python django database orm


    【解决方案1】:

    这不是错误。 filter() 仅检查模型定义,因此它不会将 fullName 识别为声明的字段(因为它不是 - 它是查询中的额外参数)。

    您可以使用extra()fullName 添加到WHERE

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

    【讨论】:

    • 不幸的是,这不起作用。它仍然抱怨找不到 fullName 属性。不过,直接从对象中检索 fullName 属性是可行的。这个extra 方法是否会以某种方式覆盖管理器先前设置的额外属性?
    • 实际上这也不起作用:models.Person.objects.extra(select={'fullName': "CONCAT(firstName, ' ', name)"}, where=['fullName LIKE %s'], params=['Alexei%'])(它会抛出“'where 子句'中的未知列'fullName'”。
    • 我很抱歉三重评论。这种行为的原因是 Django 当然将 fullName 作为别名传递,这不适用于 MySQL。它可以在HAVING-clause 中工作,但 Django 似乎不支持它。相反,我使用了以下(不太漂亮)的折衷方案:models.Person.objects.extra(where=["CONCAT(firstName, ' ', name) LIKE %s"], params=['Alexei%'])。谢谢你的回答。
    • 嗯...我不知道别名在 MySQL 上不起作用。很好,你设法解决了。
    【解决方案2】:

    我通过实现自定义聚合函数解决了这个问题。 在这种情况下,我需要将各个字段连接到街道地址中,以便能够过滤/搜索匹配项。 以下聚合函数允许指定一个字段和一个或多个其他字段来执行 SQL CONCAT_WS。

    2015 年 8 月 3 日编辑:

    https://stackoverflow.com/a/19529861/3230522 收集到的细节更好的实现。如果在子查询中使用查询集,则先前的实现将失败。表名现在是正确的,尽管我注意到这仅适用于连接同一个表中的列。

    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
    

    【讨论】:

      【解决方案3】:

      建议的解决方案与下面代码中的 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'"])
      

      【讨论】:

        猜你喜欢
        • 2014-09-15
        • 2010-12-31
        • 2017-02-04
        • 1970-01-01
        • 2021-09-13
        • 2021-09-04
        • 2022-09-26
        • 1970-01-01
        • 2019-08-23
        相关资源
        最近更新 更多