Bienvenue sur IndexError.

Ici vous pouvez poser des questions sur Python et le Framework Django.

Mais aussi sur les technos front comme React, Angular, Typescript et Javascript en général.

Consultez la FAQ pour améliorer vos chances d'avoir des réponses à vos questions.

Somme de 2 champs agrégés en Django

+1 vote

J'ai besoin sous Django (1.7+) de faire un tri sur la somme de deux champs qui sont déjà des résultats d’agrégations.

Voici mon QuerySet :

    def get_by_code(self, code_produit, code_contrat=None):
        filters = Q(produit__code=code_produit, contrat_id__isnull=True)
        order_by = ('-poids', )
        if code_contrat:
            filters |= Q(produit__code=code_produit, contrat__code=code_contrat)
            order_by = ('contrat__code', ) + order_by
        tarifs = Tarif.objects.select_related('produit', 'contrat', 'groupe', 'fonction').filter(filters).order_by(*order_by)
        tarifs = tarifs.prefetch_related('groupe__conditions').annotate(nb_conditions=Count('groupe__conditions'))
        tarifs = tarifs.prefetch_related('groupe__compositions').annotate(nb_compositions=Count('groupe__compositions'))
        tarifs = tarifs.annotate(poids_conditions=Sum('groupe__conditions__poids'), poids_compositions=Sum('groupe__compositions__poids'))
        # Ici la somme de poids_conditions et poids_compositions
        log.info(tarifs.query)
        return tarifs

Chaque condition et chaque composition dans mon exemple possède un poids pour prioriser les uns par rapport aux autres, je ne me sers de la somme des deux que pour remonter les tarifs du plus important au moins important.

Je voudrais, si possible, ne pas avoir affaire à extra car mon application doit fonctionner sur Oracle et PostgreSQL.

demandé 16-Fev-2015 par debnet (1,002 points)

Si j'ai bien compris la question, tu veux retourner une ordered list de tarifs dont le resultat est la somme de poids_conditions et poids_compositions?

Presque. Je veux un queryset qui retourne les tarifs triés par la somme de poids_conditions et poids_compositions de manière décroissante. J'ai énormément de données de tarifs, je voudrais éviter d'avoir à évaluer le queryset complet.

1 Réponse

+2 votes

Si c'est pour un order_by, je dirais qu'il te faut utiliser extra. Un truc du genre :

tarifs.extra(
    select={'poids_total':'groupe__conditions__poids' + 'groupe__compositions__poids'},
    order_by=('poids_total',)
)
répondu 30-Mar-2015 par Sam (4,974 points)
edité 1-Avr-2015 par Sam

Merci pour la réponse, je teste ça dans la journée.
Par contre qu'est ce qu'il advient de mon tri principal sur les contrats qui est prioritaire sur le poids s'il est fourni ?

Apparemment ça ne fonctionne pas, les champs ne sont pas reconnus.

{
   message: "ERREUR:  la colonne « poids_conditions » n'existe pas\nLINE 1: SELECT (poids_conditions + poids_compositions) AS "poids", "...\n                ^\n",
   trace: [
      "Traceback (most recent call last):",
      "  File "C:\\VirtualEnvs\\nouvelleoffre\\lib\\site-packages\\django\\db\\backends\\utils.py", line 65, in execute",
      "    return self.cursor.execute(sql, params)",
      "psycopg2.ProgrammingError: ERREUR:  la colonne « poids_conditions » n'existe pas",
      "LINE 1: SELECT (poids_conditions + poids_compositions) AS "poids", "...",
      "                ^",
      "",
      "",
      "The above exception was the direct cause of the following exception:",
      "",
      "Traceback (most recent call last):",
      "  File "C:\\Git\\nouvelleoffre\\nouvelleoffre\\apps\\tarificateur\\api\\api_views.py", line 19, in calculer",
      "    resultat = tarificateur.calculer()",
      "  File "C:\\Git\\nouvelleoffre\\nouvelleoffre\\apps\\common\\utils.py", line 65, in wrapped",
      "    result = func(*args, **kwargs)",
      "  File "C:\\Git\\nouvelleoffre\\nouvelleoffre\\apps\\tarificateur\\tarificateur.py", line 148, in calculer",
      "    resultat = self._calculer_contexte(ctx)",
      "  File "C:\\Git\\nouvelleoffre\\nouvelleoffre\\apps\\tarificateur\\tarificateur.py", line 274, in _calculer_contexte",
      "    tarifs = self._tarifs.get(contexte=ctx_local)",
      "  File "C:\\Git\\nouvelleoffre\\nouvelleoffre\\apps\\tarificateur\\helpers.py", line 443, in get",
      "    for tarif in tarifs:",
      "  File "C:\\VirtualEnvs\\nouvelleoffre\\lib\\site-packages\\django\\db\\models\\query.py", line 141, in __iter__",
      "    self._fetch_all()",
      "  File "C:\\VirtualEnvs\\nouvelleoffre\\lib\\site-packages\\django\\db\\models\\query.py", line 966, in _fetch_all",
      "    self._result_cache = list(self.iterator())",
      "  File "C:\\VirtualEnvs\\nouvelleoffre\\lib\\site-packages\\django\\db\\models\\query.py", line 265, in iterator",
      "    for row in compiler.results_iter():",
      "  File "C:\\VirtualEnvs\\nouvelleoffre\\lib\\site-packages\\django\\db\\models\\sql\\compiler.py", line 700, in results_iter",
      "    for rows in self.execute_sql(MULTI):",
      "  File "C:\\VirtualEnvs\\nouvelleoffre\\lib\\site-packages\\django\\db\\models\\sql\\compiler.py", line 786, in execute_sql",
      "    cursor.execute(sql, params)",
      "  File "C:\\VirtualEnvs\\nouvelleoffre\\lib\\site-packages\\django\\db\\backends\\utils.py", line 81, in execute",
      "    return super(CursorDebugWrapper, self).execute(sql, params)",
      "  File "C:\\VirtualEnvs\\nouvelleoffre\\lib\\site-packages\\django\\db\\backends\\utils.py", line 65, in execute",
      "    return self.cursor.execute(sql, params)",
      "  File "C:\\VirtualEnvs\\nouvelleoffre\\lib\\site-packages\\django\\db\\utils.py", line 94, in __exit__",
      "    six.reraise(dj_exc_type, dj_exc_value, traceback)",
      "  File "C:\\VirtualEnvs\\nouvelleoffre\\lib\\site-packages\\django\\utils\\six.py", line 658, in reraise",
      "    raise value.with_traceback(tb)",
      "  File "C:\\VirtualEnvs\\nouvelleoffre\\lib\\site-packages\\django\\db\\backends\\utils.py", line 65, in execute",
      "    return self.cursor.execute(sql, params)",
      "django.db.utils.ProgrammingError: ERREUR:  la colonne « poids_conditions » n'existe pas",
      "LINE 1: SELECT (poids_conditions + poids_compositions) AS "poids", "...",
      "                ^",
      "",
      ""
   ],
   type: "ProgrammingError"
}

Dans ta question, je vois pas "poids_conditions" (je les ai pris de tes commentaires), ils utilisés où ?

Dans la dernière instruction du queryset :

tarifs = tarifs.annotate(poids_conditions=Sum('groupe__conditions__poids'), poids_compositions=Sum('groupe__compositions__poids'))

J'ai changé la réponse pour m'adapter aux bons noms.

...