web-dev-qa-db-ja.com

Djangoフィールド値が重複する行のみを選択

Django=に以下のように定義されたモデルがあると仮定します。

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

名前フィールドは一意ではないため、値が重複する場合があります。次のタスクを実行する必要があります。少なくとも1つの重複する値nameフィールドを持つモデルからすべての行を選択します。

私はプレーンSQLを使用してそれを行う方法を知っています(最善の解決策ではないかもしれません):

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

だから、Django ORM?またはより良いSQLソリューションを使用してこれを選択することは可能ですか?

82
dragoon

試してください:

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

これは、Djangoでできる限り近いものです。問題は、これがValuesQuerySetnameのみを含む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])
166
Chris Pratt

これは編集として拒否されました。したがって、ここではbetter answer

_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をだまして、サブクエリの名前列のみを選択させます。

36
Piper Merriam

aggregation を使用してみてください

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

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
2
Eugene Pakhomov

名前リストのみを生成し、オブジェクトは生成しない場合は、次のクエリを使用できます

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