ユーニックス総合研究所

  • home
  • archives
  • django-distinct

DjangoのQuerySetのdistinct()の使い方

  • 作成日: 2020-12-06
  • 更新日: 2023-12-24
  • カテゴリ: Django

QuerySetのdistinct()の使い方

PythonのWebフレームワークであるDjango(ジャンゴ)にはQuerySetというオブジェクトがあります。
QuerySetにはfilter()order_by()などの代表的なメソッドの他に、distinct()というメソッドがあります。

このメソッドを使うとQuerySetの結果から重複したレコード(行、オブジェクト)を除去することが可能です。

この記事ではdistinct()の使い方を解説します。
具体的には↓を見ていきます。

  • モデルを作る
  • distinct()を単体で使う
  • distinct()とvalues_list()を組み合わせる
  • distinct()とorder_by()

モデルを作る

distinct()の検証用にモデルを作ります。
今回はPersonと言うモデルを作ります。
これは人物を表現するモデルで、名前や年齢などのフィールドを持っています。

from django.db import models  


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

nameageのフィールドにはuniqueを付けません。
そのためnameageの値は重複する可能性があります。
これらのフィールドの重複したデータをdistinct()でフィルタリングしてみようという感じです。

↑のPersonモデルを使ってオブジェクトを↓のように初期化しておきます。

Person.objects.create(name='Taro', age=20)  
Person.objects.create(name='Taro', age=30)  
Person.objects.create(name='Miki', age=16)  
Person.objects.create(name='Miki', age=32)  
Person.objects.create(name='Kenji', age=30)  

nameにおいてTaro, Mikiは重複しています。
またage30の値で重複しています。

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 [('Taro',), ('Miki',), ('Kenji',)]>  

nameのフィールドにはTaroMikiが重複でセットされていましたが、結果からそのオブジェクトが取り除かれているのがわかります。

values_list()には複数の引数(フィールド名)を指定できます。
ageのフィールドも指定してみましょう。

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

SQL文は↓のようになります。

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

nameageDISTINCTの条件に指定されました。
Person.objects.distinct().values_list('name', 'age')の結果は↓のようになります。

<QuerySet [('Taro', 20), ('Taro', 30), ('Miki', 16), ('Miki', 32), ('Kenji', 30)]>  

nameageを条件にした場合、重複しているオブジェクトは存在しないので↑のようにすべてのオブジェクトが取得されています。

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 [('Taro',), ('Taro',), ('Miki',), ('Miki',), ('Kenji',)]>  

nameで重複を除去してほしい所でしたが、order_by()idを指定しているので↑のようにidnameを条件に重複が除去されます。
結果としてすべてのオブジェクトが取得されます。

おわりに

distinct()を使うと重複したオブジェクトを結果から除去することが出来ます。
覚えておくと役に立つと思いますので、よろしければこの記事をブックマークしてみてください。