← Kursa Dön
📄 Text · 25 min

GROUP BY Birden Fazla Sütun

Giriş — Çok Boyutlu Gruplama

Tek sütuna göre gruplama yapmak çoğu zaman yetmez. "Her şehirdeki erkek ve kadın müşteri sayısı", "Her yılın her ayındaki satış toplamı", "Her kategorideki fiyat segmentine göre ürün dağılımı" — bunlar birden fazla sütuna göre gruplama gerektirir.

🎯 Analoji: Excel'de pivot tablo oluşturduğunu düşün. Satırlara "Şehir", sütunlara "Yıl" koyuyorsun ve kesişimde toplamı görüyorsun. GROUP BY ile birden fazla sütun kullanmak tam olarak bu "çok boyutlu" gruplama işlemidir.


İki Sütuna Göre Gruplama

-- Her şehirdeki sipariş durumu dağılımı
SELECT c.city, o.status, 
       COUNT(*) AS order_count,
       ROUND(SUM(o.total_amount), 2) AS total_revenue
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
GROUP BY c.city, o.status
ORDER BY c.city, o.status;
+----------+------------+-------------+---------------+
| city     | status     | order_count | total_revenue |
+----------+------------+-------------+---------------+
| Ankara   | delivered  | 1           | 64999.99      |
| Ankara   | pending    | 2           | 819.97        |
| İstanbul | delivered  | 2           | 56899.97      |
| İstanbul | pending    | 1           | 599.99        |
| İstanbul | processing | 1           | 899.99        |
| İzmir    | shipped    | 1           | 45299.98      |
| ...      | ...        | ...         | ...           |
+----------+------------+-------------+---------------+

Her şehir + durum kombinasyonu ayrı bir grup oluşturur.


Üç veya Daha Fazla Sütunla Gruplama

-- Yıl, Ay, Durum bazında sipariş raporu
SELECT 
    YEAR(order_date) AS year,
    MONTH(order_date) AS month,
    status,
    COUNT(*) AS order_count,
    ROUND(SUM(total_amount), 2) AS revenue
FROM orders
GROUP BY YEAR(order_date), MONTH(order_date), status
ORDER BY year, month, status;

-- Kategori, Fiyat Segmenti, Stok Durumu
SELECT 
    c.category_name,
    CASE 
        WHEN p.price < 500 THEN 'Ekonomik'
        WHEN p.price < 5000 THEN 'Orta'
        ELSE 'Premium'
    END AS price_segment,
    CASE 
        WHEN p.stock_quantity = 0 THEN 'Tükendi'
        WHEN p.stock_quantity < 50 THEN 'Düşük'
        ELSE 'Yeterli'
    END AS stock_status,
    COUNT(*) AS product_count
FROM products p
JOIN categories c ON p.category_id = c.category_id
GROUP BY c.category_name,
    CASE WHEN p.price < 500 THEN 'Ekonomik' WHEN p.price < 5000 THEN 'Orta' ELSE 'Premium' END,
    CASE WHEN p.stock_quantity = 0 THEN 'Tükendi' WHEN p.stock_quantity < 50 THEN 'Düşük' ELSE 'Yeterli' END
ORDER BY c.category_name, price_segment;

💡 İpucu: CASE WHEN ifadesini hem SELECT hem GROUP BY'da tekrarlamak zorunda kalmak rahatsız edici. Subquery veya CTE (B12'de göreceğiz) ile bu tekrarı önleyebilirsin.


Çok Boyutlu Gruplama ve HAVING

-- Aylık 2'den fazla sipariş veren şehirler
SELECT c.city,
       DATE_FORMAT(o.order_date, '%Y-%m') AS month,
       COUNT(*) AS order_count
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
GROUP BY c.city, DATE_FORMAT(o.order_date, '%Y-%m')
HAVING COUNT(*) > 2;

Gruplama Düzeyini Seçme

-- Düzey 1: Genel toplam (GROUP BY yok)
SELECT SUM(total_amount) AS grand_total FROM orders;

-- Düzey 2: Yıl bazında
SELECT YEAR(order_date), SUM(total_amount) FROM orders GROUP BY YEAR(order_date);

-- Düzey 3: Yıl + Ay bazında
SELECT YEAR(order_date), MONTH(order_date), SUM(total_amount) 
FROM orders GROUP BY YEAR(order_date), MONTH(order_date);

-- Düzey 4: Yıl + Ay + Müşteri bazında
SELECT YEAR(order_date), MONTH(order_date), customer_id, SUM(total_amount)
FROM orders GROUP BY YEAR(order_date), MONTH(order_date), customer_id;

Her seviye daha detaylı gruplama yapar — ama gereğinden fazla detay raporu okunamaz hale getirir.


Gerçek Dünya Örneği — Cross-Tab Rapor

-- Şehir × Ay satış matrisi (cross-tab)
SELECT 
    c.city,
    ROUND(SUM(CASE WHEN MONTH(o.order_date) = 1 THEN o.total_amount ELSE 0 END), 0) AS Ocak,
    ROUND(SUM(CASE WHEN MONTH(o.order_date) = 2 THEN o.total_amount ELSE 0 END), 0) AS Subat,
    ROUND(SUM(CASE WHEN MONTH(o.order_date) = 3 THEN o.total_amount ELSE 0 END), 0) AS Mart,
    ROUND(SUM(o.total_amount), 0) AS Toplam
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
WHERE YEAR(o.order_date) = 2024
GROUP BY c.city
ORDER BY Toplam DESC;

Bu sorgu, CASE WHEN + SUM + GROUP BY kombinasyonuyla Excel pivot tablo benzeri çıktı üretir.


Sıkça Yapılan Hatalar

  1. GROUP BY'da sütun eksikliği: SELECT'teki tüm non-aggregate sütunlar GROUP BY'da olmalı.

  2. Gereksiz detaylı gruplama: 5-6 sütuna göre GROUP BY yapmak okunması zor sonuçlar üretir. Raporun amacını düşün.

  3. CASE ifadesini GROUP BY'da tekrarlama: Alias GROUP BY'da (MySQL'de) kullanılabilir ama standart değil. Subquery daha güvenli.


Özet

  • Birden fazla sütunla GROUP BY, her sütun kombinasyonu için ayrı grup oluşturur

  • Daha fazla sütun = daha detaylı gruplama = daha fazla satır

  • CASE WHEN + SUM + GROUP BY = cross-tab/pivot tablo benzeri raporlar

  • Gruplama düzeyini raporun amacına göre seç — ne çok genel ne çok detaylı

  • Tüm non-aggregate sütunlar GROUP BY'da olmalı