← Kursa Dön
📄 Text · 35 min

Aggregate Fonksiyonlar: COUNT, SUM, AVG, MIN, MAX

Giriş — Veriden Bilgiye Geçiş

Buraya kadar verileri tek tek sorguladık — "şu müşteriyi getir", "bu ürünün fiyatını göster". Ama gerçek dünyada çoğu zaman özetlenmiş bilgi isteriz: "Toplam kaç siparişimiz var?", "Ortalama sipariş tutarı ne?", "En pahalı ürün hangisi?"

Aggregate fonksiyonlar (toplama/gruplama fonksiyonları), birden fazla satırdaki verileri alıp tek bir sonuç döndürür. 1000 satırı alıp 1 sayıya dönüştürür — toplam, ortalama, sayım, minimum, maksimum.

🎯 Analoji: Bir sınıf düşün. 30 öğrencinin sınav notu var. Her birini tek tek söylemek yerine "sınıf ortalaması 72" demek çok daha bilgilendirici. İşte aggregate fonksiyonlar veritabanı için aynı şeyi yapar.


COUNT — Sayma

COUNT(*) — Tüm Satırları Say

-- Toplam müşteri sayısı
SELECT COUNT(*) AS total_customers FROM customers;
-- 10

-- Toplam sipariş sayısı
SELECT COUNT(*) AS total_orders FROM orders;
-- 10

-- Koşullu sayma
SELECT COUNT(*) AS istanbul_customers 
FROM customers 
WHERE city = 'İstanbul';
-- 4

COUNT(sütun) — NULL Olmayan Değerleri Say

-- Telefonu olan müşteri sayısı (NULL olmayanlar)
SELECT COUNT(phone) AS customers_with_phone FROM customers;
-- 9 (1 müşterinin telefonu NULL)

-- COUNT(*) vs COUNT(phone) farkı
SELECT 
    COUNT(*) AS total,               -- 10 (tüm satırlar)
    COUNT(phone) AS with_phone,      -- 9 (NULL olmayan)
    COUNT(*) - COUNT(phone) AS without_phone  -- 1 (NULL olan)
FROM customers;

COUNT(DISTINCT sütun) — Benzersiz Değerleri Say

-- Kaç farklı şehirden müşterimiz var?
SELECT COUNT(DISTINCT city) AS unique_cities FROM customers;
-- 5

-- Kaç farklı müşteri sipariş vermiş?
SELECT COUNT(DISTINCT customer_id) AS unique_buyers FROM orders;
-- 7

-- Kaç farklı ürün satılmış?
SELECT COUNT(DISTINCT product_id) AS unique_products_sold FROM order_items;

SUM — Toplama

-- Toplam satış tutarı
SELECT SUM(total_amount) AS total_revenue FROM orders;
-- 138769.87

-- İstanbul'daki müşterilerin toplam harcaması
SELECT SUM(o.total_amount) AS istanbul_revenue
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
WHERE c.city = 'İstanbul';

-- Toplam stok değeri (fiyat × adet)
SELECT SUM(price * stock_quantity) AS total_inventory_value
FROM products
WHERE is_active = TRUE;

-- Bir siparişin toplam tutarını hesapla
SELECT order_id,
       SUM(quantity * unit_price * (1 - discount_percent / 100)) AS calculated_total
FROM order_items
WHERE order_id = 1
GROUP BY order_id;

⚠️ Dikkat: SUM() NULL değerleri atlar. Tüm değerler NULL ise sonuç NULL olur. Bunu önlemek için: COALESCE(SUM(amount), 0).


AVG — Ortalama

-- Ortalama sipariş tutarı
SELECT AVG(total_amount) AS avg_order FROM orders;
-- 13876.99

-- Yuvarlanmış ortalama
SELECT ROUND(AVG(total_amount), 2) AS avg_order FROM orders;

-- Ortalama ürün fiyatı
SELECT ROUND(AVG(price), 2) AS avg_price FROM products;

-- Kategoriye göre ortalama fiyat
SELECT category_id, ROUND(AVG(price), 2) AS avg_price
FROM products
GROUP BY category_id;

AVG ve NULL Tuzağı

