3
votes

Django ORM supprime les groupes indésirables lors de l'annotation de plusieurs colonnes d'agrégation

Je souhaite créer une requête comme celle-ci dans django ORM.

SELECT COUNT(CASE WHEN "analyzer_profanecontent"."added_on" BETWEEN 2020-01-01 00:00:00+00:00 AND 2020-12-31 23:59:59.999999+00:00 THEN 1 ELSE NULL END) AS "numyear" FROM "analyzer_profanecontent" GROUP BY "analyzer_profanecontent"."id"

Voici la requête djang ORM que j'ai écrite

year_case = Case(When(added_on__year = today.year, then=1), output_field=IntegerField())

qs = (ProfaneContent.objects
                    .annotate(numyear=Count(year_case))
                    .values('numyear'))

C'est la requête qui est générée par django orm.

SELECT COUNT(CASE WHEN myCondition THEN 1 ELSE NULL end) as numyear
FROM myTable

Toutes les autres choses sont bonnes, mais django place un GROUP BY à la fin menant à plusieurs lignes et à une réponse incorrecte. Je ne veux pas du tout ça. À l'heure actuelle, il n'y a qu'une seule colonne mais je vais placer plus de colonnes de ce type.

MODIFIER EN FONCTION DES COMMENTAIRES J'utiliserai la variable qs pour obtenir les valeurs de la façon dont mes classifications ont été faites dans l'année, le mois, la semaine en cours.

MISE À JOUR Sur la base des commentaires et des réponses que j'obtiens ici, permettez-moi de clarifier. Je veux faire cela uniquement à la fin de la base de données (en utilisant évidemment Django ORM et non RAW SQL). C'est une simple requête SQL. Faire quoi que ce soit à la fin de Python sera inefficace car les données peuvent être trop volumineuses. C'est pourquoi je veux que la base de données m'obtienne la somme des enregistrements en fonction de la condition CASE. J'ajouterai d'autres colonnes de ce type à l'avenir, donc quelque chose comme len () ou .count ne fonctionnera pas.

Je veux juste créer la requête mentionnée ci-dessus en utilisant Django ORM (sans GROUP BY automatiquement ajouté).


5 commentaires

Je ne sais pas trop comment vous prévoyez d'utiliser cette variable qs


C'est probablement moi, mais ... n'est-ce pas juste une manière sophistiquée d'écrire ProfaneContent.objects.filter(added_on__year=today.year).cou‌​nt() ?


Oui, mais comme je l'ai mentionné, je vais également ajouter d'autres colonnes. Alors compter ne fonctionnera pas là-bas. Ce sont également les solutions de contournement. Je veux simplement une requête ORM django.


Oui, mais compter comme une annotation dans ce scénario n'a aucun sens. Comme @hynekcer le démontre, cela ne renverra jamais qu'une seule ligne. Annoter est utilisé pour ajouter quelque chose à chaque ligne, pas un groupe de lignes.


Peut-être que la confusion vient de votre nom et de votre utilisation du compte. Voulez-vous réellement annoter chaque ligne avec un booléen indiquant qu'il s'agit de l'année en cours? Car alors débarrassez-vous du comte et nommez-le correctement.


4 Réponses :


0
votes

Qu'en est-il d'une compréhension de liste:

# Something like this 
len([pro for pro in profane if pro.numyear=today.year])
# get all the objects
profane = ProfaneContent.objects.all()

si le nombre d'années est égal, il l'ajoutera à la liste, donc au et vous pouvez vérifier le len ()

pour obtenir le décompte

J'espère que cela vous sera utile!


3 commentaires

Cela me donnera certainement la réponse que je veux, mais ce n'est pas ainsi que cela devrait être fait. Je serais mieux si nous pouvons faire la même chose avec une requête SQL uniquement. Laissez la base de données gérer ces choses.


