Veritabanına yaptığımız sorgu sayısını düşürerek ciddi bir performans artışı sağlayabiliriz. Nesnenin varlığını kontrol ederken, sadece sayım yaparken ya da yalnızca belirli alanlara ihtiyacımız olduğunda ilgili durumlar için hazırlanmış spesifik methodları kullanarak hem daha okunabilir bir kod elde etmiş, hem de sorguların dönüş süresini azaltmış oluruz.

Django sorgu setleri genelde yaramaz çalışırlar. Sorgu sonuçlarını kullanana kadar, veritabanına erişim sağlamaz, ilk kullanım zamanında oluşturulan sorgu setini ham SQL sorgusuna çevirir ardından veritabanına erişirler.

Tüm makaleleri terminal ekranına yazdırmaya çalıştığım aşağıdaki örnekte, ilk aşamada veritabanı ile bağlantı kurulmaz. Hemen alt satırda bu sorgu setini ekrana yazdırmaya çalıştığımda ise ORM sorgu setini SQL sorgusuna çevirir ve veritabanından ilgili sonuçları getirir. Ardından gelen değer ekrana yazılır.


# Burada veritabanına erişim yok.
articles = Article.objects.all()

# Şimdi, veritabanına bağlanılıp ilgili kayıtlar alınacak.
print(articles)

Önbellekleme ve Sorgu Setleri

Django, sorgu setlerini ilk defa çalıştırıp sonuçları getirdikten sonra bu sonuçları önbellekte saklar.

Örneğin, hemen aşağıda veritabanına iki defa sorgu yapılıyor:


titles = [article.title for article in Article.objects.all()]
slugs = [article.slug for article in Article.objects.all()]

Bunu önlemek için, aşağıdaki gibi, Django'nun önbellekleme sisteminin devreye girebileceği bir ortam hazırlayabilirim:


# Aktivite yok.
articles = Article.objects.all()

# Sonuçlar veritabanından alınıp, önbelleğe yazılıyor.
titles = [article.title for article in articles]

# Önbellekteki sonuçlar kullanılıyor.
slugs = [article.slug for article in articles]

Ya da diğer bir yolla, aşağıdaki gibi, sorgu sonuçlarını anahtar - değer formatında alabilirim:


articles = Article.objects.all()
results = dict(articles.values_list('title', 'slug'))
titles, slugs = results.keys(), results.values()

Aşağıdaki örnekte, halihazırda veritabanından aldığım sonuçların ilk beş ve son beş kaydını almak istiyorum. Ancak Django her seferinde veritabanına sorgu yapıyor.


articles = Article.objects.all()
first_five_articles = articles[:5] # Veritabanına sorgu yapar.
first_ten_articles = articles[:10] # Veritabanına sorgu yapar.

Sorgu setlerinin kullanılmadan önbelleklenmeyeceğini söylemiştim. Kullanmak, sadece ekrana yazdırmak ya da bir hesaplamada kullanmak anlamında değil. Örneğin aşağıda bool fonksiyona tabi tutuyorum. Böylece Django sorgu setini önbellekliyor ve sonradan yaptığım işlemlerde önbellekteki kayıtları kullanabiliyorum.


articles = Article.objects.all()

# Sorgu seti kullanıldı ve sonuçlar önbelleklendi.
bool(articles)

first_five_articles = articles[:5] # Önbelleği kullanır.
first_ten_articles = articles[:10] # Önbelleği kullanır.

Q Nesnesi ile Karmaşık Veritabanı Sorguları

Django ORM sorguları filtreleme yaparken koşulları AND ile birleştirir. Yani eklediğiniz koşulların hepsinin True olması gerekir. Daha doğrusu, sadece eklediğiniz tüm koşulların True olması durumunda elde edebileceğiniz sonuçları alabilirsiniz.

Örneğin aşağıda, yayınlanmış olan makaleler içerisinde arama yapıyorum:


