Django ORM – 多テーブルの例(集約とグループ化クエリ)

集約クエリ(aggregate)

集約クエリ関数は、値のグループに対して計算を実行し、単一の値を返します。

Django で集約クエリを使用する前に、django.db.models から Avg、Max、Min、Count、Sum(先頭は大文字)をインポートする必要があります。

from django.db.models import Avg,Max,Min,Count,Sum  #   引入函数

集約クエリの戻り値のデータ型は辞書です。

集約関数 aggregate() は QuerySet の終端句であり、集計値を生成します。これは count() に相当します。

aggregate() を使用すると、データ型は辞書になるため、QuerySet データ型の一部の API は使用できなくなります。

日付データ型(DateField)には Max と Min を使用できます。

返される辞書では、キーの名前はデフォルトで(属性名に __ と集約関数名を加えたもの)になり、値は計算された集約値です。

返される辞書のキー名をカスタマイズする場合は、エイリアスを設定できます:

aggregate(别名 = 聚合函数名("属性名称"))

すべての書籍の平均価格を計算する:

例

from django.db.models import Avg,Max,Min,Count,Sum  # 関数をインポート
...
res = models.Book.objects.aggregate(Avg("price"))
print(res, type(res))
...

すべての書籍の数、最高価格、最低価格を計算する:

例

