Django仅选择具有重复字段值的行


96

假设我们在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解决方案?

Answers:


192

尝试:

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

这与Django尽可能接近。问题在于这将返回一个ValuesQuerySetwith namecount。但是,您可以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])

4
可能是您的意思Literal.objects.values('name').annotate(name_count=Count('name')).filter(name_count__gt=1)
dragoon 2012年

原始查询给出Cannot resolve keyword 'id_count' into field
dragoon 2012年

2
感谢您提供最新的答案,我想我会坚持使用此解决方案,您甚至可以通过以下方式来做到这一点:values_list('name', flat=True)
dragoon 2012年

1
Django之前在此上有一个错误(可能在最新版本中已修复),如果您没有为Count注释另存为指定字段名,则默认为[field]__count。但是,这种双下划线语法也是Django解释要进行联接的方式。因此,实质上,当您尝试对此进行过滤时,Django认为您正在尝试进行count显然不存在的联接。解决方法是为注释结果指定一个名称,即annotate(mycount=Count('id'))然后过滤mycount
克里斯·普拉特

1
如果您在添加values('name')注释的调用之后添加了另一个调用,则可以删除列表理解,并说Literal.objects.filter(name__in=dupes)这将使所有这些都可以在单个查询中执行。
Piper Merriam

42

这被拒绝作为编辑。所以这是一个更好的答案

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仅选择子查询的名称列。


妙招,不幸的是,仅在使用一个值的情况下,此方法才有效(例如,如果同时使用“名称”和“电话”,则最后一部分将不起作用)。
吉瓦尔

1
有什么.order_by()
stefanfoulis

4
@stefanfoulis清除所有现有订单。如果您具有模型集排序,GROUP BY那么它将成为SQL 子句的一部分,这会破坏工作。在使用子查询(通过进行非常相似的分组.values())时发现了这一点
Oli

10

尝试使用聚合

Literal.objects.values('name').annotate(name_count=Count('name')).exclude(name_count=1)

好的,这给出了正确的名称列表,但是是否可以同时选择id和其他字段?
Dragoon 2012年

@dragoon-不,但克里斯·普拉特(Chris Pratt)在他的回答中提到了替代方法。
JamesO 2012年

5

如果您使用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

如果只想生成名称列表而不是对象,则可以使用以下查询

repeated_names = Literal.objects.values('name').annotate(Count('id')).order_by().filter(id__count__gt=1).values_list('name', flat='true')
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.