【问题标题】:Django select only rows with duplicate field valuesDjango 只选择具有重复字段值的行
【发布时间】:2012-02-17 20:10:35
【问题描述】:

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

【问题讨论】:

    标签: sql django django-orm


    【解决方案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))
    

    【讨论】:

      【解决方案2】:

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

      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)
      

      annotate 调用后对.values('name') 的额外调用看起来有点奇怪。没有这个,子查询将失败。额外的值会诱使 ORM 只选择子查询的名称列。

      【讨论】:

      • 不错的技巧,不幸的是,这仅在仅使用一个值时才有效(例如,如果同时使用了“姓名”和“电话”,则最后一部分将不起作用)。
      • .order_by() 是干什么用的?
      • @stefanfoulis 它清除了任何现有的订单。如果您有模型集排序,这将成为 SQL GROUP BY 子句的一部分,这会破坏事情。在玩 Subquery 时发现了这一点(您在其中通过 .values() 进行了非常相似的分组)
      【解决方案3】:

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

      【讨论】:

      • 我试过了,但是 python 代码给了我一个错误:FieldError: Expression contains mixed types: ArrayField, IntegerField. You must set output_field.。但是,SQL 查询按预期工作(Django 3.2)
      【解决方案4】:

      试试:

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

      这是您使用 Django 所能获得的最接近的结果。问题是这将返回一个只有namecountValuesQuerySet。但是,您可以使用它来构造一个常规的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])
      

      【讨论】:

      • 可能你的意思是Literal.objects.values('name').annotate(name_count=Count('name')).filter(name_count__gt=1)
      • 原始查询给出Cannot resolve keyword 'id_count' into field
      • 感谢更新的答案,我想我会坚持这个解决方案,你甚至可以通过使用values_list('name', flat=True) 来做到这一点而无需列表理解
      • Django 之前对此有一个错误(可能已在最近的版本中修复),如果您没有为要保存的 Count 注释指定字段名,则默认为 [field]__count。但是,双下划线语法也是 Django 解释您想要进行连接的方式。因此,本质上,当您尝试对其进行过滤时,Django 认为您正在尝试与显然不存在的count 进行连接。解决方法是为您的注释结果指定一个名称,即annotate(mycount=Count('id')),然后过滤mycount
      • 如果您在调用 annotate 之后添加另一个对 values('name') 的调用,您可以删除列表理解并说 Literal.objects.filter(name__in=dupes) 这将允许所有这些都在单个查询中执行。
      【解决方案5】:

      如果你只想得到名称列表而不是对象,你可以使用下面的查询

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

      【讨论】:

        【解决方案6】:

        尝试使用aggregation

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

        【讨论】:

        • 好的,这给出了正确的名称列表,但是否可以同时选择 id 和其他字段?
        • @dragonon - 不,但 Chris Pratt 在他的回答中涵盖了替代方案。
        猜你喜欢
        • 2012-03-04
        • 2021-03-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-11
        • 1970-01-01
        • 2015-05-24
        相关资源
        最近更新 更多