res=models.Book.objects.aggregate(c=Count("id"),max=Max("price"),min=Min("price"))
print(res,type(res)


グループ化クエリ(annotate)

グループ化クエリでは通常、集約関数を使用するため、使用前に django.db.models から Avg, Max, Min, Count, Sum(先頭は大文字)をインポートする必要があります。

from django.db.models import Avg,Max,Min,Count,Sum  #   引入函数

戻り値:

  • グループ化後、values で値を取得すると、戻り値は QuerySet データ型内の辞書の集合になります。
  • グループ化後、values_list で値を取得すると、戻り値は QuerySet データ型内のタプルの集合になります。

MySQL の limit は ORM の QuerySet データ型のスライスに相当します。

注意:

annotate には集約関数を置きます。

  • values または values_list は annotate の前に置きます:values または values_list はどのフィールドでグループ化するかを宣言し、annotate がグループ化を実行します。

  • values または values_list を annotate の後ろに置く場合:annotate は現在のテーブルの pk で直接グループ化を実行することを示し、values または values_list はどのフィールドをクエリするかを示します。また、annotate 内の集約関数にエイリアスを付け、values または values_list にそのエイリアスを記述する必要があります。

データの準備とモデルの作成

models.py

class Emp(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    salary = models.DecimalField(max_digits=8, decimal_places=2)
    dep = models.CharField(max_length=32)
    province = models.CharField(max_length=32)

class Emps(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    salary =     models.DecimalField(max_digits=8, decimal_places=2)
    dep = models.ForeignKey("Dep", on_delete=models.CASCADE)
    province = models.CharField(max_length=32)
class Dep(models.Model):
    title = models.CharField(max_length=32)
 

データ:

MySQL コマンドラインで実行する:

INSERT INTO `app01_emp` (`id`, `name`, `age`, `salary`, `dep`, `province`) VALUES ('1', '令狐冲', '24', '6000.00', '営業部', '河南'); INSERT INTO `app01_emp` (`id`, `name`, `age`, `salary`, `dep`, `province`) VALUES ('2', '任盈盈', '18', '8000.00', '関公部', '広東'); INSERT INTO `app01_emp` (`id`, `name`, `age`, `salary`, `dep`, `province`) VALUES ('3', '任我行', '56', '10000.00', '営業部', '広東'); INSERT INTO `app01_emp` (`id`, `name`, `age`, `salary`, `dep`, `province`) VALUES ('4', '岳霊珊', '19', '6000.00', '関公部', '河南'); INSERT INTO `app01_emp` (`id`, `name`, `age`, `salary`, `dep`, `province`) VALUES ('5', '小龍女', '20', '8000.00', '関公部', '河北'); INSERT INTO `app01_dep` (`id`, `title`) VALUES ('1', '営業部'); INSERT INTO `app01_dep` (`id`, `title`) VALUES ('2', '関公部'); INSERT INTO `app01_emps` (`id`, `name`, `age`, `salary`, `province`, `dep_id`) VALUES ('2', '令狐冲', '24', '8000.00', '河南', '1'); INSERT INTO `app01_emps` (`id`, `name`, `age`, `salary`, `province`, `dep_id`) VALUES ('3', '任盈盈', '18', '9000.00', '広東', '2'); INSERT INTO `app01_emps` (`id`, `name`, `age`, `salary`, `province`, `dep_id`) VALUES ('4', '任我行', '57', '10000.00', '広東', '1'); INSERT INTO `app01_emps` (`id`, `name`, `age`, `salary`, `province`, `dep_id`) VALUES ('5', '岳霊珊', '19', '6000.00', '河南', '2'); INSERT INTO `app01_emps` (`id`, `name`, `age`, `salary`, `province`, `dep_id`) VALUES ('6', '小龍女', '20', '8000.00', '河北', '2');

各出版社の最も安い本の価格を集計する:

例

res = models.Publish.objects.values("name").annotate(in_price = Min("book__price"))
print(res)

コマンドラインで以下の出力を確認できます:

<QuerySet [{'name': 'サンプル出版社', 'in_price': Decimal('100.00')}, {'name': '明教出版社', 'in_price': Decimal('300.00')}]>

各本の著者数を集計する:

例

res = models.Book.objects.annotate(c = Count("authors__name")).values("title","c")
print(res)
コマンドラインで以下の出力を確認できます:
<QuerySet [{'title': 'サンプル', 'c': 1}, {'title': '吸星大法', 'c': 1}, {'title': '冲灵剑法', 'c': 1}]>

「菜」で始まる各書籍の著者数を集計する:

例

res = models.Book.objects.filter(title__startswith=菜).annotate(c = Count("authors__name")).values("title","c")
print(res)

著者が複数いる書籍の名称を集計する:

例

res = models.Book.objects.annotate(c = Count("authors__name")).filter(c__gt=0).values("title","c")
print(res)
コマンドラインで以下の出力を確認できます:
<QuerySet [{'title': 'サンプル', 'c': 1}, {'title': '吸星大法', 'c': 1}, {'title': '冲灵剑法', 'c': 1}]>

1冊の本の著者数に基づいて、クエリセット QuerySet を降順に並べ替える:

例

res = models.Book.objects.annotate(c = Count("authors__name")).order_by("-c").values("title","c")
print(res)

各著者が出版した本の合計価格をクエリする:

例

res = models.Author.objects.annotate(all = Sum("book__price")).values("name","all")
print(res)

F() クエリ

F() のインスタンスはクエリ内でフィールドを参照し、同じ model インスタンス内の2つの異なるフィールドの値を比較できます。

これまでに構築したフィルタは、フィールド値を定数と比較するだけでした。2つのフィールドの値を比較したい場合は、F() を使用する必要があります。

使用前に django.db.models から F をインポートする必要があります:

from django.db.models import F

用法:

F("字段名称")

F はオブジェクトのフィールドの値を動的に取得し、演算を行うことができます。

Django は、F() オブジェクト同士および F() オブジェクトと定数との間の加算、減算、乗算、除算、剰余演算をサポートしています。

更新操作(update)でも F() 関数を使用できます。

給与が年齢より大きい人をクエリする:

例

from django.db.models import F
...
book=models.Emp.objects.filter(salary__gt=F("age")).values("name","age")
...

各本の価格を100元引き上げる:

例

res = models.Book.objects.update(price=F("price")+100)
print(res)

Q() クエリ

使用前に django.db.models から Q をインポートする必要があります:
from django.db.models import Q

用法:

Q(条件判断)

例:

Q(title__startswith="菜")

これまでに構築したフィルタ内の複数の条件の関係はすべて and です。より複雑なクエリ(例:or 文)を実行する必要がある場合は、Q を使用できます。

Q オブジェクトは、& | ~(AND、OR、NOT)演算子を使用して組み合わせることができます。

優先度は高い順に:~ & | です。

Q オブジェクトとキーワード引数を混在させることができます。Q オブジェクトとキーワード引数は "and" で結合されます(つまりカンマを and とみなします)。ただし、Q オブジェクトはすべてのキーワード引数の前に置く必要があります。

価格が 350 より大きい、または名前が「菜」で始まる書籍の名前と価格をクエリする。

from django.db.models import Q

例

...
res=models.Book.objects.filter(Q(price__gt=350)|Q(title__startswith=菜)).values("title","price")
print(res)
...

「菜」で終わる、または 2010 年 10 月ではない書籍をクエリする:

例

res = models.Book.objects.filter(Q(title__endswith=菜) | ~Q(Q(pub_date__year=2010) & Q(pub_date__month=10)))
print(res)  

出版日が 2004 年または 1999 年で、かつ書名に「菜」を含む書籍をクエリする。

Q オブジェクトとキーワード引数を混在させて使用する場合、Q オブジェクトはすべてのキーワードの前に置きます:

例

res = models.Book.objects.filter(Q(pub_date__year=2004) | Q(pub_date__year=1999), title__contains=菜)
print(res)  

その他の拡張