İÇİNDEKİLER
- •Raporlarınız yavaş mı ya da yanlış mı topluyor?
- •SQL Server’daki Aggregate Fonksiyonları
- •COUNT(*), COUNT(1) ve COUNT(kolon)
- •SUM ve AVG: Tam Sayı ve Taşma Tuzakları
- •Tam sayıların AVG’si tam sayıdır
- •SUM taşabilir
- •NULL sonuçlar
- •GROUP BY, WHERE ve HAVING
- •Koşullu Toplama
- •ROLLUP, CUBE ve GROUPING SETS ile Ara Toplamlar
- •STRING_AGG: Tek Kolonda Liste
- •Window Fonksiyonu Olarak Aggregate’ler
- •Büyük Tablolarda Aggregate’ler
- •Sık Yapılan Aggregate Hataları
- •Raporlarınızı hem hızlı hem doğru hale getirin
Aggregate (toplama) fonksiyonları çok sayıda satırı tek bir değere dönüştürür: bir sayı, bir toplam, bir ortalama, en küçük ya da en büyük değer, bir liste. Her raporun ve panonun temelidirler ve basit görünürler. Yol açtıkları hataların çoğu ise birkaç ayrıntıdan çıkar: NULL’ın nasıl ele alındığı, tam sayı aritmetiği, taşma ve COUNT(*) ile COUNT(kolon) arasındaki fark.
Bu rehberde SQL Server 2025’teki aggregate fonksiyonlarını, COUNT’un gerçekte nasıl çalıştığını, SUM ve AVG’deki tuzakları, GROUP BY ve HAVING’i, koşullu toplamayı, grouping set’leri, STRING_AGG’yi, büyük tablolar için yaklaşık aggregate’leri ve window fonksiyonu olarak aggregate’leri ele alıyoruz.
Raporlarınız yavaş mı ya da yanlış mı topluyor?
Aryasoft, raporlama sorgularınızı doğruluk ve performans açısından inceler; columnstore, indexed view ve sorgu yeniden yazımıyla süreleri kısaltır.
SQL Server’daki Aggregate Fonksiyonları
| Fonksiyon | Döndürdüğü | Notlar |
|---|---|---|
| COUNT(*) / COUNT(ifade) | Satır sayısı / NULL olmayan değer sayısı | int döndürür; çok büyük kümeler için COUNT_BIG’e bakın |
| COUNT_BIG | COUNT ile aynı, bigint olarak | GROUP BY içeren indexed view’larda zorunlu |
| SUM | Sayısal değerlerin toplamı | Sonuç tipi girdiyi izler; int taşabilir |
| AVG | Sayısal değerlerin ortalaması | Tam sayı girdi, tam sayı sonuç verir |
| MIN / MAX | En küçük / en büyük değer | Sayı, metin ve tarihlerde çalışır |
| STRING_AGG | Ayraçla birleştirilmiş değerler | SQL Server 2017+; sıralama WITHIN GROUP ile |
| STDEV, STDEVP, VAR, VARP | Standart sapma ve varyans | Örneklem ve popülasyon versiyonları |
| APPROX_COUNT_DISTINCT | Yaklaşık benzersiz değer sayısı | SQL Server 2019+; çok büyük tablolarda hızlı |
| APPROX_PERCENTILE_CONT / _DISC | Yaklaşık yüzdelikler | SQL Server 2022+ |
| PRODUCT | Değerlerin çarpımı | SQL Server 2025 ile geldi |
| JSON_OBJECTAGG / JSON_ARRAYAGG | Satırlardan üretilen JSON nesne veya dizi | SQL Server 2025 ile geldi |
| GROUPING / GROUPING_ID | Bir satırın ara toplam olup olmadığı | ROLLUP, CUBE ve GROUPING SETS ile kullanılır |
Hepsi için iki kural geçerlidir. Aggregate’ler NULL değerleri yok sayar (satırları sayan COUNT(*) hariç). GROUP BY içeren bir sorguda da SELECT listesindeki her kolon ya GROUP BY’da ya da bir aggregate’in içinde olmalıdır.
COUNT(*), COUNT(1) ve COUNT(kolon)
Bu üçü sıklıkla aynı şey sanılır. İkisi aynıdır, üçüncüsü değil:
CREATE TABLE #Office (id int, name varchar(50));
INSERT INTO #Office VALUES (1, 'Istanbul'), (2, 'Ankara'), (3, NULL), (4, 'Ankara');
SELECT
COUNT(*) AS all_rows, -- 4
COUNT(1) AS all_rows_too, -- 4
COUNT(name) AS non_null_names, -- 3
COUNT(DISTINCT name) AS distinct_names -- 2
FROM #Office;
- COUNT(*), tüm kolonları NULL olan satırlar dahil satırları sayar.
- COUNT(1) tamamen aynı sonucu döndürür ve tamamen aynı planı alır. Buradaki “1” bir kolon numarası değil, asla NULL olmayan bir sabittir. Performans farkı yoktur.
- COUNT(kolon) yalnızca o kolonun NULL olmadığı satırları sayar. Bir stil tercihi olarak değil, bilinçli olarak kullanın.
- COUNT(DISTINCT kolon) NULL olmayan benzersiz değerleri sayar; bir sort ya da hash gerektirir ve büyük tablolarda çok daha pahalıdır.
COUNT int döndürdüğü için 2.147.483.647 satırın üzerinde aritmetik taşma hatası verir. Bu büyüklüğe ulaşabilecek tablolarda COUNT_BIG kullanın.
İpucu
Büyük bir tablonun satır sayısını taramadan almak için COUNT(*) yerine metadata’yı okuyun: SELECT SUM(row_count) FROM sys.dm_db_partition_stats WHERE object_id = OBJECT_ID(‘dbo.Orders’) AND index_id IN (0, 1). Anında döner ve devam eden transaction’lar dışında doğrudur.
SUM ve AVG: Tam Sayı ve Taşma Tuzakları
Tam sayıların AVG’si tam sayıdır
AVG’nin sonuç tipi girdiyi izler. 1, 2 ve 2’nin ortalaması 1,67’dir; ama int bir kolonda SQL Server 1 döndürür:
SELECT AVG(Quantity) AS int_avg, -- 1
AVG(CAST(Quantity AS decimal(10,2))) AS decimal_avg -- 1.67
FROM (VALUES (1), (2), (2)) AS t(Quantity);
SUM taşabilir
int bir kolon üzerindeki SUM int döndürür. Toplam yaklaşık 2,1 milyarı geçtiğinde sorgu “Arithmetic overflow error converting expression to data type int” (8115) hatasıyla başarısız olur. Toplamadan sonra değil, önce dönüştürün:
SELECT SUM(CAST(LineAmount AS bigint)) AS total
FROM dbo.SalesLines;
NULL sonuçlar
SUM, AVG, MIN ve MAX, hiç satır yoksa ya da tüm değerler NULL ise 0 değil NULL döndürür. Raporun sıfıra ihtiyacı varsa kolonu değil aggregate’i sarın: ISNULL(SUM(Amount), 0). Kolonu içeriden sarmak (SUM(ISNULL(Amount, 0))) AVG sonuçlarını değiştirir; çünkü yok sayılan NULL’lar artık sıfır olarak sayılır.
Not
Bir aggregate NULL değerleri atladığında SQL Server “Null value is eliminated by an aggregate or other SET operation.” uyarısını döndürür. Bu bir hata değildir, ama bazı istemci kütüphaneleri ve SSIS paketleri onu hata gibi ele alır. Ayrıca kolonda beklemediğiniz NULL’lar olduğunu da söyler.
GROUP BY, WHERE ve HAVING
WHERE satırları gruplamadan önce, HAVING ise grupları aggregate’ler hesaplandıktan sonra filtreler. Mümkün olduğunda WHERE’de filtreleyin: gruplamaya daha az satır girer ve filtre kolonlarındaki index’ler kullanılabilir.
SELECT CustomerID,
COUNT(*) AS order_count,
SUM(Amount) AS total_amount
FROM dbo.Orders
WHERE OrderDate >= '20260101' -- satır filtresi
GROUP BY CustomerID
HAVING SUM(Amount) > 100000 -- grup filtresi
ORDER BY total_amount DESC;
Koşullu Toplama
Bir aggregate içindeki CASE yalnızca eşleşen satırları sayar ya da toplar; bu, birkaç sorguyu tablo üzerinde tek bir geçişe çevirir ve pivot yapmanın en basit yoludur:
SELECT
CustomerID,
COUNT(*) AS all_orders,
COUNT(CASE WHEN Status = 'Cancelled' THEN 1 END) AS cancelled_orders,
SUM(CASE WHEN YEAR(OrderDate) = 2025 THEN Amount END) AS amount_2025,
SUM(CASE WHEN YEAR(OrderDate) = 2026 THEN Amount END) AS amount_2026
FROM dbo.Orders
GROUP BY CustomerID;
CASE’te bilerek ELSE yok: eşleşmeyen satırlar NULL olur, COUNT ve SUM da onları yok sayar.
ROLLUP, CUBE ve GROUPING SETS ile Ara Toplamlar
SELECT
Region,
Product,
SUM(Amount) AS total,
GROUPING(Region) AS is_grand_total,
GROUPING(Product) AS is_region_total
FROM dbo.Sales
GROUP BY ROLLUP (Region, Product)
ORDER BY Region, Product;
ROLLUP her bölge için bir ara toplam ve bir genel toplam ekler; CUBE tüm kombinasyonları ekler; GROUPING SETS ise tam olarak ihtiyaç duyduğunuz gruplamaları listelemenizi sağlar. GROUPING, ara toplam satırlarını değeri tesadüfen NULL olan detay satırlarından ayırır.
STRING_AGG: Tek Kolonda Liste
SELECT
o.CustomerID,
STRING_AGG(CONVERT(nvarchar(max), p.ProductName), N', ')
WITHIN GROUP (ORDER BY p.ProductName) AS products
FROM dbo.Orders AS o
JOIN dbo.Products AS p ON p.ProductID = o.ProductID
GROUP BY o.CustomerID;
Uyarı
STRING_AGG’nin sonucu girdisiyle aynı tiptedir. nvarchar(n) ya da varchar(n) girdide sonuç 8.000 baytla sınırlıdır ve daha uzun listeler 9829 hatasıyla başarısız olur. Liste uzayabiliyorsa yukarıdaki gibi girdiyi nvarchar(max)’a dönüştürün.
Window Fonksiyonu Olarak Aggregate’ler
OVER() eklemek aggregate’i satırları daraltmadan hesaplar; böylece detay ve toplamlar yan yana görünür:
SELECT
OrderID,
CustomerID,
Amount,
SUM(Amount) OVER (PARTITION BY CustomerID) AS customer_total,
100.0 * Amount / SUM(Amount) OVER (PARTITION BY CustomerID) AS pct_of_customer,
SUM(Amount) OVER (PARTITION BY CustomerID ORDER BY OrderDate, OrderID
ROWS UNBOUNDED PRECEDING) AS running_total
FROM dbo.Orders;
Bu tür kümülatif toplamlar, cursor kullanmanın en yaygın nedenlerinden birinin yerini alır; cursor’lar ve set tabanlı alternatifler rehberimize bakabilirsiniz.
Büyük Tablolarda Aggregate’ler
- Columnstore index’ler toplama için tasarlanmıştır: sıkıştırılmış kolon segmentleri, batch mode işleme ve Enterprise sürümde aggregate pushdown. Yüz milyonlarca satır üzerindeki raporlar çoğu zaman dakikalardan saniyelere iner.
- Indexed view’lar önceden toplanmış sonuçları saklar ve otomatik güncel tutulur. View GROUP BY içeriyorsa SCHEMABINDING ve COUNT_BIG(*) gerektirir ve temel tablolardaki her yazmaya maliyet ekler.
- Yaklaşık fonksiyonlar, örneğin APPROX_COUNT_DISTINCT, küçük ve sınırlı bir hata payıyla COUNT(DISTINCT)’ten çok daha az bellek kullanır. Kesin değerin gerekmediği panolarda kullanın.
- Rowstore tablolarda gruplama kolonlarını index’leyin: key’i GROUP BY ile eşleşen bir index, SQL Server’ın sıralama yapmadan stream aggregate kullanmasını sağlar.
Sık Yapılan Aggregate Hataları
- Satır saymak isterken COUNT(kolon) kullanmak ve NULL içeren satırları sessizce kaçırmak.
- Tam sayı kolonlarda AVG kullanıp kesilmiş ortalamalar almak.
- Büyüyen tablolarda int kolonlar üzerinde SUM; toplam 2,1 milyarı geçene kadar çalışır.
- Aggregate’leri WHERE’de (izin verilmez) ya da satır koşullarını HAVING’de (daha yavaş) filtrelemek.
- AVG içinde ISNULL kullanıp bilinmeyen değerleri sıfıra çevirmek ve ortalamayı değiştirmek.
- Yalnızca satır sayısı göstermek için dev bir tabloda COUNT(*) çalıştırmak.
- Max olmayan metinlerde STRING_AGG kullanmak; liste uzadığında birden hata vermesi.
İlgili Yazılar
SQL Server Danışmanlığı
Raporlarınızı hem hızlı hem doğru hale getirin
Aryasoft’un kıdemli DBA ekibi raporlama sorgularını doğruluk ve performans açısından inceler; columnstore, indexed view, daha iyi index’ler ve sorgu yeniden yazımıyla hızlandırır; böylece rakamlar doğru ve zamanında gelir.