supposons que nous ayons un modèle en Django défini comme suit:
class Literal:
name = models.CharField(...)
...
Le champ de nom n'est pas unique et peut donc avoir des valeurs en double. J'ai besoin d'accomplir la tâche suivante: Sélectionnez toutes les lignes du modèle qui ont au moins une valeur en double du champ name
.
Je sais comment le faire en utilisant du SQL simple (ce n'est peut-être pas la meilleure solution):
select * from literal where name IN (
select name from literal group by name having count((name)) > 1
);
Alors, est-il possible de sélectionner ceci en utilisant Django ORM? Ou une meilleure solution SQL?
Essayer:
from Django.db.models import Count
Literal.objects.values('name')
.annotate(Count('id'))
.order_by()
.filter(id__count__gt=1)
C'est aussi proche que possible avec Django. Le problème est que cela renverra un ValuesQuerySet
avec seulement name
et count
. Cependant, vous pouvez ensuite l'utiliser pour construire un QuerySet
normal en le réintroduisant dans une autre requête:
dupes = Literal.objects.values('name')
.annotate(Count('id'))
.order_by()
.filter(id__count__gt=1)
Literal.objects.filter(name__in=[item['name'] for item in dupes])
Cela a été rejeté comme modification. Voici donc une réponse meilleure
dups = (
Literal.objects.values('name')
.annotate(count=Count('id'))
.values('name')
.order_by()
.filter(count__gt=1)
)
Cela renverra un ValuesQuerySet
avec tous les noms en double. Cependant, vous pouvez ensuite l'utiliser pour construire un QuerySet
normal en le réintroduisant dans une autre requête. L'ORM Django est suffisamment intelligent pour les combiner en une seule requête:
Literal.objects.filter(name__in=dups)
L'appel supplémentaire à .values('name')
après l'appel d'annotation semble un peu étrange. Sans cela, la sous-requête échoue. Les valeurs supplémentaires incitent l'ORM à sélectionner uniquement la colonne de nom pour la sous-requête.
essayez d'utiliser agrégation
Literal.objects.values('name').annotate(name_count=Count('name')).exclude(name_count=1)
Si vous utilisez PostgreSQL, vous pouvez faire quelque chose comme ceci:
from Django.contrib.postgres.aggregates import ArrayAgg
from Django.db.models import Func, Value
duplicate_ids = (Literal.objects.values('name')
.annotate(ids=ArrayAgg('id'))
.annotate(c=Func('ids', Value(1), function='array_length'))
.filter(c__gt=1)
.annotate(ids=Func('ids', function='unnest'))
.values_list('ids', flat=True))
Il en résulte cette requête SQL plutôt simple:
SELECT unnest(ARRAY_AGG("app_literal"."id")) AS "ids"
FROM "app_literal"
GROUP BY "app_literal"."name"
HAVING array_length(ARRAY_AGG("app_literal"."id"), 1) > 1
Si vous souhaitez générer uniquement une liste de noms mais pas des objets, vous pouvez utiliser la requête suivante
repeated_names = Literal.objects.values('name').annotate(Count('id')).order_by().filter(id__count__gt=1).values_list('name', flat='true')