Django выбирает только строки с повторяющимися значениями полей - PullRequest
81 голосов
/ 24 января 2012

Предположим, у нас есть модель в django, определенная следующим образом:

class Literal:
    name = models.CharField(...)
    ...

Поле имени не является уникальным и, следовательно, может иметь повторяющиеся значения.Мне нужно выполнить следующую задачу: выбрать все строки из модели, которые имеют хотя бы одно дублирующее значение поля name.

Я знаю, как это сделать с помощью простого SQL (может быть не лучшим решением):

select * from literal where name IN (
    select name from literal group by name having count((name)) > 1
);

Итак, можно ли выбрать это с помощью django ORM?Или лучшее решение SQL?

Ответы [ 5 ]

162 голосов
/ 24 января 2012

Попробуйте:

from django.db.models import Count
Literal.objects.values('name')
               .annotate(Count('id')) 
               .order_by()
               .filter(id__count__gt=1)

Это настолько близко, насколько вы можете получить с Django.Проблема в том, что это вернет ValuesQuerySet только с name и count.Однако затем вы можете использовать это для создания обычного QuerySet, передав его обратно в другой запрос:

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])
36 голосов
/ 13 апреля 2013

Это было отклонено как редактирование. Так что вот как лучше ответ

dups = (
    Literal.objects.values('name')
    .annotate(count=Count('id'))
    .values('name')
    .order_by()
    .filter(count__gt=1)
)

Это вернет ValuesQuerySet со всеми дублирующимися именами. Однако затем вы можете использовать это для создания обычного QuerySet, передав его обратно в другой запрос. ORM django достаточно умен, чтобы объединить их в один запрос:

Literal.objects.filter(name__in=dups)

Дополнительный вызов .values('name') после вызова аннотации выглядит немного странно. Без этого подзапрос не выполняется. Дополнительные значения заставляют ORM выбирать только столбец имени для подзапроса.

9 голосов
/ 24 января 2012

попробуйте использовать агрегирование

Literal.objects.values('name').annotate(name_count=Count('name')).exclude(name_count=1)
2 голосов
/ 09 марта 2017

Если вы используете PostgreSQL, вы можете сделать что-то вроде этого:

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))

Это приводит к довольно простому запросу SQL:

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
0 голосов
/ 30 декабря 2013

Если вы хотите привести только список имен, но не объекты, вы можете использовать следующий запрос

repeated_names = Literal.objects.values('name').annotate(Count('id')).order_by().filter(id__count__gt=1).values_list('name', flat='true')
...