ce serait mieux ProfaneContent.objects.filter (numyear = today.year) .count () . Normalement, je n'aime pas écrire des requêtes SQL et je conseille de ne pas utiliser de requêtes SQL sauf si vous devez absolument le faire car l'ORM peut faire tout ce dont vous avez besoin.


Je suppose que tu me trompes. En disant SQL, je voulais dire laisser l'ORM du django gérer la génération de requêtes SQL et laisser les calculs se produire à la fin de la base de données. Aussi, comme je l'ai mentionné, je dois faire la même chose pour le mois et la semaine en cours également. Donc, si j'utilise «filtre», j'aurais besoin de faire 3 requêtes et cela conduira à 3 hits de base de données différents. Et c'est affreux.



-2
votes

C'est ainsi que je l'écrirais en SQL.

SELECT SUM(CASE WHEN myCondition THEN 1 ELSE 0 END) as numyear
FROM myTable
GROUP BY SUM(CASE WHEN myCondition THEN 1 ELSE 0 END)

Si vous avez l'intention d'utiliser d'autres éléments dans la clause SELECT, je vous recommanderais également d'utiliser un groupe par qui ressemblerait à ceci:

SELECT SUM(CASE WHEN myCondition THEN 1 ELSE 0 END) as numyear
FROM myTable

SELECT 
    SUM(CASE WHEN "analyzer_profanecontent"."added_on" 
                 BETWEEN 2020-01-01 00:00:00+00:00 
                     AND 2020-12-31 23:59:59.999999+00:00 
             THEN 1 
             ELSE 0 
         END) AS "numyear" 
FROM "analyzer_profanecontent" 
GROUP BY "analyzer_profanecontent"."id"


2 commentaires

Je sais comment le faire en SQL. De plus, comme vous l'avez mentionné, la requête a un groupe par son ID. Et c'est incorrect dans mon cas. Cela générera une somme pour chaque enregistrement et cela n'a aucun sens.


J'ai déjà mentionné que je voulais supprimer ce groupe par clause. Et aussi je veux savoir comment le faire avec Django ORM pas SQL brut.



2
votes

Si vous avez besoin de résumer une seule ligne, vous devez utiliser une méthode .aggregate () au lieu d'annotate ().

SELECT
  COUNT(CASE WHEN myCondition THEN 1 ELSE NULL end) as numyear
  -- and more possible aggregated expressions
FROM myTable

Vous obtenez un dictionnaire simple de colonnes de résultats:

>>> result
{'numyear': 7, ...}

La requête SQL générée est sans groupes, exactement comme requis:

result = ProfaneContent.objects.aggregate(
    numyear=Count(year_case),
    # ... more aggregated expressions are possible here
)


0 commentaires

6
votes

Lors de l'utilisation d'agrégats dans les annotations, django doit avoir une sorte de regroupement, sinon il utilise par défaut la clé primaire. Donc, vous devez utiliser .values() avant .annotate() . Veuillez consulter la documentation de django .

Mais pour supprimer complètement un groupe, vous pouvez utiliser une valeur statique et django est assez intelligent pour le supprimer complètement, vous obtenez donc votre résultat en utilisant une requête ORM comme celle-ci:

year_case = Case(When(added_on__year = today.year, then=1), output_field=IntegerField())

qs = (ProfaneContent.objects
                    .annotate(dummy_group_by = Value(1))
                    .values('dummy_group_by')
                    .annotate(numyear=Count(year_case))
                    .values('numyear'))


3 commentaires

Merci un million. C'était la chose que je cherchais. Exactement au point.


Je donne +1 parce que cela fonctionne aussi, mais je ne vois aucun cas d'utilisation où une méthode annotate () avec un groupe factice group_by pourrait être meilleure qu'une méthode aggregate () (voir la réponse ci-dessous). Pouvez-vous expliquer comment cela pourrait être meilleur ou pourquoi une méthode aggregate () existe si elle peut être remplacée par annotate () par un groupe factice?


@hynekcer Oui, le cas d'utilisation est discutable. Problème XY typique.