Many cubes flowing through a funnel into one block with a bar chart, representing SQL Server aggregate functions

SQL Server Aggregate Fonksiyonları: COUNT, SUM, AVG, STRING_AGG ve NULL Tuzakları

SQL Server aggregate fonksiyonları: COUNT(*), COUNT(1) ve COUNT(kolon) farkı, SUM ve AVG tuzakları, NULL davranışı, GROUP BY ve HAVING, koşullu toplama, ROLLUP, STRING_AGG ve window aggregate’ler.

Nisan 10, 2023

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.

SQL Server Danışmanlığı

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.

Destek Alın

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.
Hayır. İkisi de NULL değer içerenler dahil tüm satırları sayar ve SQL Server ikisi için aynı planı üretir. COUNT(kolon) ise farklıdır: yalnızca o kolonun NULL olmadığı satırları sayar.
Evet, satırları sayan COUNT(*) dışındaki tüm aggregate’ler NULL değerleri yok sayar. SUM, AVG, MIN ve MAX, NULL olmayan değer yoksa NULL döndürür.
Çünkü sonuç tipi girdi tipini izler. int bir kolon üzerindeki AVG int döndürür. Ondalıklı sonuç için kolonu AVG içinde decimal ya da float’a dönüştürün.
WHERE tekil satırları gruplanmadan önce, HAVING ise grupları aggregate’ler hesaplandıktan sonra filtreler. Satır koşullarını WHERE’e, aggregate sonuçları üzerindeki koşulları HAVING’e yazın.
COUNT(*) çalıştırmak yerine tablonun heap ya da clustered index’i için sys.dm_db_partition_stats’tan satır sayısını okuyun. Transaction açısından kesin olması gereken sayımlarda en hızlı seçenek, dar bir nonclustered ya da columnstore index üzerinde COUNT(*)’tır.

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.

4.7/5