DATEDIFF ile İki Tarih Arasındaki Farkı Hesaplama

Veritabanı yönetiminde tarih hesaplamaları, günlük operasyonların vazgeçilmez bir parçasıdır. Bir kullanıcının son girişinden bu yana kaç gün geçtiğini bulmak, siparişlerin ortalama teslim süresini hesaplamak ya da sözleşme bitiş tarihlerini takip etmek gibi onlarca farklı senaryoda tarih farkı hesaplamaya ihtiyaç duyarsınız. MySQL ve MariaDB’de bu işin en temiz yolu DATEDIFF fonksiyonundan geçiyor. Bu yazıda fonksiyonun temel kullanımından başlayıp gerçek dünya senaryolarına kadar her şeyi ele alacağız.

DATEDIFF Fonksiyonu Nedir?

DATEDIFF, iki tarih değeri arasındaki farkı gün cinsinden döndüren bir MySQL/MariaDB fonksiyonudur. Sözdizimi son derece basittir:

DATEDIFF(tarih1, tarih2)

Fonksiyon tarih1 - tarih2 işlemini yapar ve sonucu tam sayı olarak gün cinsinden verir. Eğer tarih1, tarih2‘den büyükse pozitif değer, küçükse negatif değer döner. Aynı tarihler girildiğinde ise sıfır döner.

Önemli bir not: DATEDIFF yalnızca tarihleri karşılaştırır, saat/dakika/saniye bilgisini dikkate almaz. Eğer DATETIME değerleri geçirseniz bile sadece tarih kısmı hesaba katılır. Saat bazlı fark hesaplamaları için TIMESTAMPDIFF fonksiyonuna bakmanız gerekir, bunu da yazının ilerleyen bölümlerinde ele alacağız.

Temel Kullanım Örnekleri

Önce basit bir örnekle başlayalım:

SELECT DATEDIFF('2024-12-31', '2024-01-01') AS gun_farki;
-- Sonuç: 365

Negatif değer döndüren bir örnek:

SELECT DATEDIFF('2024-01-01', '2024-12-31') AS gun_farki;
-- Sonuç: -365

Bugünden itibaren geçen süreyi hesaplamak için CURDATE() fonksiyonu ile birlikte kullanabilirsiniz:

SELECT 
    kullanici_adi,
    kayit_tarihi,
    DATEDIFF(CURDATE(), kayit_tarihi) AS uyelik_gun_sayisi
FROM kullanicilar
ORDER BY uyelik_gun_sayisi DESC;

Bu sorgu, her kullanıcının sisteme kayıt olduğu günden bugüne kadar kaç gündür üye olduğunu listeler. Müşteri sadakat programlarında sıkça kullanılan bir sorgudur.

Gerçek Dünya Senaryoları

Senaryo 1: E-Ticaret – Sipariş Teslim Süresi Analizi

Bir e-ticaret platformu yönetiyorsunuz ve kargo firmasının performansını ölçmek istiyorsunuz. Siparişin verildiği tarih ile teslim edildiği tarih arasındaki farkı hesaplayarak ortalama teslim süresini bulabilirsiniz:

SELECT 
    kargo_firmasi,
    COUNT(*) AS toplam_siparis,
    AVG(DATEDIFF(teslim_tarihi, siparis_tarihi)) AS ortalama_teslim_gun,
    MIN(DATEDIFF(teslim_tarihi, siparis_tarihi)) AS en_hizli_teslimat,
    MAX(DATEDIFF(teslim_tarihi, siparis_tarihi)) AS en_yavas_teslimat
FROM siparisler
WHERE teslim_tarihi IS NOT NULL
    AND siparis_tarihi >= '2024-01-01'
GROUP BY kargo_firmasi
ORDER BY ortalama_teslim_gun ASC;

Bu sorgu ile hangi kargo firmasının daha hızlı teslimat yaptığını net olarak görebilirsiniz. WHERE teslim_tarihi IS NOT NULL koşulunu eklemek önemli, çünkü henüz teslim edilmemiş siparişler hesabı bozar.

Senaryo 2: İnsan Kaynakları – Deneme Süresi Takibi

Yeni işe alınan çalışanların deneme süresini takip etmek için klasik bir HR senaryosu:

SELECT 
    ad_soyad,
    ise_giris_tarihi,
    DATEDIFF(CURDATE(), ise_giris_tarihi) AS gecen_gun,
    CASE 
        WHEN DATEDIFF(CURDATE(), ise_giris_tarihi) < 90 THEN 'Deneme Süresinde'
        WHEN DATEDIFF(CURDATE(), ise_giris_tarihi) BETWEEN 90 AND 365 THEN 'İlk Yıl'
        ELSE 'Kıdemli Çalışan'
    END AS durum,
    90 - DATEDIFF(CURDATE(), ise_giris_tarihi) AS deneme_bitis_kalan_gun
