RAPORLAMA İÇİN TARİH BOYUTU MANTIĞI: SQL’DE DÖNEM VE KARŞILAŞTIRMA
Raporlama katmanında “tarih” çoğu zaman bir sütun değildir; doğru kurgulanmış bir analiz eksenidir. Ay kapanışı, çeyrek hedefleri, YTD/MTD hesapları, “geçen yılın aynı dönemi” kıyasları… Hepsi görünürde basit, arka planda ise tutarlı bir zaman modeline ihtiyaç duyar. Bu tutarlılığı sağlayan en temel yapı taşlarından biri tarih boyutudur.
Operasyonel tablolardan doğrudan tarih alanı çekip rapor yapmak, ilk etapta hızlı görünür; fakat birkaç sprint sonra “hangi ay?”, “hangi iş günü?”, “hangi mali dönem?” gibi sorular birikmeye başlar. Üstelik farklı ekipler aynı kavramı farklı tanımladığında, raporlar arasında güven kaybı oluşur. Bu yazıda SQL’de tarih boyutu mantığını, dönem tanımlama yaklaşımlarını ve karşılaştırma senaryolarını pratik örneklerle ele alacağız.
Amacımız; hem geliştirici ekiplerin hem de karar vericilerin ortak bir “zaman sözlüğü” üzerinden konuşmasını sağlamak. Yazının sonunda, kendi veri ambarınızda veya raporlama veritabanınızda kullanabileceğiniz bir tarih boyutu taslağınız ve gerçekçi kıyas sorgularınız olacak.

