やろうとしたこと
例えば、次のような共通部品から、優先(priorityが小さいもの)して使うものを抽出したい。
products
id |
---|
1 |
2 |
3 |
common_parts
product_id | priority | name |
---|---|---|
1 | 1 | ネジa |
2 | 1 | ネジb |
1 | 2 | ネジA |
2 | 2 | ネジB |
1 | 3 | ネジα |
このとき、MySQL ✕ DjangoのORMでは色々制約が合って、なかなか満足行くものがすぐに作れなかった。
NGパターン
group_byでいいじゃない?
queryset = Products.objects.all().annotate(
common_part=Subquery(
CommonParts.objects.filter(
product_id=OuterRef('pk'),
).order_by('priority').group_by('product_id').values('name')
),
)
DjangoのORMに、group_byは無いそうです(;・∀・)
distinctで、product_idを指定すればいいじゃない?
queryset = Products.objects.all().annotate(
common_part=Subquery(
CommonParts.objects.filter(
product_id=OuterRef('pk'),
).order_by('priority').distinct('product_id').values('name')
),
)
MySQLが、distinctに引数は指定できないそうです(;・∀・)
PostgreSQLはできるみたいな情報は在った気がする。
OKパターン
queryset = Products.objects.all().annotate(
common_part=Subquery(
CommonParts.objects.filter(
product_id=OuterRef('pk'),
).order_by('priority')[:1].values('name')
),
)
順番つけた後に、1つ目のものを取ってくる。
まぁ、はじめにこれがすぐ思いつく人も多いか(´Д`)…
ポイント
- [1]ではなく[:1]にすることで、共通部品がなくてもエラーにならないようにした。
所感
後から考えると、すぐにOKパターンが思いついても良さそうなもんだなと思ったけど、思考の流れ的に「こういう場合Group By使うよな」から始まってたからなかなかたどり着くのに時間がかかった。。
distinctについても、結構
MyModel.objects.all().values_list('hoge', flat=True).order_by('hoge').distinct()
で出来る話はたくさん出てきたけど、使いたい項目とdistinctしたい項目が違うことで悩まされた。(↑はPostgreSQLではdistinctに引数指定できるのに、MySQLではできないけどどうするのっていう対処法だった)
今回わかったgroup_byはDjangoのORMにないことと、distinctを引数に使えないという仕様は覚えておこうと思って残しておく。