FROM calisanlar
WHERE aktif = 1
    AND DATEDIFF(CURDATE(), ise_giris_tarihi) < 90
ORDER BY ise_giris_tarihi ASC;

Bu sorgu, deneme süresindeki çalışanları listeler ve kaç günlerinin kaldığını gösterir. İK departmanı için haftalık otomatik rapor olarak çalıştırılabilecek bir sorgudur.

Senaryo 3: Güvenlik – Şifre Yaşı Kontrolü

Sistem güvenliği açısından kullanıcıların şifrelerini belirli periyotlarla değiştirmesini zorunlu kılmak istiyorsunuz:

SELECT 
    kullanici_adi,
    email,
    son_sifre_degisim_tarihi,
    DATEDIFF(CURDATE(), son_sifre_degisim_tarihi) AS sifre_yasi_gun,
    CASE
        WHEN DATEDIFF(CURDATE(), son_sifre_degisim_tarihi) > 90 THEN 'KRİTİK - Hemen Değiştirilmeli'
        WHEN DATEDIFF(CURDATE(), son_sifre_degisim_tarihi) > 75 THEN 'UYARI - Değiştirilmeli'
        WHEN DATEDIFF(CURDATE(), son_sifre_degisim_tarihi) > 60 THEN 'BİLGİ - Yakında Değiştirilmeli'
        ELSE 'Normal'
    END AS sifre_durumu
FROM kullanici_hesaplari
WHERE aktif = 1
    AND DATEDIFF(CURDATE(), son_sifre_degisim_tarihi) > 60
ORDER BY sifre_yasi_gun DESC;

Bu sorguyu bir cron job ile çalıştırıp sonuçları mail olarak gönderebilirsiniz. Özellikle PCI-DSS gibi uyumluluk gereksinimlerini karşılamak zorunda olduğunuzda bu tür sorgular hayat kurtarır.

DATEDIFF ile Filtreleme

WHERE koşullarında DATEDIFF kullanmak çok yaygın bir ihtiyaçtır:

-- Son 30 gün içinde kayıt olan kullanıcıları bul
SELECT kullanici_adi, email, kayit_tarihi
FROM kullanicilar
WHERE DATEDIFF(CURDATE(), kayit_tarihi) <= 30;

-- 1 yıldan eski ve işlem yapılmamış hesapları bul
SELECT hesap_no, kullanici_adi, son_islem_tarihi
FROM hesaplar
WHERE DATEDIFF(CURDATE(), son_islem_tarihi) > 365
    AND durum = 'aktif';

Performans notu: DATEDIFF fonksiyonunu WHERE koşulunda bir sütuna uyguladığınızda, o sütundaki indeksin kullanılmasını engelleyebilirsiniz. Büyük tablolarda performans sorunuyla karşılaşırsanız, şu yaklaşımı tercih edin:

-- İndeks dostu yaklaşım
SELECT kullanici_adi, email, kayit_tarihi
FROM kullanicilar
WHERE kayit_tarihi >= DATE_SUB(CURDATE(), INTERVAL 30 DAY);

Bu yöntem, tarihi hesaplayarak sabit bir değere dönüştürdüğü için indeksi kullanabilir ve büyük tablolarda çok daha hızlı çalışır.

TIMESTAMPDIFF ile Karşılaştırma

DATEDIFF sadece gün cinsinden sonuç verirken, TIMESTAMPDIFF farklı birimlerde sonuç almanızı sağlar:

SELECT 
    DATEDIFF('2024-12-31', '2024-01-01') AS gun_farki,
    TIMESTAMPDIFF(WEEK, '2024-01-01', '2024-12-31') AS hafta_farki,
    TIMESTAMPDIFF(MONTH, '2024-01-01', '2024-12-31') AS ay_farki,
    TIMESTAMPDIFF(YEAR, '2020-06-15', '2024-06-15') AS yil_farki,
    TIMESTAMPDIFF(HOUR, '2024-01-01 08:00:00', '2024-01-01 17:30:00') AS saat_farki,
    TIMESTAMPDIFF(MINUTE, '2024-01-01 08:00:00', '2024-01-01 08:45:00') AS dakika_farki;

