Dashboard rakamları tutmuyorsa nedeni tahmin etmeye başlamayın; önce farkın hangi günde ve hangi kırılımda başladığını bulun. Fark tek bir güne ya da tek bir şubeye sığıyorsa aranacak yer birkaç satıra iner. Yazı dashboard kavramını bildiğinizi varsayar; bilmiyorsanız dashboard rehberi ve dashboard yazılımı rehberi başlangıç için yeterli. Aşağıda belirtiye göre sıralanmış nedenler var; her birinin altında PostgreSQL'de çalışan bir kontrol sorgusu ve örnek çıktısı duruyor. Yazının sonunda, farkı eşiğin üstünde olan raporu hiç yayımlamamak için bir kural kurulur.
| Belirti | İlk bakılacak yer |
|---|---|
| Dashboard toplamı kaynaktan büyük | JOIN sonrası satır sayısı |
| Dashboard toplamı kaynaktan küçük | filtre, durum alanı, NULL |
| Fark gün sonuna yakın kayıtlarda | gün sınırı ve saat dilimi |
| Dün doğruydu, bugün tutmuyor | son yükleme zamanı, önbellek |
| İki kişi aynı raporda farklı toplam görüyor | satır düzeyi güvenlik rolü |
| Toplam tutuyor, ortalama tutmuyor | ortalamanın ortalaması |
Önce farkı daralt: hangi gün, hangi kırılım
Varsayalım ki bir sipariş tablosu ve dashboard'un beslendiği günlük özet tablosu var. Tüm örnek veri uydurmadır; rakamların hepsi "örnek"tir ve sorgular PostgreSQL'de çalıştırılmış, çıktılar o çalıştırmadan alınmıştır (yükleme tazeliği bölümündeki yukleme_log örneği hariç; o, uyarlamanız için kurgudur).
SET timezone = 'UTC';
CREATE TEMP TABLE siparis (id int, musteri_id int, tutar numeric(10,2), durum text, olusma timestamptz, indirim numeric(10,2));
INSERT INTO siparis VALUES
(1,10,100.00,'tamam','2026-09-01 10:00+03',NULL),
(2,11,250.00,'tamam','2026-09-01 22:30+03',10.00),
(3,10,100.00,'tamam','2026-09-02 00:40+03',NULL),
(4,12,400.00,'iptal','2026-09-02 12:00+03',NULL),
(5,11,150.00,'tamam','2026-09-02 23:59:59.500+03',20.00),
(6,13, 80.00,'tamam','2026-09-03 09:15+03',NULL);
CREATE TEMP TABLE adres (musteri_id int, tip text);
INSERT INTO adres VALUES (10,'fatura'),(11,'fatura'),(11,'teslimat'),(12,'fatura'),(13,'fatura');
CREATE TEMP TABLE dash (gun date, toplam numeric(10,2));
INSERT INTO dash VALUES ('2026-09-01',450.00),('2026-09-02',150.00),('2026-09-03',80.00);Önce kaynak ve dashboard'u gün gün yan yana koyun. FULL JOIN kullanmak önemli: yalnızca bir tarafta bulunan günü de görürsünüz.
WITH kaynak AS (
SELECT (olusma AT TIME ZONE 'Europe/Istanbul')::date AS gun, sum(tutar) AS toplam
FROM siparis WHERE durum <> 'iptal' GROUP BY 1)
SELECT coalesce(k.gun, d.gun) AS gun, k.toplam AS kaynak, d.toplam AS dashboard,
coalesce(d.toplam,0) - coalesce(k.toplam,0) AS fark
FROM kaynak k FULL JOIN dash d ON d.gun = k.gun
ORDER BY 1; gun | kaynak | dashboard | fark
------------+--------+-----------+---------
2026-09-01 | 350.00 | 450.00 | 100.00
2026-09-02 | 250.00 | 150.00 | -100.00
2026-09-03 | 80.00 | 80.00 | 0.00Çıktıdaki işarete bakın: 1 Eylül'de +100, 2 Eylül'de tam −100. Aynı büyüklükte, ters işaretli iki fark kaydın kaybolmadığını, yanlış güne yazıldığını düşündürür; üç günün toplamı tutuyor. Bu örnekte 3 numaralı sipariş (100, İstanbul saatiyle 2 Eylül 00:40) dashboard'da 1 Eylül'e yazılmış. Nedeni gün sınırı bölümünde.
Fark gün gün değil de tek yönlü büyüyorsa aynı sorguyu bir kırılımla (şube, ürün grubu, kanal) tekrarlayın ve farkı olan yarıyı seçip yeniden bölün. Her turda arama alanı yarıya iner.
Toplam şişiyorsa: JOIN satırı çoğaltıyor
Dashboard kaynaktan büyükse ilk şüpheli, bir JOIN'in ilişkili tabloda birden fazla satır bulması ve ana tablodaki tutarın tekrar toplanmasıdır. Kontrol, JOIN'li satır sayısını sipariş sayısıyla kıyaslamaktır:
SELECT count(*) AS satir, count(DISTINCT s.id) AS siparis, sum(s.tutar) AS sisen_toplam
FROM siparis s JOIN adres a USING (musteri_id);
SELECT sum(tutar) AS gercek FROM siparis; satir | siparis | sisen_toplam
-------+---------+--------------
8 | 6 | 1480.00
gercek
---------
1080.00Altı sipariş sekiz satıra çıkmış, toplam 1.480'e şişmiş; gerçek 1.080. Çoğaltan anahtarı bulmak için ilişkili tabloyu müşteriye göre sayın:
SELECT musteri_id, count(*) FROM adres GROUP BY 1 HAVING count(*) > 1;11 numaralı müşterinin iki adresi (fatura ve teslimat) var; 2 ve 5 numaralı siparişler (250 + 150 = 400) iki kez toplanmış. Çözüm, adresi sipariş başına tek satıra indirmektir:
SELECT sum(s.tutar) AS dogru FROM siparis s JOIN adres a ON a.musteri_id = s.musteri_id AND a.tip = 'fatura';Sonuç 1.080. Bu noktada akla sum(DISTINCT tutar) gelir; çözüm olarak kullanmayın, ne yaptığını görmek için çalıştırın:
SELECT sum(DISTINCT tutar) AS sum_distinct_tutar FROM siparis;Sonuç 980. Ne şişmiş 1.480 ne gerçek 1.080: iki farklı siparişin tutarı aynı (100) olduğu için biri sessizce düşmüş. DISTINCT çoğalmayı gizlemez, yerine başka bir hata koyar. Satır sayısı farkını teşhis aracı olarak kullanın, çözümü anahtar ilişkisinde yapın.
Aynı count(*) / count(DISTINCT id) karşılaştırması, yükleme betiği yeniden çalıştığında oluşan çift kayıtları da gösterir; o durumun ayrıntısı bu yazının dışında.
Toplam eksikse: filtre ve NULL
Dashboard küçükse önce özet tablonun hangi durumları dışladığına bakın. Durum dağılımı farkın ne kadarının bilinçli filtreden geldiğini söyler:
SELECT durum, count(*), sum(tutar) FROM siparis GROUP BY 1 ORDER BY 1; durum | count | sum
-------+-------+--------
iptal | 1 | 400.00
tamam | 5 | 680.00Dashboard yalnızca "tamam" gösteriyorsa 1.080 − 400 = 680 beklenir. İptal ve iade gibi filtreleri raporun başlığında yazılı tutmak, bu farkı kimsenin "hata" sanmasını önler.
Filtreden sonra kalan farkın bir kaynağı da NULL'dur. PostgreSQL toplama fonksiyonları belgesine göre count(*) giriş satırlarını, count(ifade) ise yalnız NULL olmayanları sayar; avg NULL olmayan değerlerin ortalamasıdır. Örnek tabloda indirim 6 satırın yalnız 2'sinde dolu:
SELECT count(*) AS hepsi, count(indirim) AS dolu, avg(indirim) AS ort, sum(indirim) AS top,
sum(tutar - indirim) AS fark_toplam, sum(tutar - coalesce(indirim,0)) AS dogru_net
FROM siparis; hepsi | dolu | ort | top | fark_toplam | dogru_net
-------+------+---------------------+-------+-------------+-----------
6 | 2 | 15.0000000000000000 | 30.00 | 370.00 | 1050.00sum(tutar - indirim) 370 verdi, doğru net toplam 1.050. NULL ile yapılan çıkarma NULL üretir; o satırların tutarı toplamdan sessizce düşer. Çözüm coalesce(indirim,0). "Net tutar" gibi bir ölçünün tanımı raporlar arasında farklıysa (biri indirimi düşüyor, öbürü düşmüyor) sorun sorguda değil tanımdadır; ölçü tanımlarını tek yerde tutmak için kurumsal raporlama rehberine bakın.
Aynı toplama fonksiyonları belgesi, hiç satır seçilmediğinde sum'ın sıfır değil NULL döndürdüğünü de yazıyor. İade olmayan bir gün için dashboard hücresi boş görünüyorsa sebep bu olabilir:
SELECT sum(tutar) AS bos_kume FROM siparis WHERE durum = 'iade';
SELECT coalesce(sum(tutar),0) AS bos_kume_sifir FROM siparis WHERE durum = 'iade';İlki boş, ikincisi 0 döner.
Gün sonuna yakın kayıtlar kayıyorsa: 23:59:59 ve saat dilimi
Gün kesimini <= '2026-09-02 23:59:59' diye yazarsanız, saniyenin kesri olan kayıtlar dışarıda kalır. Örnekte 5 numaralı sipariş 23:59:59.500'de oluşmuş:
SELECT count(*) FROM siparis WHERE (olusma AT TIME ZONE 'Europe/Istanbul') >= '2026-09-02' AND (olusma AT TIME ZONE 'Europe/Istanbul') <= '2026-09-02 23:59:59';
SELECT count(*) FROM siparis WHERE (olusma AT TIME ZONE 'Europe/Istanbul') >= '2026-09-02' AND (olusma AT TIME ZONE 'Europe/Istanbul') < '2026-09-03';İlki 2, ikincisi 3 döner. Kural basit: alt sınır dahil, üst sınır ertesi günün 00:00'ı ve hariç.
İkinci tuzak saat dilimidir. PostgreSQL tarih/saat tipleri belgesine göre timestamptz değerleri içeride UTC saklanır ve oturumun TimeZone ayarına göre gösterilir. Kolonu hiçbir dilim vermeden ::date yaparsanız gün, oturum diliminin gününe göre kesilir. Sunucu ya da BI aracı UTC'de çalışıyorsa:
SELECT olusma::date AS utc_gun, sum(tutar) FROM siparis GROUP BY 1 ORDER BY 1;
SELECT (olusma AT TIME ZONE 'Europe/Istanbul')::date AS yerel_gun, sum(tutar) FROM siparis GROUP BY 1 ORDER BY 1; utc_gun | sum
------------+--------
2026-09-01 | 450.00
2026-09-02 | 550.00
2026-09-03 | 80.00
yerel_gun | sum
------------+--------
2026-09-01 | 350.00
2026-09-02 | 650.00
2026-09-03 | 80.00Bu toplamlar iptal edilen sipariş dahil hesaplandığı için ilk bölümdeki rakamlardan farklıdır. 00:40 (İstanbul) UTC'de bir önceki günün 21:40'ıdır; UTC günüyle toplayan dashboard bu siparişi 1 Eylül'e yazar. İlk bölümdeki +100/−100 farkı budur. Kalıcı çözüm sorguda dilimi açıkça yazmaktır; +03 ofsetini elle yazmayın, bölge adını kullanın. Resmî Gazete'de 8 Eylül 2016'da yayımlanan 2016/9154 sayılı Bakanlar Kurulu kararı, 27 Mart 2016'da başlatılan yaz saati uygulamasının "her yıl, yıl boyu" sürdürülmesini söylüyor. Yani kışın saat geri alınmıyor ve Türkiye yıl boyu UTC+3'te kalıyor. Bu yüzden örnek verideki +03 ofseti her ay geçerli. Yukarıdaki 00:40'ın UTC'de 21:40 çıkması da aynı üç saatlik farkı gösteriyor.
AT TIME ZONE kolonun tipine göre ters yönde çalışır. PostgreSQL tarih/saat fonksiyonları belgesine göre timestamptz üzerinde kullanıldığında dilimsiz yerel saati döndürür:
SELECT '2026-09-02 00:40+03'::timestamptz AT TIME ZONE 'Europe/Istanbul' AS yerel, '2026-09-02 00:40+03'::timestamptz AT TIME ZONE 'UTC' AS utc;
SELECT TIMESTAMP '2026-09-02 00:40+03' AS dilim_gostergesi_yok_sayilir; yerel | utc
---------------------+---------------------
2026-09-02 00:40:00 | 2026-09-01 21:40:00
dilim_gostergesi_yok_sayilir
------------------------------
2026-09-02 00:40:00İkinci sorgu, tarih/saat tipleri belgesinde yazan davranışı gösteriyor: dilimsiz timestamp olarak yorumlanan değerde saat dilimi göstergesi sessizce yok sayılır. Kolonunuz dilimsiz timestamp ise ETL aşamasında dilimin nasıl atıldığını kontrol edin; sonradan düzeltmesi zordur.
Dün doğruydu, bugün tutmuyorsa: yükleme tazeliği
Rakam kendi kendine değişmediyse veri eski olabilir. Yükleme günlüğü tutuyorsanız bayatlığı sorguyla yakalayın. Aşağıdaki yukleme_log bir uyarlama örneğidir: bu yazının örnek kurulumunda yoktur, kendi yükleme kaydı tablonuzun adı ve sütunlarıyla değiştirin (burada tablo ve bitis sütunları olduğu varsayıldı). 26 saat de örnek bir eşiktir, kendi yükleme sıklığınıza göre belirleyin:
SELECT tablo, max(bitis) AS son_yukleme, now() - max(bitis) > interval '26 hours' AS bayat_mi FROM yukleme_log GROUP BY 1;
SELECT max(olusma) AS kaynakta_son_kayit FROM siparis; tablo | son_yukleme | bayat_mi
---------+------------------------+----------
siparis | 2026-09-02 23:10:00+00 | tBu örnek çıktıdaki varsayımsal son yükleme İstanbul saatiyle 3 Eylül 02:10; siparis tablosundaki son kayıt ise 3 Eylül 09:15. Bu çıktı ilk bölümdeki dashboard'dan bağımsız bir kurgudur; gerçek bir dashboard'da böyle bir fark görürseniz yüklemeden sonra gelen siparişler henüz içeri girmemiştir. bayat_mi sütununun değeri now()'a bağlı olduğundan sorguyu kendi tablonuzda çalıştırdığınızda farklı çıkar. Yükleme günlüğü yoksa kaynakta son kayıt zamanıyla dashboard'daki son gün karşılaştırması yine işe yarar. Özet tabloyu veri ambarında tutuyorsanız günlüğü de orada yönetmek kolaylaşır; ayrıntı için veri ambarı raporlama rehberi.
İki öneri: dashboard'un köşesine "son yükleme" zamanını koyun; ve raporu gösteren aracın önbelleğinin ayrı bir katman olduğunu hesaba katın. Özet tablo yenilenmiş olsa bile önbellek eski sonucu verebilir; kullandığınız BI aracının önbellek ayarına bakın.
İki kişi aynı rapora bakıp farklı toplam görüyorsa
Power BI kullanıyorsanız ve raporu hazırlayan kişiyle izleyen kişi farklı toplam görüyorsa satır düzeyi güvenliğe (RLS) bakın. Microsoft belgesine göre RLS yalnızca Görüntüleyici izinli kullanıcılar için veri erişimini kısıtlar; çalışma alanı Yöneticisi, Üye veya Katkıda Bulunan rolleri için geçerli değildir. Raporu yönetici hesabınızla açıp tam toplamı görürsünüz; görüntüleyici ise yalnız rolünün satırlarını görür.
Kontrol adımları:
- Çalışma alanında anlam modelinin "Diğer seçenekler" menüsünden "Güvenlik"i seçin; açılan satır düzeyi güvenlik sayfasında tanımlı rolleri ve üyelerini görürsünüz.
- Rolün yanındaki "Diğer seçenekler"den "Rol olarak test et"i seçin ve raporu o rolle açın.
- Toplamı kaynak sorgudaki aynı filtreli toplamla karşılaştırın.
Rolle açılan rapor kaynaktaki aynı filtreli toplamla tutuyorsa fark hata değil, RLS'in işidir; yanlış olan, kaynakla karşılaştırmayı yönetici görünümüyle yapmaktır. Tutmuyorsa sorun rol filtresinde ya da yukarıdaki nedenlerden birindedir. Aynı metrik iki raporda farklı çıkmaya devam ediyorsa bu iş raporlama danışmanlığı kapsamında ele alınabilir.
Toplam tutuyor ama ortalama tutmuyorsa
Satır bazlı oranların ya da ortalamaların ortalaması tutmaz; ağırlık sipariş adedidir. Örnek: A şubesinde 10 sipariş ortalama 100, B şubesinde 90 sipariş ortalama 200.
CREATE TEMP TABLE sube (sube text, siparis_adedi int, ort_sepet numeric);
INSERT INTO sube VALUES ('A',10,100),('B',90,200);
SELECT avg(ort_sepet) AS ortalamanin_ortalamasi, sum(siparis_adedi*ort_sepet)/sum(siparis_adedi) AS agirlikli FROM sube; ortalamanin_ortalamasi | agirlikli
------------------------+----------------------
150.0000000000000000 | 190.0000000000000000Dashboard 150 gösteriyor, kaynağın tüm siparişlerinden hesaplanan 190. Oran ve ortalama ölçülerini özet tabloda hazır ortalama olarak değil, pay ve payda ayrı toplanarak saklayın.
Rapor yayımlanmadan önce fark kuralı
Nedeni bulmak bir kez yapılır; yeniden olmaması için raporu yayımlamadan önce günlük bir kontrol çalıştırın. Aşağıdaki sorgu ilk bölümdeki karşılaştırmaya bir yüzde farkı ve karar sütunu ekler. 0.001 (yani %0,1) örnek bir eşiktir, standart değildir; kendi raporunuzun riskine göre belirleyin.
WITH kaynak AS (
SELECT (olusma AT TIME ZONE 'Europe/Istanbul')::date AS gun, sum(tutar) AS toplam
FROM siparis WHERE durum <> 'iptal' GROUP BY 1)
SELECT coalesce(k.gun,d.gun) gun, k.toplam kaynak, d.toplam dash,
round(100*abs(coalesce(d.toplam,0)-coalesce(k.toplam,0))/nullif(k.toplam,0),2) AS fark_yuzde,
CASE WHEN abs(coalesce(d.toplam,0)-coalesce(k.toplam,0)) > 0.001*coalesce(k.toplam,0) THEN 'YAYIMLAMA' ELSE 'tamam' END AS karar
FROM kaynak k FULL JOIN dash d ON d.gun=k.gun ORDER BY 1;| Gün | Kaynak | Dashboard | Fark % | Karar |
|---|---|---|---|---|
| 2026-09-01 | 350,00 | 450,00 | 28,57 | YAYIMLAMA |
| 2026-09-02 | 250,00 | 150,00 | 40,00 | YAYIMLAMA |
| 2026-09-03 | 80,00 | 80,00 | 0,00 | tamam |
Bu kuralı işe yarar kılan üç tercih:
- Eşiği para ya da yüzde olarak yazın, raporun sahibine bildirin ve eşik aşılırsa raporu yayımlamayın; "bir bakarız" bir kural değildir.
- Eşiği her ölçü için ayrı koyun: tutar toplamında %0,1 örnek olabilir, kişi sayısında 0 fark bile istenebilir.
- Dashboard'a kaynakla karşılaştırmanın yapıldığı zamanı yazın; okur rakamın hangi kontrolden geçtiğini görsün.