-- Veriler: 100, 200, NULL, 300
-- AVG() NULL'ları ATLAR: (100+200+300) / 3 = 200
-- NULL'ları 0 sayarak: (100+200+0+300) / 4 = 150

-- NULL'ları 0 olarak dahil etmek:
SELECT AVG(COALESCE(discount_percent, 0)) AS avg_discount FROM products;

MIN ve MAX — Minimum ve Maksimum

-- En düşük ve en yüksek fiyat
SELECT MIN(price) AS cheapest, MAX(price) AS most_expensive
FROM products;

-- En eski ve en yeni sipariş
SELECT MIN(order_date) AS first_order, MAX(order_date) AS last_order
FROM orders;

-- En düşük ve en yüksek maaş
SELECT MIN(salary) AS min_salary, MAX(salary) AS max_salary
FROM employees;

-- String'lerde MIN/MAX (alfabetik)
SELECT MIN(first_name) AS first_alpha, MAX(first_name) AS last_alpha
FROM customers;
-- MIN: 'Ahmet' (alfabetik ilk), MAX: 'Zeynep' (alfabetik son)

Aggregate Fonksiyonları Birlikte Kullanma

-- Ürün istatistikleri (tek sorguda)
SELECT 
    COUNT(*) AS total_products,
    COUNT(DISTINCT category_id) AS category_count,
    ROUND(MIN(price), 2) AS min_price,
    ROUND(MAX(price), 2) AS max_price,
    ROUND(AVG(price), 2) AS avg_price,
    ROUND(SUM(price * stock_quantity), 2) AS total_inventory_value,
    SUM(stock_quantity) AS total_stock
FROM products
WHERE is_active = TRUE;
+----------------+----------------+-----------+---------------+-----------+----------------------+-------------+
| total_products | category_count | min_price | max_price     | avg_price | total_inventory_value| total_stock |
+----------------+----------------+-----------+---------------+-----------+----------------------+-------------+
| 15             | 7              | 89.99     | 64999.99      | 13009.99  | 12345678.90          | 3405        |
+----------------+----------------+-----------+---------------+-----------+----------------------+-------------+

Koşullu Aggregate (CASE + Aggregate)

Aggregate fonksiyonlar içinde CASE WHEN kullanarak koşullu toplama/sayma yapabilirsin:

-- Sipariş durumlarına göre dağılım (tek sorguda)
SELECT 
    COUNT(*) AS total_orders,
    COUNT(CASE WHEN status = 'pending' THEN 1 END) AS pending,
    COUNT(CASE WHEN status = 'processing' THEN 1 END) AS processing,
    COUNT(CASE WHEN status = 'shipped' THEN 1 END) AS shipped,
    COUNT(CASE WHEN status = 'delivered' THEN 1 END) AS delivered,
    COUNT(CASE WHEN status = 'cancelled' THEN 1 END) AS cancelled
FROM orders;

-- SUM ile koşullu toplam
SELECT 
    SUM(total_amount) AS total_revenue,
    SUM(CASE WHEN status = 'delivered' THEN total_amount ELSE 0 END) AS delivered_revenue,
    SUM(CASE WHEN status = 'cancelled' THEN total_amount ELSE 0 END) AS cancelled_amount,
    ROUND(
        SUM(CASE WHEN status = 'delivered' THEN total_amount ELSE 0 END) * 100.0 
        / SUM(total_amount), 2
    ) AS delivery_rate_pct
FROM orders;

-- Fiyat aralığına göre ürün dağılımı
SELECT 
    COUNT(CASE WHEN price < 100 THEN 1 END) AS ekonomik,
    COUNT(CASE WHEN price BETWEEN 100 AND 999.99 THEN 1 END) AS uygun,
    COUNT(CASE WHEN price BETWEEN 1000 AND 9999.99 THEN 1 END) AS orta,
    COUNT(CASE WHEN price >= 10000 THEN 1 END) AS premium
FROM products;

Bu teknik, birden fazla sayma/toplama işlemini tek bir sorgu ile yapmanı sağlar — her koşul için ayrı sorgu yazmana gerek kalmaz.


Aggregate ve WHERE: Önemli Kural

