Sorgun hazır
SELECT u.id, u.name, COUNT(*) AS siparis_adedi, SUM(o.amount) AS toplam_tutarFROM orders AS oJOIN users AS u ON u.id = o.user_idWHERE o.created_at >= NOW() - INTERVAL '30 days' AND o.status = 'completed'GROUP BY u.id, u.nameORDER BY siparis_adedi DESC, toplam_tutar DESCLIMIT 10;Ne yapıyor
JOIN users AS u ON u.id = o.user_idSiparişi sahibine bağlar; kullanıcı adını sipariş tablosunda tutmadığımız için bu birleştirme şart.
WHERE o.created_at >= NOW() - INTERVAL '30 days'Sabit tarih yazmak yerine koşulduğu ana göre son 30 günü alır; sorgu yarın da doğru çalışır.
AND o.status = 'completed'İptal ve iade edilmiş siparişler sayıma girmez — “en çok sipariş veren” tamamlanmış siparişi olan demektir.
GROUP BY u.id, u.nameSatırlar kullanıcı başına toplanır. `u.id` birincil anahtar olduğu için PostgreSQL `u.name`i istemez ama yazmak taşınabilirliği artırır.
ORDER BY siparis_adedi DESC, toplam_tutar DESCAdet eşitse tutarı yüksek olan öne geçer; sıralama böylece kararlı olur.
Örnek sonuç
| id | name | siparis_adedi | toplam_tutar |
|---|---|---|---|
| 1 | Ayşe Yıldız | 3 | 815.00 |
| 2 | Mehmet Kaya | 2 | 1380.00 |
| 5 | Zeynep Ak | 1 | 540.00 |
Bu satırlar rehberdeki örnek şema bir SQLite dosyasına kurulup sorgu gerçekten koşularak alındı.
Performans notu
- CREATE INDEX ON orders (created_at DESC) WHERE status = 'completed'; — kısmi index, tarama yalnız tamamlanmış siparişlere iner.
- orders (user_id) üstünde index yoksa birleştirme her satırda users tablosunu tarar.
- Milyonlarca satırda `COUNT(*)` yerine önceden toplanmış bir özet tablo (günlük materialized view) düşünülmeli.
Lehçe farkı
MySQL’de `NOW() - INTERVAL 30 DAY`, SQL Server’da `DATEADD(day, -30, GETDATE())`, SQLite’ta `date('now', '-30 days')` yazılır. Çıktı kolonu adını ORDER BY içinde kullanmak PostgreSQL ve MySQL’de serbest, SQL Server’da değildir.