Herkese merhabalar.
Bazı durumlarda veritabanınızdaki değerleri bir takım matematiksel hesaplamalara tabi tutarak veriler elde etmek isteyebilirsiniz. Raporlamaların, grafiksel gösterimlerin büyük yer kapladığı uygulamalarda bu gereksinimlere ihtiyaç duyulur. Bu yazı genelinde Django ORM ile bu işlemleri nasıl yürütebileceğimizi, kullanılabilecek fonksiyonları açıklamaya çalışacağım. Ufaktan başlayalım.
Örnek olması ve bol bol hesaplama türetebilmek adına şirketlerin ödeme kayıtlarını tutan bir uygulama geliştirdiğimizi varsayalım. Bu uygulama basitce aşağıdaki gibi 2 modele sahip olsun.
class Company(models.Model): name = models.CharField(max_length=60) def __str__(self): return self.name class Payment(models.Model): company = models.ForeignKey( Company, on_delete=models.CASCADE, related_name="payments" ) amount = models.DecimalField(decimal_places=2, max_digits=7) date = models.DateField() def __str__(self): return f"{self.company.name} paid {self.amount} date {self.date}"
Not. Django varsayılan olarak Sqlite ile çalışır. Fakat Sqlite doğrudan DecimalField veya bazı tarih bazlı operasyonları desteklemez. Sorunsuz takip edebilme adına Postgresql veya Mysql kullanmanız önerilir.
Ufak bir management komutu yazarak ve çalıştırarak rastgele veri üretelim.
Management commandlar ile ilgili daha fazla okumak isterseniz.
# your_app/management/commands/create_random_payments from django.core.management import BaseCommand from company.models import Payment, Company import random, datetime class Command(BaseCommand): help = "Creates a random payments for TechGeek, Dozkers, Appify, ClusterArmy" def create_random_date(self): current_year = datetime.datetime.now().year return datetime.datetime.strptime( "{} {}".format(random.randint(1, 366), current_year), "%j %Y" ).date() def handle(self, *args, **kwargs): companies = [ Company(name="Dozkers"), Company(name="Appify"), Company(name="TechGeek"), Company(name="ClusterArmy"), ] Company.objects.bulk_create(companies) companies = Company.objects.all() payments = [ Payment( company=companies.order_by("?").first(), amount=round(random.uniform(100, 99999), 2), date=self.create_random_date(), ) for _ in range(20) ] Payment.objects.bulk_create(payments) self.stdout.write("Random data created.")
Aggregate ve annotate kavramlarına göz atalım.
Aggregate vs Annotate.
Aggregate bir queryset üzerinde çalışarak tüm queryseti kapsayan tek bir sonuç döndürür. Bu filtrelenen querysetin bir belirli bir alanının toplamı, ortalaması veya standart sapması olabilir.
Annotate işlemi ise yine queryset üzerinde çalışır fakat tek bir sonuç döndürmek yerine, o queryset içinde bulunan her nesne için belirtilen işlemi uygular ve sonucunda da dahil olduğu bir queryset döner.
Farkı biraz daha somut ifade edecek olursak; sistemde kayıtlı tüm ödemelerin toplam tutarını bulmak bir aggregation, her bir şirketin yaptığı ödeme sayılarını hesaplamak annotation işlemidir.
Örnekler.
Bir çok farklı kullanımı deneyimleyebileceğimiz bir liste yapalım ve sırasıyla gerekli işlemleri gerçekleştirip, üzerine konuşalım. (Örnekler daha çoğaltılabilir aklıma hemen gelen bir kaç tanesini ekliyorum.)
- Toplam şirket sayısı.
- Toplam ödeme sayısı.
- Tüm şirketlerin ödeme sayıları.
- Seçilen şirketin maksimum ödeme tutarı.
- Seçilen şirketin minimum ödeme tutarı.
- Seçilen şirketin ortalama ödeme tutarı.
- Şirketlerin yaptığı ortalama ödeme tutarı.
- Seçilen şirketin aylar bazında yaptığı toplam ödeme miktarı.
- Tarihlere göre ayrılmış şekilde her tarihte yapılmış ortalama ödeme.
Toplam şirket sayısı için.
>>> Company.objects.count() 4
Toplam ödeme sayısı.
>>> Payment.objects.count() 20
Bu iki örnek aslında sürekli kullandığımız aggregate işlemi.
Tüm şirketlerin ödeme sayıları.
Şirketlerin isimlerini sunduğumuz bir API miz olduğunu varsayalım. Ekstra bilgi olarak artık toplam yaptıkları ödemeleri de dönecek cevaba ekleyeceğiz.
>>> from django.db.models import Count >>> Company.objects.all().annotate(Count('payments')).values("payments__count", "name") <QuerySet [{'name': 'Dozkers', 'payments__count': 5}, {'name': 'Appify', 'payments__count': 5}, {'name': 'TechGeek', 'payments__count': 3}, {'name': 'ClusterArmy', 'payments__count': 7}]>
İlk veritabanı fonskiyonumuzu içeri aktardık sonra, tüm şirketler üzerinde related_name’i kullanarak gerekli sayma işlemini yapıp, listeyi dönüyoruz. Dikkat ettiyseniz Django hesaplanan yeni alanı ön ek daha sonra yapılan işlemi birleştirerek key olarak dönüyor. Bu durumu değiştirmek için pekala aynı sorguyu şu şekilde de yazabilir ve bir alias atayabiliriz.
Tüm aggregation ve annotation işlemlerini aliaslar ile yönetebilirsiniz.
>>> Company.objects.all().annotate(total_payment = Count('payments')).values("total_payment", "name") <QuerySet [{'name': 'Dozkers', 'total_payment': 5}, {'name': 'Appify', 'total_payment': 5}, {'name': 'TechGeek', 'total_payment': 3}, {'name': 'ClusterArmy', 'total_payment': 7}]>
Seçilen şirketin maksimum ödeme tutarı.
Dozkers adlı şirket üzerinde çalışalım.
>>> from django.db.models import Max >>> Company.objects.filter(name="Dozkers").aggregate(max_amount=Max("payments__amount")) {'max_amount': Decimal('64267.73')}
Burada şirketleri filtrelediğimize dikkat edelim. Django ilk olarak bizim için gerekli filtreye göre gruplama işlemini yapıyor daha sonrasında fonksiyon yardımı ile cevabı dönüyor.
Seçilen şirketin minimum ödeme tutarı.
>>> from django.db.models import Min >>> Company.objects.filter(name="Dozkers").aggregate(min_amount=Min("payments__amount")) {'min_amount': Decimal('2603.16')}
Seçilen şirketin ortalama ödeme tutarı.
from django.db.models import Avg >>> Company.objects.filter(name="Dozkers").aggregate(avg_amount=Avg("payments__amount")) {'avg_amount': Decimal('48373.490000000000')}
Teker teker hesaplamaları yaptık fakat hepsini tümleşik olarak da dönebilirdik. Yukarıda dönülen tüm değerleri şu sorguyla kolayca tek seferde elde edebiliriz.
>>> Company.objects.filter(name="Dozkers").aggregate(avg_amount=Avg("payments__amount"), max_amount=Max("payments__amount"), min_amout=Min("payments__amount")) {'avg_amount': Decimal('48373.490000000000'), 'max_amount': Decimal('64267.73'), 'min_amout': Decimal('2603.16')}
Şirketlerin yaptığı ortalama ödeme tutarı.
Sistemdeki ortalama ödeme tutarını bulalım.
>>> Payment.objects.aggregate(avg_amount=Avg("amount")) {'avg_amount': Decimal('45257.022500000000')}
Seçilen şirketin aylar bazında yaptığı toplam ödeme miktarı.
Daha komplike ama tam da gerçek kullanım senaryosuna yakın bir hesaplama yapalım. Bir şirketin ödeme yaptığı aylarda, ay bilgisin, o ayki toplam ödeme tutarını ve sayısını bulalım. Kullandıklarımızın aksine yardımcı bir veritabanı fonksiyonu daha kullanacağız ExtractMonth. Bizim için üzerinde çalışılan tarih alanından ayı elde etmemizi sağlayacak. Sorguyu yazalım ve konuşalım.
>>> from django.db.models.functions import ExtractMonth >>>Company.objects.filter(name="Dozkers").values(month=ExtractMonth("payments__date")).annotate(sum_of_month=Sum("payments__amount"), payment_count=Count("payments")) <QuerySet [{'month': 1, 'sum_of_month': Decimal('52266.42'), 'payment_count': 1}, {'month': 3, 'sum_of_month': Decimal('62023.28'), 'payment_count': 1}, {'month': 5, 'sum_of_month': Decimal('63310.02'), 'payment_count': 2}, {'month': 7, 'sum_of_month': Decimal('64267.73'), 'payment_count': 1}]>
İlk olarak şirketi filtreliyoruz. Daha sonra şirketin ödemelerinin aylarını hesaplıyor ve aylara göre gruplama işlemini gerçekleştiriyoruz. Veri gruplandıktan total ödemeyi ve o aya ait ödeme sayısını hesaplayarak işlemi tamamlıyoruz.
Tarihlere göre ayrılmış şekilde her tarihte yapılmış toplam ödeme ve sayısı.
>>> Payment.objects.values("date").annotate(total_of_date=Sum("amount"), count=Count("id")).order_by("-count") <QuerySet [{'date': datetime.date(2021, 3, 8), 'total_of_date': Decimal('122730.14'), 'count': 2}, {'date': datetime.date(2021, 9, 9), 'total_of_date': Decimal('39413.67'), 'count': 1}, {'date': datetime.date(2021, 6, 28), 'total_of_date': Decimal('65506.26'), 'count': 1}, {'date': datetime.date(2021, 9, 21), 'total_of_date': Decimal('68775.92'), 'count': 1}, {'date': datetime.date(2021, 5, 22), 'total_of_date': Decimal('2603.16'), 'count': 1} ...continues...
Tarihe göre grupluyor daha sonra o tarihteki toplam ödemeyi hesaplıyor ve o grubun içersine dahil olan ödemelerin idlerini sayarak sayısını o tarihte ki ödeme sayılarını buluyoruz. Toplam geliri hesaplamak için birebir 🙂 Fark etmiş iseniz order_by ile hesapladığımız alanı sıralama için kullandık. Hesaplanan alanların zincirlenerek kullanımı için bir sonraki başlığa bakın.
Sorguları zincirlemek.
Django ile annotate veya aggregation işlemleri diğer ORM metodları ile zincirlenebilir. Yukarıda aggregate ve filter ile bir çok kullanım bulunmakta. Fakat kullanım yerleri önem teşkil etmekte. annotate metodu kullanım yerine göre son metod ya da ara metod olarak konumlandırabilir fakat aggregate ilgili queryset için bir cevap döndüğünden mutlaka en sona konumlandırılmalıdır. Aşağıda ki 2 örnek zincirlenerek kullanım hakkında sizi fikir sahibi yapacaktır.
- ay veya daha sonra yapılmış ödemelerin ortalaması.
>>> Payment.objects.filter(date__month__gte=1).aggregate(avg=Avg("amount")) {'avg': Decimal('45257.022500000000')}
2. en çok ödeme kaydı bulunan şirketlerin büyükten küçüğe sıralanarak isimlerinin ve ödeme sayılarının basılması.
Company.objects.annotate(payments_count=Count("payments")).order_by("-payments_count").values("name","payments_count") <QuerySet [{'name': 'ClusterArmy', 'payments_count': 7}, {'name': 'Appify', 'payments_count': 5}, {'name': 'Dozkers', 'payments_count': 5}, {'name': 'TechGeek', 'payments_count': 3}]>
Sonuç.
Django ORM ile aggreation konusuna ve genel kullanım örneklerine göz attık. Tabii ki ne yardımcı fonksiyonlar ne de ince trickler bu kadarla sınırlı değil. Daha derin bir bilgi sahibi olmak için dökümantasyonun linki burada. Beğenmeniz ve paylaşmanız durumunda kısıtlı olan Türkçe içeriği daha fazla insana ulaştırabiliriz. Şimdiden teşekkürler, kendinize iyi bakın 🙂
İlk Yorumu Siz Yapın