-- ✅ WHERE ile filtreleyip sonra aggregate
SELECT AVG(price) AS avg_expensive_price
FROM products
WHERE price > 1000;

-- ❌ HATA — WHERE'de aggregate fonksiyon kullanılamaz!
SELECT * FROM products WHERE price > AVG(price);
-- ERROR! AVG() WHERE'de kullanılamaz

-- ✅ Çözüm: Subquery
SELECT * FROM products 
WHERE price > (SELECT AVG(price) FROM products);

⚠️ Dikkat: WHERE tek satır bazında filtreler, aggregate ise birden fazla satırı gruplar. WHERE çalışırken aggregate sonucu henüz belli değildir. Aggregate sonucuna göre filtreleme yapmak istiyorsan HAVING kullan (bir sonraki bölümde göreceğiz) veya subquery kullan.


Aggregate Fonksiyonlar Olmadan Single-Row

Aggregate fonksiyonunu GROUP BY olmadan kullandığında tüm tablo tek bir grup olarak ele alınır ve sonuç her zaman tek satır döner:

SELECT COUNT(*), SUM(price), AVG(price), MIN(price), MAX(price)
FROM products;
-- Tek satır sonuç — tüm ürünlerin özeti

GROUP BY ile kullandığında ise her grup için bir satır döner — bunu bir sonraki bölümde detaylıca göreceğiz.


Gerçek Dünya Örneği — E-Ticaret KPI Dashboard

-- Anahtar Performans Göstergeleri (KPI) Dashboard
SELECT 
    -- Genel metrikler
    (SELECT COUNT(*) FROM customers WHERE is_active = TRUE) AS aktif_musteriler,
    (SELECT COUNT(*) FROM products WHERE is_active = TRUE AND stock_quantity > 0) AS stokta_urunler,
    
    -- Sipariş metrikleri
    COUNT(*) AS toplam_siparis,
    COUNT(DISTINCT customer_id) AS siparis_veren_musteri,
    ROUND(SUM(total_amount), 2) AS toplam_gelir,
    ROUND(AVG(total_amount), 2) AS ortalama_siparis,
    ROUND(MIN(total_amount), 2) AS min_siparis,
    ROUND(MAX(total_amount), 2) AS max_siparis,
    
    -- Durum dağılımı
    ROUND(SUM(CASE WHEN status = 'delivered' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 1) 
        AS teslim_orani,
    ROUND(SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 1) 
        AS iptal_orani

FROM orders;

Sıkça Yapılan Hatalar

  1. COUNT(*) vs COUNT(sütun): COUNT(*) tüm satırları, COUNT(sütun) NULL olmayan değerleri sayar. Hangisini istediğini bil.

  2. AVG NULL tuzağı: AVG, NULL'ları atlar. 10 satırdan 3'ü NULL ise ortalama 7 satır üzerinden hesaplanır. NULL'ları 0 saymak istiyorsan COALESCE kullan.

  3. WHERE'de aggregate kullanmak: WHERE COUNT(*) > 5 yazamazsın — HAVING kullan.

  4. SUM'ın NULL dönmesi: Tüm değerler NULL ise SUM() NULL döner. COALESCE(SUM(amount), 0) kullan.

  5. SELECT'te aggregate ile non-aggregate sütun karıştırmak: SELECT product_name, AVG(price) FROM products — bu hangi product_name'i gösterecek? GROUP BY olmadan aggregate ve normal sütun karıştırma (MySQL strict mode'da hata verir).


Özet

  • COUNT(*) — tüm satırları sayar, COUNT(sütun) — NULL olmayanları sayar

  • COUNT(DISTINCT sütun) — benzersiz değerleri sayar

  • SUM() — toplam, AVG() — ortalama, MIN() — minimum, MAX() — maksimum

  • Aggregate fonksiyonlar NULL'ları atlar (COUNT(*) hariç)

  • CASE WHEN + aggregate = koşullu toplama/sayma (pivot-like)

  • WHERE'de aggregate fonksiyon kullanılamaz — HAVING veya subquery kullan

  • GROUP BY olmadan aggregate → tüm tablo tek grup → tek satır sonuç

  • Sonuçta NULL olasılığı → COALESCE(SUM(...), 0) ile önle