TIMESTAMPDIFF parametreleri şunlardır:

  • SECOND: Saniye cinsinden fark döndürür
  • MINUTE: Dakika cinsinden fark döndürür
  • HOUR: Saat cinsinden fark döndürür
  • DAY: Gün cinsinden fark döndürür
  • WEEK: Hafta cinsinden fark döndürür
  • MONTH: Ay cinsinden fark döndürür
  • QUARTER: Çeyrek yıl cinsinden fark döndürür
  • YEAR: Yıl cinsinden fark döndürür

Yaş hesaplama gibi senaryolarda DATEDIFF yerine TIMESTAMPDIFF çok daha güvenilir sonuç verir:

-- Doğum tarihinden yaş hesaplama
SELECT 
    ad_soyad,
    dogum_tarihi,
    TIMESTAMPDIFF(YEAR, dogum_tarihi, CURDATE()) AS yas,
    DATEDIFF(CURDATE(), dogum_tarihi) / 365.25 AS yas_yaklasik -- Bu yöntem yanlış olabilir
FROM musteriler
WHERE durum = 'aktif';

Yaş hesaplamasında DATEDIFF / 365 yaklaşımı artık yıllar nedeniyle hatalı sonuç verebilir. TIMESTAMPDIFF(YEAR, ...) kullanmak çok daha doğrudur.

Gelişmiş Kullanım: Hafta ve İş Günü Hesaplamaları

DATEDIFF hafta sonu ve resmi tatilleri dikkate almaz, sadece takvim günü farkı verir. Ancak iş günü bazlı hesaplamalar yapmanız gerekiyorsa biraz daha yaratıcı olmak gerekir:

-- İki tarih arasındaki tahmini iş günü sayısı (hafta sonları hariç)
SELECT 
    siparis_no,
    siparis_tarihi,
    teslim_tarihi,
    DATEDIFF(teslim_tarihi, siparis_tarihi) AS toplam_gun,
    -- Hafta sonu günlerini çıkartarak iş günü tahmini
    DATEDIFF(teslim_tarihi, siparis_tarihi) 
        - (FLOOR(DATEDIFF(teslim_tarihi, siparis_tarihi) / 7) * 2)
        - (CASE WHEN DAYOFWEEK(siparis_tarihi) = 1 THEN 1 ELSE 0 END)
        - (CASE WHEN DAYOFWEEK(teslim_tarihi) = 7 THEN 1 ELSE 0 END)
    AS tahmini_is_gunu
FROM siparisler
WHERE teslim_tarihi IS NOT NULL;

Bu hesaplama mükemmel değil ve resmi tatilleri içermiyor, ancak haftalık SLA hesaplamalarında işe yarayacak kadar doğru bir yaklaşım sunar. Resmi tatilleri de dahil etmek istiyorsanız ayrı bir tatil_gunleri tablosu tutup subquery ile saydırmanız gerekir.

NULL Değer Yönetimi

DATEDIFF fonksiyonuna NULL değer geçildiğinde sonuç her zaman NULL döner. Bu durum özellikle henüz tamamlanmamış süreçlerde sorun çıkarabilir:

SELECT 
    proje_adi,
    baslangic_tarihi,
    bitis_tarihi,
    DATEDIFF(
        COALESCE(bitis_tarihi, CURDATE()),  -- Bitmemişse bugünü kullan
        baslangic_tarihi
    ) AS gecen_gun,
    CASE 
        WHEN bitis_tarihi IS NULL THEN 'Devam Ediyor'
        ELSE 'Tamamlandı'
    END AS proje_durumu
FROM projeler
ORDER BY baslangic_tarihi DESC;

COALESCE fonksiyonu burada kritik rol oynuyor. Bitis_tarihi NULL ise CURDATE() kullanarak hâlâ devam eden projeler için bugüne kadar geçen süreyi gösteriyoruz.

Performans ve İndeksleme İpuçları

Üretim ortamında milyonlarca satırlı tablolarla çalışırken birkaç önemli noktayı aklınızda tutun:

Fonksiyon bazlı sorgularda indeks kullanımını test etmek için EXPLAIN kullanın:

-- Önce EXPLAIN ile sorgu planına bakın
EXPLAIN SELECT kullanici_adi, kayit_tarihi
FROM kullanicilar
WHERE DATEDIFF(CURDATE(), kayit_tarihi) <= 30;

-- Sonra indeks dostu versiyonu deneyin
EXPLAIN SELECT kullanici_adi, kayit_tarihi
FROM kullanicilar
WHERE kayit_tarihi >= DATE_SUB(CURDATE(), INTERVAL 30 DAY);

