假设我们在 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 解决方案?
尝试:
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])
此内容因编辑而被拒绝。所以这是一个更好的答案
dups = (
Literal.objects.values('name')
.annotate(count=Count('id'))
.values('name')
.order_by()
.filter(count__gt=1)
)
这将返回包含所有重复名称的
ValuesQuerySet
。但是,您可以使用它通过将其反馈到另一个查询中来构造常规 QuerySet
。 django ORM 足够智能,可以将这些组合成一个查询:
Literal.objects.filter(name__in=dups)
注释调用后对
.values('name')
的额外调用看起来有点奇怪。如果没有这个,子查询就会失败。额外的值会欺骗 ORM 仅选择子查询的名称列。
尝试使用聚合
Literal.objects.values('name').annotate(name_count=Count('name')).exclude(name_count=1)
如果您使用 PostgreSQL,您可以执行以下操作:
from django.contrib.postgres.aggregates import ArrayAgg
from django.db.models import Func, Value, DecimalField
duplicate_ids = (Literal.objects.values('name')
.annotate(ids=ArrayAgg('id'))
.annotate(c=Func('ids', Value(1), function='array_length', output_field=DecimalField()))
.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
好吧,由于某种原因,上述方法都不起作用,它总是返回
<MultilingualQuerySet []>
。我使用以下更容易理解但不是那么优雅的解决方案:
dupes = []
uniques = []
dupes_query = MyModel.objects.values_list('field', flat=True)
for dupe in set(dupes_query):
if not dupe in uniques:
uniques.append(dupe)
else:
dupes.append(dupe)
print(set(dupes))
如果您只想结果名称列表而不是对象,您可以使用以下查询
repeated_names = Literal.objects.values('name').annotate(Count('id')).order_by().filter(id__count__gt=1).values_list('name', flat='true')