概要
- 昔 Django の
order_by
でどハマりした時のメモが発掘されたので残しておく - Django で
order_by
やvalues
やdistinct
を同時に使うと意図しない結果が得られてしまうので注意という話
環境
- Django1.9
- MySQL
前提
- 下記のようなDjangoモデルがあるとする
# polls/models.py
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
- あらかじめこんなデータが入ってることとする
q1 = Question.objects.get(question_text='question1')
q2 = Question.objects.get(question_text='question2')
Choice.objects.create(question=q1, choice_text='choice1', votes=1)
Choice.objects.create(question=q1, choice_text='choice2', votes=1)
Choice.objects.create(question=q2, choice_text='choice3', votes=1)
Choice.objects.create(question=q2, choice_text='choice4', votes=1)
1.SELECTするカラムを絞る
-
Choice
の外部キーであるquestion
(=question_id
)だけをSELECTする
# valuesで絞る --
Choice.objects.values('question')
-- 実行されるSQL
mysql> SELECT `polls_choice`.`question_id` FROM `polls_choice`;
+-------------+
| question_id |
+-------------+
| 1 |
| 1 |
| 2 |
| 2 |
+-------------+
4 rows in set (0.00 sec)
2. 1にdistinct
を加える
# distinct で 重複を削除する --
Choice.objects.values('question').distinct()
-- 実行されるSQL
mysql> SELECT DISTINCT `polls_choice`.`question_id` FROM `polls_choice`;
+-------------+
| question_id |
+-------------+
| 1 |
| 2 |
+-------------+
2 rows in set (0.00 sec)
- ちゃんと重複排除できている
3. 2にorder_by
を加える
# valuesで絞ったフィールドとは別のフィールドで order_by する
Choice.objects.values('question').distinct().order_by('-id')
-- 実行されるSQL
-- なぜか、order_byで指定したフィールドがSELECTされてる...
mysql> SELECT DISTINCT `polls_choice`.`question_id`, `polls_choice`.`id` FROM `polls_choice` ORDER BY `polls_choice`.`id` DESC;
+-------------+----+
| question_id | id |
+-------------+----+
| 2 | 4 |
| 2 | 3 |
| 1 | 2 |
| 1 | 1 |
+-------------+----+
4 rows in set (0.00 sec)
- え? なぜかSELECT対象にidが追加されている。。。
- おかげで
distinct question_id
としたつもりが出来ていない。。。。 - 上記はコード上に
order_by
が現れるからまだ原因特定しやすい - 下記のように
Meta
でordering
されてたりすると見つけづらくて最悪である。
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
class Meta:
ordering = ('-id',)
- もしデフォルトの
ordering
が設定されてる場合は、意図的にorder_by()
を空で読んで無効化するなどの対処が必要になる。
Choice.objects.values('question').distinct().order_by()
Django でorder_by
すると指定したフィールドは SELECT される
- SELECT に含まれるのはしょうがないので、
order_by
やvalues/only/distinct
などと組み合わせる場合は結果の一意性を損なう可能性があるので気をつけなければならない。 - この辺の話は、下記参考URLのDjangoのドキュメントに注意点として記載されている。
Postgres の場合は DISTINCT ON が使える
- バックエンドがPostgresの場合は
DISTINCT ON
が使えるので、distinct
メソッドに対象を渡すことができる
Choice.objects.values('question').distinct('question').order_by('question', '-id')
まとめ
-
distinct/values/only/defer
+order_by
を使う時はどういうSQLが発行されるか注意しような > 俺。