Tarih boyutu nedir ve neden raporlamada kritik?
Tarih boyutu (date dimension), her bir takvim gününü temsil eden satırlardan oluşan ve o güne ait çok sayıda türetilmiş niteliği taşıyan bir boyut tablosudur. “2026-02-06” gibi tek bir günü; hafta numarası, ay adı, çeyrek, yıl, hafta içi/sonu, resmi tatil, mali yıl, mali dönem, çalışma günü gibi onlarca alanla birlikte sunar.
Bu yaklaşımın en büyük faydası, hesaplamaların ve dönem tanımlarının tek bir yerde standardize edilmesidir. Böylece BI araçlarında veya uygulama kodunda tekrar tekrar tarih mantığı yazmak yerine, SQL join ile tüm raporlar aynı kurala bağlanır. Ayrıca performans tarafında da avantaj sağlar: tarih boyutu küçük ve indekslenebilir bir tablo olduğu için büyük fact tablolarıyla birleşimlerde öngörülebilir sonuçlar üretir.
Tekil kaynak: “Zaman sözlüğü” ve ortak dil
Kurumsal ortamlarda asıl problem, tarih hesaplarının karmaşık olması değil; aynı kavramın farklı ekiplerce farklı yorumlanmasıdır. Örneğin “bu ay” bazı raporlarda takvim ayı iken bazı raporlarda “son 30 gün” olabilir. Tarih boyutu, bu kavramları isimlendirilmiş alanlara dönüştürerek ortak dil sağlar.
Zaman zekâsını SQL tarafında güvenilir hale getirmek
Birçok BI aracı kendi zaman zekâsı fonksiyonlarını sunsa da, kaynak veride mali dönem veya iş günü gibi kurumsal kurallar yoksa sonuçlar hatalı olabilir. Boyut üzerinde bu kuralları saklamak, “doğruyu” veritabanı katmanında garanti eder. Özellikle denetim, finans ve KPI raporlamasında bu kritik bir konudur.
Boyut şeması tasarımı: anahtarlar, alanlar ve temel kararlar
Tarih boyutu tasarımında iki yaygın anahtar yaklaşımı vardır: doğal anahtar (date) ve surrogate key (tamsayı). Veri ambarı pratiklerinde çoğunlukla surrogate key tercih edilir; çünkü fact tablolarında daha küçük yer kaplar, join performansını artırır ve tarih formatı değişikliklerinden etkilenmez.
Surrogate key (DateKey) önerisi
Yaygın bir desen; DateKey alanını YYYYMMDD formatında tamsayı olarak tutmaktır (ör. 20260206). Bu değer hem okunabilir hem de sıralama/filtre için kullanışlıdır. Aynı zamanda gerçek tarih alanını (Date) da saklayıp gerektiğinde formatlı gösterim için kullanabilirsiniz.
Minimum alan seti: raporlamanın omurgası
Her kurumun ihtiyacı farklı olsa da, aşağıdaki alanlar çoğu raporlama senaryosunda çekirdek sayılır:
- DateKey (YYYYMMDD tamsayı) ve Date (DATE)
- Year, MonthNumber, MonthName, Quarter
- WeekOfYear, DayOfWeekNumber, DayOfWeekName
- IsWeekend, IsHoliday, IsWorkday
- FiscalYear, FiscalPeriod, FiscalQuarter
- MonthStartDate, MonthEndDate, QuarterStartDate, QuarterEndDate
Bu alanları oluşturmak için veritabanı fonksiyonlarını kullanabilirsiniz; ancak iş günü ve resmi tatil gibi kurumsal kurallar için ayrı bir tatil takvimi tablosu veya yönetilebilir bir iş kuralı mekanizması eklemek gerekir.
SQL ile tarih boyutu oluşturma ve doldurma
Tarih boyutu genellikle 10–20 yıllık bir aralığı kapsar ve ETL/ELT sürecinde bir kez oluşturulup düzenli olarak ileriye dönük uzatılır. Aşağıdaki örnek, basit bir yaklaşımı gösterir. Kullanacağınız SQL lehçesine göre fonksiyon isimleri değişebilir; ancak mantık aynıdır.
Örnek tablo şeması
-- Örnek: SQL Server benzeri söz dizimi
CREATE TABLE dbo.DimDate (
DateKey INT NOT NULL PRIMARY KEY, -- YYYYMMDD
[Date] DATE NOT NULL,
[Year] SMALLINT NOT NULL,
MonthNumber TINYINT NOT NULL,
MonthName NVARCHAR(20) NOT NULL,
[Quarter] TINYINT NOT NULL,
WeekOfYear TINYINT NOT NULL,
DayOfWeekNumber TINYINT NOT NULL, -- 1-7
DayOfWeekName NVARCHAR(20) NOT NULL,
IsWeekend BIT NOT NULL,
IsHoliday BIT NOT NULL DEFAULT 0,
IsWorkday BIT NOT NULL,
MonthStartDate DATE NOT NULL,
MonthEndDate DATE NOT NULL,
FiscalYear SMALLINT NOT NULL,
FiscalPeriod TINYINT NOT NULL,
FiscalQuarter TINYINT NOT NULL
);
Takvim günlerini üretmek ve alanları hesaplamak
Boyutu doldurmanın birçok yolu vardır: sayı tablosu (tally table), rekürsif CTE, takvim tablosu üretimi, ETL aracı vb. Aşağıdaki örnek, sayı tablosu yaklaşımına benzer bir CTE mantığıyla gün üretip alanları türetir. Mali yılın Nisan’da başladığını varsayan basit bir fiscal kuralı da ekliyoruz.
-- Örnek: 2020-01-01 ile 2030-12-31 arası gün üretimi
DECLARE @StartDate DATE = '2020-01-01';
DECLARE @EndDate DATE = '2030-12-31';
;WITH N AS (
SELECT 0 AS n
UNION ALL
SELECT n + 1 FROM N WHERE DATEADD(DAY, n + 1, @StartDate) <= @EndDate
),
D AS (
SELECT CAST(DATEADD(DAY, n, @StartDate) AS DATE) AS [Date]
FROM N
)
INSERT INTO dbo.DimDate (
DateKey, [Date], [Year], MonthNumber, MonthName, [Quarter], WeekOfYear,
DayOfWeekNumber, DayOfWeekName, IsWeekend, IsHoliday, IsWorkday,
MonthStartDate, MonthEndDate, FiscalYear, FiscalPeriod, FiscalQuarter
)
SELECT
CONVERT(INT, FORMAT([Date], 'yyyyMMdd')) AS DateKey,
[Date],
YEAR([Date]) AS [Year],
MONTH([Date]) AS MonthNumber,
DATENAME(MONTH, [Date]) AS MonthName,
DATEPART(QUARTER, [Date]) AS [Quarter],
DATEPART(WEEK, [Date]) AS WeekOfYear,
DATEPART(WEEKDAY, [Date]) AS DayOfWeekNumber,
DATENAME(WEEKDAY, [Date]) AS DayOfWeekName,
CASE WHEN DATENAME(WEEKDAY, [Date]) IN ('Saturday','Sunday') THEN 1 ELSE 0 END AS IsWeekend,
0 AS IsHoliday,
CASE
WHEN DATENAME(WEEKDAY, [Date]) IN ('Saturday','Sunday') THEN 0
ELSE 1
END AS IsWorkday,
DATEFROMPARTS(YEAR([Date]), MONTH([Date]), 1) AS MonthStartDate,
EOMONTH([Date]) AS MonthEndDate,
CASE WHEN MONTH([Date]) >= 4 THEN YEAR([Date]) ELSE YEAR([Date]) - 1 END AS FiscalYear,
CASE WHEN MONTH([Date]) >= 4 THEN MONTH([Date]) - 3 ELSE MONTH([Date]) + 9 END AS FiscalPeriod,
CASE
WHEN MONTH([Date]) IN (4,5,6) THEN 1
WHEN MONTH([Date]) IN (7,8,9) THEN 2
WHEN MONTH([Date]) IN (10,11,12) THEN 3
ELSE 4
END AS FiscalQuarter
FROM D
OPTION (MAXRECURSION 0);
Not: Resmi tatiller için ayrı bir “HolidayCalendar” tablosu tutup DimDate’te IsHoliday alanını güncellemek daha yönetilebilir bir yaklaşımdır. Böylece tatil eklemek için kod deploy etmek yerine veri güncellemesi yeterli olur. Kurumsal sürdürülebilirlik açısından bu fark önemlidir.

