본문 바로가기
python/Django

Django ORM distinct()

by Redking

Django에는 QuerySet이라는 객체가 있고 filter및 order_by등의 대표적인 메소드 외에, distinct 라는 메소드가 있습니다.

이 메소드를 사용하면 QuerySet 결과에서 중복 레코드(행, 오브젝트)를 제거할 수 있습니다.

 

distinct()검증을 위해 모델을 만듭니다.
Person 이라는 모델을 만듭니다.
이것은 사람을 표현하는 모델이며 이름과 나이와 같은 필드가 있습니다.

from django.db import models


class Person(models.Model): 
    name = models.CharField(max_length = 128) 
    age = models.IntegerField()

name나 age필드에는 unique을 붙이지 않습니다.
따라서 name와 age값은 중복 될 수 있습니다.


이러한 필드의 중복된 데이터를 distinct()필터링해 보자는 느낌입니다.

↑의 Person모델을 사용해 오브젝트를 ↓와 같이 초기화해 둡니다.

Person.objects.create(name='김일', age=20) 
Person.objects.create(name='김일', age=30) 
Person.objects.create(name='이이', age=16) 
Person.objects.create(name='이이', age=32) 
Person.objects.create(name='삼셋', age=30)

name에서 김일과 이이가 중복되어 있습니다.
또한 age역시 30이라는 값에서 중복 있습니다.

distinct()를 단독으로 사용

그럼 distinct()사용해봅시다.
distinct()는 아래와 같이 단체로 사용할 수 있습니다.

Person.objects.distinct()

↑ 코드의 SQL 쿼리를 살펴 보겠습니다.

print(Person.objects.distinct().query)
SELECT DISTINCT "myapp_person"."id", "myapp_person"."name", "myapp_person"."age" FROM "myapp_person"

↑을 보면 DISTINCT SQL문이 생성되고 있는 것을 알 수 있습니다.
기본적으로 ↑와 같이 모델의 모든 필드를 조건으로 지정하여 쿼리를 생성합니다.
따라서 Person.objects.distinct()결과는 ↓와 같은 결과입니다.

<QuerySet [ 
    <Person:Person object(1)>, 
    <Person:Person object(2)>, 
    <Person:Person object(3)>, 
    <Person:Person object(4)>, 
    <Person:Person object(5))> 
]>

모든 객체가 검색되었습니다.
즉 모델의 모든 필드를 DISTINCT의 조건으로 했을 경우, 중복되는 레코드는 없다는 것입니다.

distinct()와 values_list()를 결합

distinct()에서 특정 필드를 조건으로 지정하고 싶은 경우 values_list()와 조합합니다.
distinct()를 호출한 후 ↓처럼 values_list()호출합니다.

Person.objects.distinct().values_list('name')

↑의 경우 values_list()에는 Person필드 name을 지정합니다.
이렇게하면 name필드를 조건으로 DISTINCT만듭니다.
SQL 문을 살펴 보겠습니다.

print (Person.objects.distinct().values_list('name').query)
 
SELECT DISTINCT "myapp_person"."name" FROM "myapp_person"

↑와 같이 SQL문이 생성되고 있습니다. DISTINCT의 필드는 name단독으로 지정됩니다.
따라서 Person.objects.distinct().values_list('name')결과는 ↓와 같습니다.

<QuerySet [('김일',), ('이이',), ('삼셋',)]>

name필드는 김일과 이이가 중복으로 되어 있었지만, 결과에서 중복이 제거되어있는 것을 알 수 있습니다.

values_list()에는 여러 인수(필드 이름)를 지정할 수 있습니다.
age의 필드도 지정해 봅시다.

Person.objects.distinct().values_list('name', 'age')

SQL문은 ↓와 같이 됩니다.

SELECT DISTINCT "myapp_person"."name", "myapp_person"."age" FROM "myapp_person"

name그리고 age이 DISTINCT조건에 지정되었습니다.
Person.objects.distinct().values_list('name', 'age')의 결과는 ↓와 같습니다.

<QuerySet [('김일', 20), ('김일', 30), ('이이', 16), ('이이', 32), ('삼사', 30)]>

name그리고 age을 조건으로 한 경우 중복 된 개체는 존재하지 않기 때문에 ↑처럼 모든 개체를 검색합니다.

distinct() 및 order_by()

distinct()와 order_by()조합할 때는 주의가 필요합니다.
order_by()에 지정한 필드가 DISTINCT에 추가됩니다.
예를 들어 ↓ 코드를 살펴 보겠습니다.

Person.objects.order_by('id').distinct().values_list('name')

order_by()에서 id키를 정렬하고 distinct().value_list()를 사용하고 있습니다.
distinct()와 value_list()조합하면 ↑의 경우 name에만 DISTINCT조건으로 지정됩니다.
하지만 ↑처럼 order_by()지정한 경우는 다릅니다.
↑ 코드의 SQL 문장을 살펴 보겠습니다.

SELECT DISTINCT "myapp_person"."name", "myapp_person"."id" FROM "myapp_person" ORDER BY "myapp_person"."id" ASC

↑ 와 같은 DISTINCT조건으로 id설정되어 있습니다.
이것에 대한 Person.objects.order_by('id').distinct().values_list('name')결과는 ↓와 같습니다.

<QuerySet [('김일',), ('김일',), ('이이',), ('이이',), ('삼사',)]>

name의 중복을 제거 조건을 제외하고, order_by()로 id를 지정하고 있으므로↑와 같이 id와 name를 조건에 중복이 제거됩니다.
결과적으로 모든 오브젝트가 검색됩니다.

결론

distinct()를 사용하면 중복 객체를 결과에서 제거할 수 있습니다.

 

간단 요약

A는 그냥 A

B = A에 여러개 붙을 수 있음 

distinct는 A모델을 필터링 할 때 사용하는데 B를 기준으로 필터링할때 B가 n개 이상이고 각각의 필드가 다른 경우에 A가 B의 개수만큼 나오기 때문에 이를 막고자 사용하며 사용하게 되면 B로 인해 중복되어 여러개 나오던 A가 1개만 나오게 됨.

 

A B 2 이상이 아닌 경우에는 사용하지 않도록 해야함.

댓글