読者です 読者をやめる 読者になる 読者になる

Django の発行する ORM のクエリでリレーションによってはサブクエリを使っている件と、QuerySet を評価するときのパフォーマンスについて

サブクエリの件

Blog:1 Entry:n みたいなモデルのリレーションがあるとする。ちょっと違うけど。

print(Hoge.objects.filter(entry__in=blog.entry_set.all()).query)

擬似的なクエリはこんな感じ。

SELECT `entry`.`id`, `entry`.`ctime`, `entry`.`utime`, `entry`.`name`,
FROM `entry`
WHERE (`entry`.`blog_id`
  IN (SELECT U0.`id`
      FROM `blog` U0
      WHERE U0.`blog_id` = 313
  )
)

いや、単純に __in だと SELECT * FROM hoge WHERE moge IN (a, b, c) だと思うけど、Django の ORM が発行しているリレーションをまたがる model_set.all() みたいな参照をすると where のなかにもう一度 select をするサブクエリと呼ばれるクエリを発行する。
いやまあそれだけなんだけど、リレーションが深くなる場合はこういうところでパフォーマンスに気をつけないといけないかもな、という気づき。サブクエリが発行されること自体は初めて知った。

QuerySet を評価するパフォーマンスについて

もし上記のようなクエリをとってきたときに、「それが存在すればいいか」ってだけのフラグをたてたいだけなら bool(QuerySet) でいいんだけど、その QuerySet が重いとまたパフォーマンス的にアレだよねってときにどう節約するか。厳密な測定はしていないが*1方法は2通り教わった。のでまとめる。

QuerySet.count() > 0

この場合

SELECT COUNT(*) FROM hoge WHERE (moge IN (SELECT id from toge where id = 1));

count でまとめてくれるこちらのほうが下のやり方よりパフォーマンスはいい、らしい。下のやり方より若干命名としてはイマイチだとは思う。十分意味は通じるけどね。

Queryset.exists()
SELECT (1) AS `a` FROM hoge WHERE (moge IN (SELECT id from toge where id = 1)) LIMIT 1;

要は LIMIT 1 で存在するかを確かめている。パフォーマンスは上記のほうがいい、らしい。ただ命名としてわかりやすいので問題にならなければこちらのほうがいい気がする。

Django で SQL 文の発行を確かめる

ちなみにどんな SQL が発行されているかは Django-Debug-Toolbar をいれるのがいいらしいですけどなんとなく面倒でいれてなくて、シェル(あるいは pdb)から from djano.db import connection して確かめました。
参考::ForeignKeyとfilterのメモ - atas

*1:実用上パフォーマンスは気にするほどじゃないので