2010-12-03 19 views
9

Quiero filtrar algunos objetos de base de datos por una cadena concatenada.Django ORM: filtro por atributo adicional

La consulta SQL normal sería:

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

En el modelo, he creado un gerente llamó 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) 

También he configurado esto en mi modelo:

objects = managers.PersonObjects() 

Ahora el acceso a fullName funciona para objetos individuales:

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

Pero no funciona en un filtro:

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

Es esto un error o de una pieza? ¿Cómo puedo arreglar esto?

Gracias.

Respuesta

16

No es un error. filter() solo inspecciona las definiciones del modelo, por lo que no reconoce fullName como un campo declarado (porque no lo es, es un argumento adicional en una consulta).

Puedes añadir el fullName-WHERE usando extra():

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

Lamentablemente, esto no funciona. Todavía se queja de no encontrar el atributo fullName. Sin embargo, la recuperación del atributo fullName de un objeto funciona directamente. ¿Este método 'extra' sobrescribe de alguna manera los atributos adicionales previamente establecidos del administrador? –

+1

En realidad, esto tampoco funciona: 'models.Person.objects.extra (select = {'fullName':" CONCAT (firstName, '', name) "}, donde = ['fullName LIKE% s'], params = ['Alexei%']) '(Lanza" Columna desconocida 'fullName' en 'where clause' ". –

+7

Lo siento por el comentario triple. La razón de este comportamiento es que Django, por supuesto, pasa el nombre completo como un alias, que no funciona con MySQL. Funcionaría en una cláusula 'HAVING', pero Django no parece apoyarlo. En su lugar, estoy usando los siguientes (no tan bonitos) modelos de compromiso:'. Person.objects.extra (where = ["CONCAT (firstName, '', name) LIKE% s"], params = ['Alexei%']) 'Gracias por su respuesta. –

1

que resolvieron este mediante la implementación de una función personalizada agregada. En este caso, necesitaba concatenar campos individuales en una dirección para poder filtrar/buscar coincidencias. La siguiente función de agregado permite especificar un campo y uno o más para realizar un SQL CONCAT_WS.

Datos 3 Ago 2015:

Una mejor aplicación con datos recogidos de https://stackoverflow.com/a/19529861/3230522. La implementación anterior fallaría si el conjunto de consulta se usara en una subconsulta. Los nombres de la tabla ahora son correctos, aunque observo que esto solo funciona para la concatenación de columnas de la misma tabla.

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

La solución propuesta funcionó muy bien con los campos postgresql y JSONB en el siguiente código. Solo se devuelven los registros que tienen la clave 'partner' debajo del campo jsonb 'clave':

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'"]) 
Cuestiones relacionadas