DjangoのQuerySetでannotateで生成したtimedeltaをfilterしたいとき
今回はDjangoのQuerySetを使っているときにannotateで日付の比較などを行って生成したカラムに対してfilter条件で抽出したいときどのようにすれば良いのかについてご紹介いたします。
DjangoのQuerySetでannotateで生成したtimedeltaをfilterしたいとき
例としてEventというモデルがあって、DateTimeFieldのdatetime_fromとDateTimeFieldのdatetime_toがEventモデルにあったとしましょう。
annotate関数を使って、datetime_toとdatetime_fromの差であるevent_timeを取得するとします。
上記の例では、一旦F関数を使って
1 2 |
from django.db.models import F Event.objects.all().annotate(event_time=F('datetime_to')-F('datetime_from')) |
というソースコードが書けると思います。
さて、上記のときにevent_timeが120秒以上のものを抽出したいとしましょう。
単純にfilter条件だけではエラーが出てしまいます。
1 2 3 |
from django.db.models import F Event.objects.all().annotate(event_time=F('datetime_to')-F('datetime_from')).filter(event_time__gte=120) # TypeError: expected string or bytes-like objectというエラーが出力される |
ここで、Eventモデルの新しく設定したevent_timeの値を見てみると、
datetime.timedelta(seconds=17000)
などの値が出力されていると思います。(私の動作実行時の環境はPostgresSQL環境です)
つまり、このままでは型も合ってそうにないのでうまくいかなそうなことが分かると思います。
結論を言うと、ExpressionWrapperとtimedeltaを使うようにしましょう。
先にコード例を書くと下記のようになります。
1 2 3 |
from django.db.models import F, ExpressionWrapper, DurationField from datetime import timedelta Event.objects.all().annotate(event_time=ExpressionWrapper(F('datetime_to')-F('datetime_from'), output_field=DurationField())).filter(event_time__gte=timedelta(seconds=120)) |
ExpressionWrapperを使ってannotateで新しく追加したカラムをDurationFieldとして出力するようにしましょう。
その後DurationFieldとして出力した結果に対して、timedeltaの型でfilter条件で抽出すればOKです。
注意点としては、DurationFieldはPostgreSQLではうまく動作しますが、他のDB環境ではうまく動作しない場合があるようです。
DBの環境にも注意するようにしましょう。
終わりに
今回はDjangoのQuerySetを使っているときにannotateで日付の比較などを行って生成したカラムに対してfilter条件で抽出したいときどのようにすれば良いのかについてご紹介いたしました。
ディスカッション
コメント一覧
まだ、コメントがありません