Django ORM: фильтр по дополнительному атрибуту - PullRequest
11 голосов
/ 03 декабря 2010

Я хочу отфильтровать некоторые объекты базы данных по объединенной строке.

Обычный запрос 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, (...)

Это ошибка или особенность? Как я могу это исправить?

Спасибо.

Ответы [ 3 ]

18 голосов
/ 03 декабря 2010

Это не ошибка. filter() проверяет только определения модели, поэтому он не распознает fullName как объявленное поле (потому что это не так - это дополнительный аргумент в запросе).

Вы можете добавить fullName к WHERE, используя extra():

Person.objects.extra(where=["fullName LIKE %s"], params=["Alexei%"])
1 голос
/ 31 июля 2015

Я решил эту проблему, реализовав собственную функцию Aggregate. В этом случае мне нужно было объединить отдельные поля в уличный адрес, чтобы иметь возможность фильтровать / искать совпадения. Следующая агрегатная функция позволяет указать поле и одно или несколько других для выполнения SQL CONCAT_WS.

Изменить 3 августа 2015 г .:

Лучшая реализация с подробностями, полученными из 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
0 голосов
/ 24 апреля 2016

Предлагаемое решение прекрасно работает с полями postgresql и JSONB в приведенном ниже коде.Возвращаются только те записи, которые имеют ключ партнера под полем jsonb «ключ»:

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'"])
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...