Django выбирает только строки с двойными значениями поля
предположим, что мы имеем модель в 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
);
Итак, можно ли это выбрать с помощью ORM django? Или лучше SQL-решение?
Ответы
Ответ 1
Try:
from django.db.models import Count
Literal.objects.values('name')
.annotate(Count('id'))
.order_by()
.filter(id__count__gt=1)
Это как можно ближе к Django. Проблема в том, что это вернет a 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])
Ответ 2
Это было отклонено как редактирование. Так вот, это как лучший ответ
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 выбирать только столбец имени для подзапроса.
Ответ 3
попробуйте использовать aggregation
Literal.objects.values('name').annotate(name_count=Count('name')).exclude(name_count=1)
Ответ 4
Если вы используете 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
Ответ 5
Если вы хотите привести только список имен, но не объекты, вы можете использовать следующий запрос
repeated_names = Literal.objects.values('name').annotate(Count('id')).order_by().filter(id__count__gt=1).values_list('name', flat='true')