PIVOT operatörü
Koşullu toplama ile pivot yapmayı “İleri Sorgu Yapıları” bölümünde gördük. SQL Server ve Oracle bunun için ayrı bir PIVOT operatörü sunar. Yapı: kaynak sorgu → toplanacak değer → sütuna dönüşecek değerler.
SELECT *
FROM (
SELECT channel, YEAR(order_date) AS yil, order_id
FROM orders
) AS kaynak
PIVOT (
COUNT(order_id) FOR yil IN ([2022], [2023], [2024])
) AS p
ORDER BY channel;
Sütun değerleri sabit yazılır. Değer listesi değişkense (ör. her yıl yeni sütun) dinamik SQL ile sorgu metni üretilir; bunu ilerideki derste göreceğiz.
UNPIVOT: sütunları satıra çevirmek
WITH genis AS (
SELECT channel,
SUM(CASE WHEN status = 'delivered' THEN 1 ELSE 0 END) AS teslim,
SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS iptal
FROM orders
GROUP BY channel
)
SELECT channel, durum, adet
FROM genis
UNPIVOT (adet FOR durum IN (teslim, iptal)) AS u
ORDER BY channel, durum;
Derinleş: dinamik PIVOT ve NULL hücreler
PIVOT'un en büyük kısıtı, sütun listesinin sorgu yazılırken sabit olmasıdır. Lezzet Kurye'ye yarın yeni bir ödeme yöntemi eklenirse IN ([cüzdan], [kart], [nakit]) listesi onu göstermez. Çözüm, sütun listesini veriden üretip sorgu metnini dinamik olarak kurmaktır:
DECLARE @cols NVARCHAR(MAX), @sql NVARCHAR(MAX);
SELECT @cols = STRING_AGG(QUOTENAME(payment), ', ') WITHIN GROUP (ORDER BY payment)
FROM (SELECT DISTINCT payment FROM lk_orders) AS t;
SET @sql = N'SELECT status, ' + @cols + N'
FROM (SELECT status, payment, order_id FROM lk_orders) AS k
PIVOT (COUNT(order_id) FOR payment IN (' + @cols + N')) AS p
ORDER BY status;';
PRINT @sql;
EXEC sp_executesql @sql;
QUOTENAMEher değeri köşeli paranteze alır; değerde boşluk, Türkçe karakter ya da]olsa bile sorgu bozulmaz ve enjeksiyon riski azalır.WITHIN GROUP (ORDER BY ...)sütun sırasını sabitler; yoksa STRING_AGG'in sırası garanti değildir.PRINT @sqlüretilen metni görmenin en kolay yoludur; hata ayıklarken önce metni oku.- STRING_AGG SQL Server 2017+; daha eski sürümlerde
FOR XML PATH('')+STUFFkalıbı kullanılır.
NULL hücreler
PIVOT içinde COUNT eşleşme yoksa 0 verir, ama SUM, AVG, MAX NULL verir. Raporlarda boş hücre “veri yok” mu, “sıfır” mı karışmasın diye dış SELECT'te ISNULL uygulanır (PIVOT'un içinde uygulanamaz):
SELECT cuisine,
ISNULL([İstanbul], 0) AS [İstanbul],
ISNULL([Ankara], 0) AS Ankara,
ISNULL([İzmir], 0) AS [İzmir]
FROM (SELECT r.cuisine, r.city, o.tip
FROM lk_orders o JOIN lk_restaurants r ON r.restaurant_id = o.restaurant_id) AS k
PIVOT (SUM(tip) FOR city IN ([İstanbul], [Ankara], [İzmir])) AS p
ORDER BY cuisine;
| İhtiyaç | PIVOT | Koşullu toplama (CASE) |
|---|---|---|
| Tek ölçü, sabit sütunlar | Uygun | Uygun |
| Birden çok ölçü (adet + tutar) | Tek PIVOT'ta olmaz | Kolay |
| Farklı koşullar (tarih aralığı gibi) | Zor | Kolay |
| Taşınabilirlik | SQL Server, Oracle | Her SQL türü |
Kontrol listesi: Kaynak sorguda yalnızca satır anahtarı, pivot sütunu ve ölçü var mı? Sütun listesi sıralı ve QUOTENAME'li mi? Toplam fonksiyonu NULL üretebiliyorsa dışarıda ISNULL var mı? Sonucun satır toplamı, pivotsuz bir GROUP BY toplamıyla aynı mı?
o.order_id eklersen her sipariş ayrı bir gruplama anahtarı olur ve sonuç 8 satır yerine binlerce satıra çıkar.