DJANGO ORM QUERYSET NEDİR?

Koyu yeşil Django wordmark logosunun büyük dominant biçimde tek başına merkezlendiği marka görseli

Django geliştiricilerinin büyük bölümü QuerySet'i "veritabanından satır getiren bir liste" sanır. Bu yanılgı yıllarca fark edilmeden taşınabilir — taa ki bir sayfa açılışı 200 sorguya çıkana kadar. Gerçekte QuerySet bir liste değildir; tembel (lazy) bir sorgu nesnesidir, gerçekten ihtiyaç duyulana kadar veritabanına dokunmaz. Bu makale Django ORM'in en kritik soyutlamasını sıfırdan ele alıyor: bir QuerySet ne zaman çalışır, neden zincirlenir, performans tuzakları nerede gizlenir. Web tarafında çalışan ya da öğrenmeye yeni başlayan Python geliştiricileri için Django'nun veri katmanını net biçimde kavramak temel bir kazanım.

QuerySet Tam Olarak Nedir

QuerySet, bir model üzerinde tanımladığınız veritabanı sorgusunu temsil eden Python nesnesidir. Article.objects.filter(published=True) yazdığınızda Python bellekte bir QuerySet instance oluşturur; bu noktada SQL henüz çalışmamıştır. Veritabanına gidiş, ancak siz sonucu iterate ettiğinizde, bir slice aldığınızda, listeye çevirdiğinizde veya len() çağırdığınızda gerçekleşir.

Django'nun tasarım kararı şudur: sorgu inşası ile sorgu çalıştırması ayrı aşamalardır. Bu sayede aynı QuerySet üzerine birden çok filtre üst üste eklenebilir, oluşan tek SQL sorgusu veritabanına en sonunda gider.

Lazy Evaluation Davranışı

Tembellik QuerySet'in karakter özelliğidir. Aşağıdaki kod hangi satırda veritabanına gider, hangi satırda gitmez?

q = Article.objects.filter(published=True)
q = q.filter(category="python")
q = q.order_by("-created_at")
q = q[:10]
for article in q:
    print(article.title)

İlk dört satır sıfır SQL üretir. Yalnızca son satırdaki for döngüsü veritabanını tetikler. Django o anda dört yapılandırmayı tek SQL içine derler:

SELECT * FROM articles
WHERE published = true AND category = 'python'
ORDER BY created_at DESC
LIMIT 10;

Bu davranışı bilmek view ile servis katmanı arasında QuerySet'leri taşımayı, koşullara göre dinamik sorgu inşa etmeyi mümkün kılar. Aynı zamanda bir tuzak da içerir: aynı QuerySet'i bir template içinde iki kez iterate ederseniz, Django sonucu cache'lediğinden ikinci tur bellekten okur. Ama iki ayrı QuerySet'i ardışık iterate ediyorsanız iki sorgu gider.

Sorgu Zincirleme ve Filtre Mantığı

QuerySet metotlarının çoğu yeni bir QuerySet döndürür. Yani filter(), exclude(), order_by(), annotate() orijinali bozmaz; üzerine yeni katman ekler. Bu immutability sayesinde zincirleme güvenlidir:

  • filter(field=value) — koşulu karşılayan satırları tutar
  • exclude(field=value) — koşulu karşılayanları çıkarır
  • order_by("-field") — sıralar; başındaki tire azalan demek
  • distinct() — yinelenen satırları teker
  • values("field") — model instance yerine dict döndürür
  • annotate() — her satıra hesaplanmış alan ekler

Birden fazla filter() zincirlemek AND mantığı kurar. OR için Q objesi kullanılır: filter(Q(published=True) | Q(featured=True)). Field lookup'ları çift alt çizgiyle yazılır: title__icontains="django", created_at__gte=date(2026, 1, 1), author__name__startswith="A". Son örnekte ikinci alt çizgi ilişki üzerinden ilerler — Django ORM JOIN'i sizin için kurar. Lookup operatörlerinin tam listesi ve örnek kullanımları için resmi sorgu rehberi başvurulabilecek en güncel kaynaktır.

N+1 sorgu problemini ve select_related JOIN çözümünü yan yana karşılaştıran sorgu sayısı diyagramı

QuerySet'i Gerçekten Çalıştıran Tetikleyiciler

Veritabanına ne zaman gidildiğini bilmek, performans analizinde temeldir. QuerySet aşağıdaki durumlarda kesinlikle SQL üretir:

  1. for ile iteration
  2. list(qs) dönüşümü
  3. Slicing — step içeriyorsa: qs[::2]
  4. bool(qs), if qs:
  5. len(qs) — alternatif: qs.count() daha verimlidir
  6. Pickling, repr, template'te ilk dolaşım
  7. exists(), count(), first(), last(), get(), aggregate() çağrıları