Article.objects.filter(title__contains='django', is_published=True;

Bu sorgu seti, arka tarafta ORM tarafından aşağıdaki SQL sorgusuna dönüştürülüyor:


SELECT * FROM article_table WHERE title LIKE '%django%' AND is_published=true;

Ancak, eğer istediğim terimi hem başlıkta hem de makale içeriğinde aramak istersem muhtemelen şuna benzer birşey yapmam gerekecek:


articles = Article.objects.all()
results = []
for article in articles:
    if 'django' in article.title or 'django' in article.body:
        results.append(article)

Yukarıdaki örnekte, tüm kayıtlar getiriliyor ve başlık ya da makale içeriğinde 'django' kelimesi geçenler ayıklanıyor. Göründüğü üzere, pek yakışıklı bir yol değil.

Aynı işlemi Q nesnesini kullanarak aşağıdaki gibi yapabiliyorum:


from django.db.models import Q

articles = Article.objects.filter(
    Q(title__contains='django') | Q(body__contains='django')
)

Bu kodun arkada üretilen SQL karşılığı ise şu:


SELECT * FROM article_table WHERE title LIKE '%django%' OR body LIKE '%django%';

Sorgu setlerini hazırlarken Q nesnesini AND, OR, NOT koşulları için kullanabilirim.

işlemoperatöraçıklama
OR|Belirtilen koşullardan herhangi birinin True olması beklenir.
AND&Belirtilen koşullardan hepsinin True olması beklenir.
NOT~Belirtilen koşulu tersine çevirir. True ise False ya da tam tersi.

Q koşullarının işlem önceliğini ayarlamak için parantezleri kullanabilirim. Örneğin aşağıdaki sorgu seti ile, başlığında ya da makale içeriğinde 'django' geçen ve yayınlanmış makaleleri getiriyorum:


Article.objects.filter(
    (
        Q(title__contains='django') | Q(body__contains='django')
    ) & Q(is_published=True)
)

Toplu Nesne Oluşturma

Birden fazla nesne oluştururken, klasik create methodu yerine, bulk_create methodunu kullanarak veritabanına yapılan sorgu sayısını düşürebiliriz.

Aşağıdaki senaryoda, dört tane makale oluşturmak için, ilgili bilgileri bir liste içine koyup, bir döngü yardımıyla veritabanına dört defa sorgu atıyorum:


article_details = [
    ('Güzel Bir Başlık', 'Güzel bir içerik.'),
    ('Süper Bir Başlık', 'Süper bir içerik.'),
    ('Harikulade Bir Başlık', 'Harikulade bir içerik.'),
    ('On Numara Bir Başlık', 'On numara bir içerik.')
]
for title, body in article_details:
     Article.objects.create(title=title, body=body)

Oysa, aynı işlemi tek bir sorgu ile aşağıdaki gibi halledebilirdim:


articles = [Article(title=title, body=body) for title, body in article_details]
Article.objects.bulk_create(articles)

F Nesnesi ile Toplu Güncelleme

Bir modelin bir alanının mevcut değerini almak için F nesnesini kullanırız. Böylece, güncelleme yaparken, önce varolan değeri almak için bir sorgu, ardından yeni değeri yazmak için ikinci bir sorgu atmaya gerek kalmaz. Tabii sorgu sayısı güncellemek istenen kayıt sayısına bağlı olarak katlanarak artacaktır.

Örneğin, tüm çalışanların maaşını 500 TL artırmak için aşağıdaki işlemleri yapmam gerekir:


employees = Employee.objects.all()
for employee in employees:
    employee.salary = employee.salary + 500
    employee.save()

Aynı işlemi F nesnesi kullanarak aşağıdaki gibi yapabilirim:


from django.db.models import F

employees = Employee.objects.all()
employees.update(salary=F('salary') + 500)

Sadece Gerekli Alanları Getirme

Eğer model methodları ya da ilişkili nesneler yerine sadece veriye (alanlar ve değerler) ihtiyacımız varsa, sorgu süresini düşürmek için values methodu ile sadece ilgili nesneye/nesnelere ait veriyi dictionary olarak getirebiliriz. Eğer ona da gerek yoksa values_list ile veriyi tuple formatında alabiliriz.

  • Örneğin, tüm çalışanların bilgilerini getirelim:

    
    # Veritabanına erişim yok.
    employees = Employee.objects.all()
    
    # Nesnenin alanları dictionary olarak geliyor.
    employees_data = employees.values()
    

    Yukarıdaki sorgu aşağıdaki sonucu verecek:

    
    [
        {'id': 1, 'name': 'Umut', 'salary': 30000},
        {'id': 2, 'name': 'Çağdaş', 'salary': 40000},
    ]
    
  • Eğer alanların hepsi gerekli değil ise sadece belirttiğim alanların getirilmesini sağlayabilirim:

    
    employees = Employee.objects.all()
    employees_data = employees.values('name', 'salary')
    

    Bu sefer, yukarıdaki sorgu aşağıdaki sonucu verecek:

    
    [
        {'name': 'Umut', 'salary': 30000},
        {'name': 'Çağdaş', 'salary': 40000},
    ]
    
  • Eğer bu sonucu tuple formatında almak isteseydim aşağıdaki gibi values_list methodunu kullanırdım:

    
    employees = Employee.objects.all()
    employees_data = employees.values_list('name', 'salary')
    

    Ve aşağıdaki gibi bir sonuç elde ederdim:

    
    [
        ('Umut', 30000),
        ('Çağdaş', 40000),
    ]
    
  • Eğer sadece bir alanı (örneğin; isim) almak isteseydim, values_list methoduna flat parametresi göndererek tüm veriyi bir liste halinde alabilirdim:

    
    employees = Employee.objects.all()
    employees_data = employees.values_list('name', flat=True)
    

    Ve aşağıdaki gibi tüm isimleri liste halinde alırdım:

    
    ['Umut', 'Çağdaş']
    

    Ancak dikkat edin, flat parametresi yalnızca bir alan getirmek istediğimiz durumlarda işe yarar.

İlişkili Nesneleri Tek Sorguda Getirme

Model eğer başka modeller ile ilişki içeriyorsa ve bu alanları kullanmak istiyorsak, ilişkili nesneleri de getirerek sorgu sayısını düşürmemiz gerekir.

Makale içindeki author ve category alanlarının ForeignKey olduğunu varsayarsak, aşağıdaki örnekte veritabanına toplamda 3 sorgu yaparız:


article = Article.objects.get(id=1)
author = article.author # Veritabanına sorgu yapar.
category = article.category # Veritabanına sorgu yapar.

Üçüncü sorgu nereden yapıldı diye soracak olabilirsiniz; sorgu setlerinin kullanılma durumunda veritabanına sorgu yaptığını söylemiştim. İlk kullanımda (author alanını alırken) nesnenin kendisini alıyor (1), ardından author alanına bağlı User nesnesini alıyor (2) ve son olarak category alanına bağlı Category nesnesini alıyor (3).

Bunun yerine, select_related methodunu kullanarak tüm bu işlemleri tek sorguda halledebilirim:


article = Article.objects.select_related().get(id=1)
author = article.author # Aktivite yok.
category = article.category # Aktivite yok.

Ya da yalnızca istediğim alanları önceden getirebilirim:


article = Article.objects.select_related('category').get(id=1)
author = article.author # Veritabanına sorgu yapar.
category = article.category # Aktivite yok.

Ancak unutmamak gerekir, select_related methodu yalnızca ForeignKey ya da OneToOneField için kullanılabilir.

Eğer ManyToMany ya da ManyToOne ilişkiler ile kullanmak isterseniz, kullanım şekli select_related ile aynı olan prefetch_related methodunu kullanabilirsiniz.

Nesneleri Sayma

Sorgu setinden dönen nesneleri saymak için, gelen nesnelerin sayısını len fonksiyonu ile kontrol etmek yerine count methodunu kullanmalıyız. Böylece yalnızca sayım yapmak için tüm nesnelerin verisini çekmediğimiz için sorgu oldukça hızlı çalışacak.

Kullanımı oldukça basit, şöyle:


count = Article.objects.filter(is_published=True).count()

Nesnelerin Varlığını Kontrol Etme

Eğer bir nesnenin yalnızca var olup olmadığını kontrol etmek istiyorsak, nesneyi çekip ardından if ile None olup olmadığını kontrol etmek yerine, exists methodunu kullanmalıyız:


is_exists = Article.objects.filter(author_id=3).exists()

Dönen değer tahmin edeceğiniz üzere True ya da False olacaktır.

Kolay gelsin. :)