Django'da Aggregate ve Annotate

Django veritabanı yönetiminde annotate() ve aggregate() kullanarak,satırların toplamı, ortalaması gibi, veritabanında birden çok satırdan veya ilgili diğer tablolardan bilgi toplayarak bulunması gereken değerleri bulabilirsiniz. annotate() ve aggregate() metodlarının farkı, aggregate'in tüm tablo için tek bir sonuç döndürmesi, annotate'in ise veritabanındaki tüm satırlar için ayrı birer değer oluşturmasıdır. Bu yazıda kısaca Django'da annotate ve aggreate kullanımı ile ilgili örnekler göstereceğim.

Burada anlatılanlar kısaca django'da annotate ve aggregate nasıl kullanılır konusuna giriş yapmak içindir, daha fazlasını django belgelerinde aggregate ile ilgili bölümden bulabilirsiniz.

Tablo alanlarının ortalaması

###
# models.py
###

from django.db import models

class Urun(models.Model):
    fiyat = models.PositiveIntegerField()

###
# Tüm ürünlere ait ortalama fiyatları almak istediğinizde
###
from uygulama_adi.models import Urun
from django.db import Avg

a = Urun.objects.aggregate(Avg('fiyat'))

# a = {"fiyat__avg": 23.56} -> bir dict objesi döndürür.
# Dönen sözlükteki anahtar adını da ayarlayabilirsiniz.

a = Urun.objects.aggregate(ortalama_fiyat=Avg('fiyat'))

# a = {"ortalama_fiyat": 23.56}

##
# Birden fazla değer hesaplamak
##
from django.db.models import Max, Min
a = Urun.objects.aggregate(Ortfiyat=Avg('fiyat'),Minfiyat=Min('fiyat'),maksfiyat=Max('fiyat'))

# a = {"Ortfiyat" : 15.23, "Minfiyat": 3, "Maxfiyat" : 28}

Django'da annotate kullanımı

Bir istem kümesindeki (QuerySet) tüm değerler için aggregate almak için annotate kullanılır. Çoğu zaman annotate m2m (çokdan çoğa) ilişkilerde kullanılır.

###
# models.py
###
from django.db import models

class Etiket(models.Model):
    yazi = models.CharField(max_length=60)
class Makale(models.Model):
    etiketler = models.ManyToManyField(Etiket, blank=True)

### model.py sonu ###

# Her etiket için kaç makale var

from uygulamam.models import Etiket
from django.db.models import Count

a = Etiket.objects.annotate(makale_sayisi=Count('makale'))

# a değişkeni Etiket.objects.all() gibi, ancak her elemanın artık makale_sayisi diye bir özelliği var,
# örneğin: a[0].makale_sayisi ilk sıradaki etikete ait kaç adet makale olduğunu verir.
# aynı zamanda bir etikete ait diğer tüm özellikler de her bir elemanda mevcuttur.

Alakalı tablo sütünları

Django'daki çift alt tire gösterimini, annotate ve aggregate için kullanabilirsiniz.

# gerekli import'lar yapılmış sayın

"""
Bu örnekteki uygulama, aynı site içerisinde birden fazla blog tutuyor.
Her makale bir blog'a ilişkilendirilmiş
"""
class Blog(models.Model):
    # bir iki alan tanımı var burda

class Makale(models.Model):
    blog = models.ForeignKey(Blog,related_name="makaleler")
    begeni_yuzdesi = models.DecimalField()

###
# Her blog için, en az beğenilen makalelerin ne kadar begeni_yuzdesi aldığı!
###
from django.models import Min
a = Blog.objects.annotate(en_az_begeni = Min('makaleler__begeni_yuzdesi'))

# a değişkeni tüm blogların bir listesini tutuyor. a'nın her elemanının en_az_begeni diye bir
# özelliği var ve bu özellik o blog'daki tüm makeler içerisinde en az beğenilen makalenin
# begeni yüzdesini tutuyor.

İstem (Query) metotlarını zincirlememek

Django'da aggregate() ve annotate() metodları, Django veritabanı apisindeki filter, exclude gibi diğer istem metotlarıyla zicirleyebilirsiniz. Ancak aggregate() her zaman en son metot olmak zorunda. annotate'in ise zincirlemedeki yerine göre, istem kümesinin elemanları değişiyor.

Makale.objects.filter(yayinlandi=True).aggregate(ortalama_begeni = Avg("begeni_yuzdesi"))
# Yayınlanan makalelerin ortalama beğeni yüzdesini döndürür : {"ortalama_begeni": 93.21}

Blog.objects.annotate(makale_sayisi = Count("makaleler")).order_by("-makale_sayisi")

# annotate ile eklediğimiz makale_sayisi özelliğini Blog'ları sıralamak için kullanıyoruz.