Sıkça karıştırılan iki nokta: qs[5:10] (offset+limit) sorgu çalıştırmaz, ancak qs[5:10:2] step içerdiği için Django sonucu çekip Python tarafında step'ler. Ayrıca if qs: tüm satırları çekip değerlendirir — sadece varlık testi yapacaksanız if qs.exists(): kullanın, o yalnızca SELECT 1 LIMIT 1 üretir.

N+1 Sorgu Problemi ve select_related

Django ORM ile yazılmış kodun en yaygın performans sorunu N+1'dir. İlişkili bir alan template'te ya da loop içinde her satır için ayrı sorgu üretirse, listenizdeki 100 makale 1 + 100 = 101 sorguya patlar.

# Sorunlu kod — N+1
articles = Article.objects.all()
for article in articles:
    print(article.author.name)  # her satır için ek SELECT

Çözüm yazarı baştan JOIN ile çekmektir:

# Tek sorgu — JOIN ile
articles = Article.objects.select_related("author")
for article in articles:
    print(article.author.name)

select_related ForeignKey ve OneToOne ilişkilerinde SQL JOIN üretir. Tek seferde ilişkili tablonun kolonlarını da çeker. ManyToMany veya reverse ForeignKey için JOIN tekrarlı satır üreteceğinden farklı bir araç gerekir: prefetch_related. Bu yöntem iki ayrı sorgu çalıştırır ve eşleştirmeyi Python tarafında yapar.

# M2M ve reverse ForeignKey için
articles = Article.objects.prefetch_related("tags", "comments")

Karar şu basit kuralla yapılır: ilişki "tek tarafa" doğruysa select_related, "çok tarafa" doğruysa prefetch_related. Django backend tarafına ciddi şekilde girmek için Django eğitimi ORM optimizasyonu, view katmanı ve REST framework entegrasyonunu birlikte ele alır.

QuerySet Üzerinde Yapılan Yaygın Hatalar

Üretimde sık görülen kalıplar:

  • count() yerine len() kullanmak: len(qs) tüm satırları belleğe çeker; qs.count() SQL COUNT(*) üretir, satırları getirmez
  • exists() yerine if qs: Varlık testi için tüm satırları çekmek; qs.exists() tek satır bile getirmez
  • QuerySet'i listeye çevirip filtrelemek: list(qs) sonrasında Python for ile süzmek; filtreyi ORM seviyesinde tutmak çok daha hızlıdır
  • only() ve defer() yanlış kullanımı: Bu metotlar belirli kolonları erteler/seçer; gerekmediği yerde eklenirse her erişimde ek sorgu doğurabilir
  • order_by'sız Paginator: Sıralama olmadan sayfalanan sorgu kayan sonuçlar üretir; aynı sayfa iki kez farklı sıra dönebilir

QuerySet Caching ve Yeniden Değerlendirme

Bir QuerySet ilk kez iterate edildiğinde sonucu kendi içinde cache'ler. Aynı instance ikinci kez döndürüldüğünde veritabanına gitmez. Ancak QuerySet üzerinde yeni bir metot çağırırsanız (örneğin qs.filter(...)) bu yeni bir QuerySet üretir, cache aktarılmaz.

Pratikte bunun anlamı: aynı sonucu birden fazla yerde kullanacaksanız QuerySet'i list() ile veya bir değişkene atayıp iterate ederek bir kez çalıştırın. Şablon (template) tarafında aynı QuerySet'i iki ayrı bloğa geçiriyorsanız view içinde önceden bir kez dolaşmanız ya da değerlendirmeniz tutarlı performans verir. Python web geliştirme tarafını derinleştirmek için Python eğitimi faydalı bir başlangıçtır.

Ne Zaman QuerySet, Ne Zaman Raw SQL

ORM hızlı geliştirme ve okunabilirlik için tasarlanmıştır. Raporlama sorguları, çok adımlı CTE'ler veya veritabanı-spesifik özelliklere ihtiyaç duyan analitik sorgular için ORM zorlanır. Django bu durumda iki kapı sunar: Manager.raw() bir SQL stringini model instance'larına döndürür; connection.cursor() tamamen kontrolü size bırakır.

Genel kural: CRUD ve liste/detay tipi okumalarda QuerySet, raporlama ve agresif optimizasyon gereken yerlerde raw SQL. İkisinin arasında Django Func, Subquery, Window gibi ileri ORM yapıları da bulunur — gerçek dünyada bunlar raw SQL'e olan ihtiyacın büyük bölümünü kapatır.