見出し画像

【Django】データマスターへの道 - annotate編

こんにちは、エンジニアのsoeです。採用管理システムOwnedMakerの開発を担当しています。

最近では業務効率化をすべく、管理側の開発を進めているところです。管理画面では

・どのユーザーがどれだけ利用しているか
・OwnedMakerを使ってどれだけの効果(応募採用)が得られたか
・サブアカウントの作成とその権限設定

などなど、管理画面では細かな数字をデータベースから引っ張ってきて計算する必要があります。そしてDjangoを使ったWebアプリでは「Django ORM (Django Object-Relation Mapping)」を用いて実現できます。これはかなりざっくりいうとSQLを書くことなく、Pythonを触る要領でデータベースの操作を行える技術です。つまり、Django ORM(あるいはその他ORM)を使いこなすことが、データを縦横無尽に操作し、データに強いエンジニアになるための第一歩なのです!なおDjango ORMの知識をつけても当然ながらDjangoでしか使えませんが、他フレームワークの場合であっても基本的なところは同じなのでまったく無駄ではないと思います👌

とはいえDjangoのこの機能に関しては新しい実装をするたびに新たな発見があるような状態で、すべてをマスターするには相当な時間がかかると思われます。(と同時にDjangoの奥の深さ、なんでも機能をそろえてくれるおもてなし精神には驚かされます)というわけで今回は実際に使っていたORMのテクニックを抜粋して紹介しようかと思います。

1. 日別のログデータを取得

たとえば過去一週間の一日当たりのログイン数などを調べたときがあります。仮にLogテーブルがあり、ログインするたびにここにレコードを追加し、カラムとして「created_at」を持つとします。

よくあるのがこれをグラフに表してログイン数の推移を可視化する機能です。これを実現するには

from sample_app.models import Log
from django.db.models.functions import TruncDate
from django.db.models import Count

Log.objects\
   .all()\
   .annotate(date=TruncDate('created_at'))\ # 1
   .values("date")\ # 2
   .annotate(count=Count('id'))\ # 3
   .order_by("date")

のようなコードが必要です。これで「{'date': datetime.date(2021, 4, 30), 'count': 169}」のようなデータが得られます。一体何が行われているのでしょうか。

①「created_at」(Datetime型)の日にちより細かい数字(時・秒など)を省略した「date」情報を新たにカラムとして追加する。

②「date」カラム以外を省略し、グループ分けします。

③日ごとのデータ数を数え上げる。

詳しくはこちら

これはそのままChart.jsなどのライブラリに渡してやればデータ可視化の完成です。

2. 条件付きCount

OwnedMakerには応募ごとにメール送信機能が付いており、送受信履歴が見られるようになっています。簡単のためいろいろ問題はありますが仮に以下のように定義します。

#models.py

class Mail(models.Model):
   apply = models.ForeignKey(Apply, on_delete=models.CASCADE)
   body = models.TextField()
   mail_address = models.CharField(max_length=200)
   is_read = models.BooleanField(default=False) # 既読か未読
   created_at = models.DateTimeField(auto_now_add=True)
   
   
 class Apply(models.Model)
   user = models.ForeignKey(User, on_delete=models.CASCADE)
   applicant_name = models.CharField(max_length=200)
   job_offer_name = models.CharField(max_length=200)
   created_at = models.DateTimeField(auto_now_add=True)

まず応募ごとにやりとりしたすべてのメールの数を一緒に取得するならこうです。自分(ここではApply)を外部キーとして持つテーブル(ここではMail)に関する情報をカラムに追加したいときは小文字にしてアクセスする、という点が味噌です。

Apply.objects.filter(user=user).annotate(mail_count=Count('mail'))

では未読数を取得するにはどうすればよいでしょうか。これはis_readがFalseという条件でMailを絞り込みつつ、Countする必要があります。この実装は「Q」を使って以下のようにできます。

from django.db.models import Q

Apply.objects.filter(user=user)\
  .annotate(mail_count=Count('mail'))\
  .annotate(not_read_count=Count('mail', filter=Q(mail__is_read=False)))

もちろんCountだけでなくMax関数などでも同様にできます。Maxをcreated_atに適用して最新のメールだけ取り出す、ということも可能です。

まとめ​

少し知ってるだけでもデータの表現の幅がグンと広がります。実装のアイデアはビジネスサイドから要件を定義する場合が多いと思いますが、「技術的に何ができるか」というところから提案していくことも大事です。今回のテクニックも知ってるだけで「こういう実装、できますよ」と言えるので、知っているのと知らないのではまた違ったWebアプリケーションの形になると思います。


この記事が気に入ったらサポートをしてみませんか?