EXPLAIN çıktısında type sütununa bakın. İlk sorguda ALL (full table scan) görüyorsanız ve ikinci sorguda range görüyorsanız, ikinci yaklaşımın ne kadar fark yarattığını anlarsınız.

Tarih sütunlarına mutlaka indeks ekleyin:

-- Sık sorguladığınız tarih sütunlarına indeks ekleyin
ALTER TABLE siparisler ADD INDEX idx_siparis_tarihi (siparis_tarihi);
ALTER TABLE kullanicilar ADD INDEX idx_kayit_tarihi (kayit_tarihi);

-- Bileşik sorgular için composite index düşünün
ALTER TABLE kullanicilar ADD INDEX idx_durum_kayit (durum, kayit_tarihi);

Stored Procedure ile Periyodik Raporlama

Sysadmin olarak çok sık karşılaşacağınız ihtiyaçlardan biri de bu tür sorguları otomatikleştirmektir. Kısa bir stored procedure örneği:

DELIMITER //

CREATE PROCEDURE pasif_kullanici_raporu(IN gun_esigi INT)
BEGIN
    SELECT 
        kullanici_adi,
        email,
        son_giris_tarihi,
        DATEDIFF(CURDATE(), son_giris_tarihi) AS inaktif_gun,
        kayit_tarihi
    FROM kullanicilar
    WHERE 
        aktif = 1
        AND son_giris_tarihi IS NOT NULL
        AND DATEDIFF(CURDATE(), son_giris_tarihi) >= gun_esigi
    ORDER BY inaktif_gun DESC;
END //

DELIMITER ;

-- Kullanım: 90 gündür giriş yapmayan aktif kullanıcıları listele
CALL pasif_kullanici_raporu(90);

Bu stored procedure’ü bir cron job ile çağırıp çıktısı MySQL’in INTO OUTFILE komutu ile dosyaya yazdırabilir ya da uygulama katmanından düzenli aralıklarla tetikleyebilirsiniz.

Yaygın Hatalar ve Çözümleri

Pratikte karşılaşılan bazı tuzaklara dikkat etmek gerekiyor:

Tarih formatı tutarsızlığı: DATEDIFF YYYY-MM-DD formatını bekler. Farklı formatlarda tarihler saklıyorsanız STR_TO_DATE kullanmanız gerekir.

  • Hatalı kullanım: DATEDIFF('31-12-2024', '01-01-2024')
  • Doğru kullanım: DATEDIFF(STR_TO_DATE('31-12-2024', '%d-%m-%Y'), STR_TO_DATE('01-01-2024', '%d-%m-%Y'))

Saat dilimi sorunları: Sunucunuzun saat dilimi ile uygulamanın saat dilimi farklıysa CURDATE() beklenmedik sonuçlar verebilir. SELECT @@global.time_zone, @@session.time_zone; komutu ile kontrol edin.

Negatif değerlere dikkat: Parametre sırasını karıştırırsanız negatif değer alırsınız. Güvenli olmak için ABS(DATEDIFF(...)) kullanabilirsiniz, ancak bu durumda hangi tarihin daha büyük olduğu bilgisini kaybedersiniz.

Tip dönüşümü sorunu: VARCHAR olarak saklanan tarih değerleri DATEDIFF ile beklenmedik sonuç verebilir veya tamamen yanlış çalışabilir. Tarih sütunlarınızın her zaman DATE veya DATETIME tipinde olduğundan emin olun.

Sonuç

DATEDIFF, sadeliği ile güçlü bir fonksiyondur. Gün bazlı tarih farkı hesaplamalarında güvenilir ve hızlı çalışır. Ancak saat/dakika/saniye hassasiyeti gerektiren durumlarda TIMESTAMPDIFF‘e yönelmeniz, büyük tablolarda performans için fonksiyon yerine DATE_SUB ile statik tarih karşılaştırması yapmanız ve NULL değerleri her zaman COALESCE ile yönetmeniz gerektiğini aklınızda tutun.

Yazı boyunca ele aldığımız senaryolar, günlük veritabanı yönetim işlerinde karşınıza çıkacak durumların büyük bölümünü kapsıyor. E-ticaret analitikten İK yönetimine, güvenlik denetiminden proje takibine kadar geniş bir yelpazede DATEDIFF çözüm üretebilecek kadar esnek bir araç. Üretim ortamında her yeni sorguyu önce EXPLAIN ile test etmeyi alışkanlık haline getirirseniz, ilerleyen zamanlarda performans sorunlarıyla çok daha az uğraşırsınız.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir