Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Django の order_by の 注意点

More than 3 years have passed since last update.

概要

  • 昔 Django の order_by でどハマりした時のメモが発掘されたので残しておく
  • Django で order_byvaluesdistinct を同時に使うと意図しない結果が得られてしまうので注意という話

環境

  • 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が現れるからまだ原因特定しやすい
  • 下記のようにMetaorderingされてたりすると見つけづらくて最悪である。
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_byvalues/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が発行されるか注意しような > 俺。

参考

tell-k
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away