Dönem mantığı: takvim ayı, mali dönem, iş günü ve kapanış
“Dönem” kelimesi tek başına yeterince açık değildir. Kurumlarda en az üç farklı dönem tanımı sık görülür: takvim ayı (calendar month), mali dönem (fiscal period) ve operasyonel kapanış dönemi (closing). Tarih boyutu, bu dönemlerin hepsini aynı gün düzeyinde işaretleyerek raporlarda tutarlılığı korur.
Takvim ayı ve çeyrek: standart ve beklendik
Takvim ayı genellikle “ayın 1’i ile son günü” arasıdır ve MonthStartDate/MonthEndDate alanlarıyla kolayca yönetilir. Çeyrek hesapları (QuarterStartDate/QuarterEndDate gibi) da benzer şekilde türetilebilir. Bu alanları boyutta saklamak, her sorguda yeniden hesaplama ihtiyacını azaltır.
Mali dönem: kurum kuralı, teknik alan
Özellikle finansal raporlama yapan yapılarda mali yıl, takvim yılıyla aynı olmayabilir. Örneğin mali yıl Nisan’da başlayabilir veya 4-4-5 gibi perakende odaklı dönemlemeler kullanılabilir. Bu nedenle DimDate’te FiscalYear/FiscalPeriod/FiscalQuarter alanlarını taşımak, raporlama katmanında “mali dönem” filtresini tek hamlede mümkün kılar.
İş günü takvimi: SLA, operasyon ve kapasite
Bir KPI “iş gününde ortalama çözüm süresi” üzerinden takip ediliyorsa, hafta sonu ve resmi tatillerin ayıklanması gerekir. IsWorkday alanı bunun için vardır. Daha ileri senaryolarda “WorkdayNumberInMonth” gibi alanlar da eklenir; böylece “ayın 3. iş günü” gibi tanımlar yapılabilir. Bu yaklaşım, SLA raporları ve çağrı merkezi/operasyon ekiplerinde ciddi değer üretir.
Karşılaştırma kurgusu: MoM, YoY, YTD ve “aynı gün” eşlemesi
Karşılaştırma raporları, yöneticilerin en sık istediği çıktılardandır: “Bu ay geçen aya göre nasıl?”, “Geçen yılın aynı dönemine göre büyüme nedir?”, “Yılbaşından bugüne trend ne durumda?”. Bu soruların doğru yanıtı, doğru eşleştirmeye bağlıdır.
Basit YoY: aynı ayın toplamı
En temel yaklaşım, ilgili ayı seçip önceki yılın aynı ayını Fiscal/Calendar kuralına göre filtrelemektir. Boyut alanları sayesinde “aynı ay” tanımı nettir: Year ve MonthNumber üzerinden gideriz (veya fiscal için FiscalYear/FiscalPeriod).
“Aynı gün” problemi: 29 Şubat ve hafta kaymaları
Gün bazlı kıyaslarda “aynı gün” her zaman aynı tarihe denk gelmez. Artık yıllar (29 Şubat) veya “aynı iş günü” kıyası gibi ihtiyaçlar doğar. Bu noktada boyuta eklenebilecek alanlar devreye girer: ComparableDateKey (eşlenmiş gün), SameWeekdayLastYearDateKey gibi önceden hesaplanmış eşleştirme alanları, raporları çok daha güvenilir yapar.
Örnek: Ay bazlı gelir için MoM ve YoY tek sorguda
Aşağıdaki sorgu, bir gelir fact tablosunu DimDate üzerinden ay bazında özetler; hem geçen ayı hem de geçen yılın aynı ayını yanına getirir. Bu tür sorgularda dikkat edilmesi gereken nokta, filtrelemenin boyut üzerinden yapılması ve fact tablosunun tarih anahtarıyla join edilmesidir.
-- Varsayım: FactSales(DateKey, Amount) ve DimDate(DateKey, Year, MonthNumber, MonthStartDate, MonthEndDate)
WITH Monthly AS (
SELECT
d.[Year],
d.MonthNumber,
MIN(d.MonthStartDate) AS MonthStartDate,
MAX(d.MonthEndDate) AS MonthEndDate,
SUM(s.Amount) AS Revenue
FROM dbo.FactSales s
JOIN dbo.DimDate d ON d.DateKey = s.DateKey
WHERE d.[Date] >= '2025-01-01'
GROUP BY d.[Year], d.MonthNumber
),
Shifted AS (
SELECT
m.*,
LAG(m.Revenue, 1) OVER (ORDER BY m.[Year], m.MonthNumber) AS RevenuePrevMonth,
LAG(m.Revenue, 12) OVER (ORDER BY m.[Year], m.MonthNumber) AS RevenuePrevYearSameMonth
FROM Monthly m
)
SELECT
[Year],
MonthNumber,
Revenue,
RevenuePrevMonth,
RevenuePrevYearSameMonth,
CASE WHEN RevenuePrevMonth IS NULL OR RevenuePrevMonth = 0 THEN NULL
ELSE (Revenue - RevenuePrevMonth) * 1.0 / RevenuePrevMonth END AS MoMChangeRate,
CASE WHEN RevenuePrevYearSameMonth IS NULL OR RevenuePrevYearSameMonth = 0 THEN NULL
ELSE (Revenue - RevenuePrevYearSameMonth) * 1.0 / RevenuePrevYearSameMonth END AS YoYChangeRate
FROM Shifted
ORDER BY [Year], MonthNumber;
Bu örnek “takvim ayı” üzerinden çalışır. Eğer mali dönem kullanıyorsanız, aynı yaklaşımı FiscalYear/FiscalPeriod ile kurabilirsiniz. Asıl amaç; karşılaştırmayı SQL’de “kestirme tarih hesapları” ile değil, boyut alanlarıyla deterministik hale getirmektir.

