Djangoは重複するフィールド値を持つ行のみを選択します


96

次のように定義された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ソリューション?

回答:


192

試してください:

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

これは、Djangoで可能な限り近くなります。問題は、これが返されるということですValuesQuerySetだけでnamecount。ただし、これを使用QuerySetして、別のクエリにフィードバックすることにより、regularを作成できます。

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)か?
ドラグーン

元のクエリの結果Cannot resolve keyword 'id_count' into field
ドラッグーン

2
更新された答えをありがとう、私はこの解決策に固執すると思います、あなたは使用することでリストの理解なしでそれを行うことさえできますvalues_list('name', flat=True)
dragoon

1
Djangoには以前これに関するバグがあり(最近のバージョンでは修正されている可能性があります)、Count保存する注釈のフィールド名を指定しない場合、デフォルトでになり[field]__countます。ただし、その二重下線構文は、Djangoが結合を実行したいことを解釈する方法でもあります。そのため、基本的にそれをフィルタリングしようとすると、Djangoは、count明らかに存在しない結合を実行しようとしていると考えます。修正は、アノテーション結果の名前を指定することです。つまりannotate(mycount=Count('id'))mycount代わりにフィルタリングします。
Chris Pratt

1
values('name')annotateの呼び出しの後に別の呼び出しを追加する場合は、リスト内包表記を削除して、Literal.objects.filter(name__in=dupes)これを1つのクエリですべて実行できるようにすることができます。
Piper Merriam 2013年

42

これは編集として拒否されました。だからここにそれはより良い答えとして

dups = (
    Literal.objects.values('name')
    .annotate(count=Count('id'))
    .values('name')
    .order_by()
    .filter(count__gt=1)
)

これはValuesQuerySet、すべての重複した名前を含むを返します。ただし、これを使用QuerySetして、別のクエリにフィードバックすることにより、通常のオブジェクトを作成できます。django ORMは、これらを1つのクエリに組み合わせるのに十分スマートです。

Literal.objects.filter(name__in=dups)

.values('name')注釈呼び出しの後の追加の呼び出しは少し奇妙に見えます。これがないと、サブクエリは失敗します。追加の値は、ORMをだましてサブクエリの名前列のみを選択します。


素敵なトリック、残念ながらこれは1つの値のみが使用されている場合にのみ機能します(たとえば、「name」と「phone」の両方が使用されている場合、最後の部分は機能しません)。
guival

1
何の.order_by()ためですか?
stefanfoulis 2017年

4
@stefanfoulis既存の順序をすべてクリアします。モデルセットの順序がある場合、これはSQL GROUP BY句の一部になり、問題が発生します。サブクエリ(を介して非常に類似したグループ化を行う.values())で遊んでいるときに判明
Oli

10

集計を使用してみてください

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

OK、それは名前の正しいリストを提供しますが、IDと他のフィールドを同時に選択することは可能ですか?
ドラグーン

@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')
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.