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
);

그래서 장고 ORM을 사용하여 이것을 선택할 수 있습니까? 아니면 더 나은 SQL 솔루션?

답변:


193

시험:

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])

5
아마 당신은 의미 Literal.objects.values('name').annotate(name_count=Count('name')).filter(name_count__gt=1)합니까?
dragoon

원래 쿼리 제공Cannot resolve keyword 'id_count' into field
dragoon

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')주석을 달기 위해 호출 후에 에 다른 호출을 추가 하는 경우 목록 이해를 제거하고 Literal.objects.filter(name__in=dupes)단일 쿼리에서이 모든 것이 실행되도록 허용 할 것인지 말할 수 있습니다.
Piper Merriam

43

이것은 편집으로 거부되었습니다. 그래서 여기에 더 나은 대답입니다.

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

이렇게하면 ValuesQuerySet모든 중복 이름이있는 a가 반환 됩니다. 그러나이를 사용하여 일반 QuerySet쿼리를 다른 쿼리로 다시 공급 하여 생성 할 수 있습니다. django ORM은이를 단일 쿼리로 결합 할 수있을만큼 똑똑합니다.

Literal.objects.filter(name__in=dups)

에 추가 전화 .values('name')주석 달기 호출 후 조금 이상한 보인다. 이것이 없으면 하위 쿼리가 실패합니다. 추가 값은 ORM이 하위 쿼리에 대한 이름 열만 선택하도록 속입니다.


안타깝게도이 방법은 하나의 값만 사용하는 경우에만 작동합니다 (예 : '이름'과 '전화 번호'가 모두 사용 된 경우 마지막 부분은 작동하지 않음).
guival

1
무엇입니까 .order_by()?
stefanfoulis

4
@stefanfoulis 기존 주문을 지 웁니다. 모델 세트 순서가있는 경우 이것은 SQL GROUP BY절의 일부가 되어 문제가 발생합니다. Subquery (를 통해 매우 유사한 그룹화를 수행하는 하위 쿼리로 플레이 할 때 발견됨 .values())
Oli

10

집계를 사용해보십시오

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

좋아, 그것은 이름의 corrent 목록을 제공하지만 동시에 ID와 다른 필드를 선택할 수 있습니까?
dragoon

@dragoon-아니요, Chris Pratt는 그의 대답에서 대안을 다루었습니다.
JamesO

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.