2010-12-03 4 views
9

Je souhaite filtrer certains objets de base de données par une chaîne concaténée.Django ORM: Filtrer par attribut supplémentaire

La requête SQL normale serait:

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

Dans le modèle, j'ai créé un gestionnaire appelé 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) 

J'ai aussi Configuré dans mon modèle:

objects = managers.PersonObjects() 

L'accès à fullName fonctionne maintenant pour les objets uniques:

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

Mais cela ne fonctionne pas dans un filtre:

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

Est-ce un bug ou une fonctionnalité? Comment puis-je réparer cela?

Merci.

Répondre

16

Ce n'est pas un bug. filter() inspecte uniquement les définitions de modèle, donc il ne reconnaît pas fullName comme un champ déclaré (parce que ce n'est pas - c'est un argument supplémentaire dans une requête).

Vous pouvez ajouter le fullName-WHERE en utilisant extra():

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

Malheureusement, cela ne fonctionne pas. Il se plaint toujours de ne pas trouver l'attribut fullName. Récupérer l'attribut fullName d'un objet fonctionne directement. Est-ce que cette méthode 'extra' remplace en quelque sorte les attributs supplémentaires précédemment définis par le gestionnaire? –

+1

En fait cela ne fonctionne pas non plus: 'models.Person.objects.extra (select = {'fullName':" CONCAT (prénom, '', nom) "}, où = ['fullName LIKE% s'], params = ['Alexei%']) '(Il lance" Unknown column 'fullName' dans 'where clause' ". –

+7

Je suis désolé pour le triple commentaire. La raison de ce comportement est que Django passe bien sûr le fullName en tant que alias, qui ne fonctionne pas avec MySQL Cela fonctionnerait dans une clause 'HAVING', mais cela ne semble pas être supporté par Django, mais j'utilise le compromis suivant (pas si beau):' models. Person.objects.extra (où = ["CONCAT (prénom, '', nom) LIKE% s"], params = ['Alexei%']) '. Merci pour votre réponse. –

1

Je résolu ce problème en mettant en œuvre une fonction personnalisée globale. Dans ce cas, je devais concaténer des champs individuels en une adresse de rue pour pouvoir filtrer/rechercher des correspondances. La fonction d'agrégation suivante permet de spécifier un champ et un ou plusieurs autres pour exécuter un SQL CONCAT_WS.

Modifier le 3 août 2015:

Une meilleure mise en œuvre avec des détails glanés https://stackoverflow.com/a/19529861/3230522. L'implémentation précédente échouerait si le queryset était utilisé dans une sous-requête. Les noms de table sont maintenant corrects, bien que je note que ceci fonctionne juste pour la concaténation des colonnes de la même table.

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 solution proposée a très bien fonctionné avec les champs postgresql et JSONB dans le code ci-dessous. Seuls les enregistrements ayant la clé 'partenaire' sous le champ jsonb 'clé' sont renvoyés:

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'"]) 
Questions connexes