Performans ve sürdürülebilirlik: indeks, partition ve veri kalitesi
Tarih boyutu küçük olduğu için genellikle performans darboğazı yaratmaz; fakat boyutun doğru anahtarlarla fact tablolara bağlanması kritiktir. Fact tablosunda tarih alanını DATE yerine DateKey gibi tamsayı ile tutmak, join ve partition stratejilerini kolaylaştırır. Aynı zamanda “hangi gün?” sorusunun tek bir anahtarla cevaplanmasını sağlar.
İndeksleme stratejisi: küçük tablo, büyük etki
DimDate’te DateKey zaten PK olduğu için indeksli gelir. Fact tablolarında DateKey üzerinde indeks veya partition kullanımı (özellikle aylık/yıllık partition) rapor sorgularını hızlandırır. Ayrıca raporların çoğu tarih aralığı ile filtrelendiğinden, DateKey tabanlı aralık taramaları daha verimli olur.
Veri kalitesi: eksik gün, hatalı join ve “unknown date”
Boyutta kapsanmayan bir tarih fact’e düşerse join sonucu NULL olur ve raporlar eksik görünür. Bu nedenle boyutun ileriye dönük yeterli aralığı kapsaması gerekir. Ek olarak, bazı ambarlarda “Unknown” satırı (ör. DateKey = 0) ile eksik tarihlerin raporlarda izlenmesi sağlanır. Bu, veri kalitesi takibi için pratik bir yöntemdir.
Uygulamada sık kullanılan senaryolar ve tasarım ipuçları
Tarih boyutu sadece tarih parçalamak için değil, iş süreçlerini rapora doğru yansıtmak için de kullanılır. Aşağıdaki pratik senaryolar, hangi alanların neden işe yaradığını gösterir.
“Son tamamlanan ay” filtresi
Yöneticiler çoğu zaman “içinde bulunduğumuz ay” yerine “tamamlanan son ay” raporunu ister. Bu, eksik ay verisi nedeniyle yanlış kıyasların önüne geçer. Boyuta IsMonthClosed veya IsCompleteMonth gibi bir alan ekleyip ETL’de güncellemek, raporların tek tıkla doğru filtrelenmesini sağlar.
Hafta bazlı raporlar ve ISO hafta standardı
Hafta raporlarında “hafta 1–52” tanımı ülkeden ülkeye ve standarda göre değişebilir. ISO week kullanıyorsanız, WeekOfYear’ü ISO kurallarıyla hesaplayıp ayrıca ISOYear alanı eklemek gerekebilir. Bu sayede yılın başındaki günlerin “geçen yılın son haftası” olarak sayılması gibi durumlar raporlarda doğru görünür.
4-4-5 dönemleme gibi özel perakende takvimleri
Perakendede 4-4-5 gibi dönemlemeler, ayların eşit gün sayısına yaklaşmasını sağlar. Bu durumda DimDate’te Period445, PeriodStartDate, PeriodEndDate gibi alanlar gerekir. Bu alanlar hesaplanması zor olduğundan, boyutta saklamak raporlama tarafını ciddi ölçüde basitleştirir.
SQL’de tarih boyutu ile doğru rapor tasarımı için kontrol listesi
Tarih boyutu kurulduktan sonra esas değer, raporların bu boyuta “bağlı kalması” ile ortaya çıkar. Aşağıdaki kontrol listesi, geliştirme sürecinde hızlı bir doğrulama sağlar:
- Fact tablosunda tarih anahtarı tek tip mi (DateKey gibi) ve DimDate ile birebir join oluyor mu?
- Takvim ayı ve mali dönem ayrımı raporlarda net mi; ilgili alanlar boyutta mevcut mu?
- İş günü ve resmi tatil kuralları yönetilebilir bir kaynaktan mı geliyor?
- YoY/MoM/YTD gibi kıyaslar boyut alanlarıyla mı yapılıyor, yoksa sorgu içinde “kestirme” mi?
- Eksik tarih ve veri kalitesi izleniyor mu (Unknown satırı, uyarı raporu vb.)?
Bu kontrol listesi, özellikle birden çok ekip aynı veri seti üzerinde çalışıyorsa önem kazanır. Boyut tasarımını bir kez doğru yaptığınızda, raporlama taleplerine yanıt verme hızınız artar ve sürpriz farklılıklar azalır.
Öğrenmeyi hızlandırmak: SQL pratiği ve standartlar
Tarih boyutu; modelleme, ETL ve raporlama disiplinlerinin kesişimindedir. Ekibinizin aynı kavramları aynı şekilde uygulaması için örnek sorgular, şablonlar ve isimlendirme standartları oluşturmak iyi bir yatırımdır. Eğer bu konuları daha sistematik şekilde ele almak isterseniz, içeriği pekiştirecek pratiklerle ilerleyebileceğiniz bir SQL eğitimi planlamak da faydalı olabilir.
Sonuç olarak; dönem ve karşılaştırma raporları “SQL fonksiyonları” ile değil, iyi tasarlanmış bir tarih boyutu ile güvenilir hale gelir. Doğru boyut alanlarıyla hem analistlerin hem de karar vericilerin beklediği tutarlılığı yakalayabilirsiniz; üstelik bu tutarlılık, yeni rapor taleplerinde hız olarak geri döner.


