MariaDB ve MySQL’de DATE_FORMAT ile Tarih Biçimlendirme
Veritabanlarında tarihlerle çalışmak, her sysadmin ve veritabanı yöneticisinin günlük rutininin ayrılmaz bir parçası. Log analizi yapıyorsun, rapor üretiyorsun, kullanıcı aktivitelerini izliyorsun… Her durumda tarihleri istediğin formatta gösterebilmek kritik bir beceri. MariaDB ve MySQL’de DATE_FORMAT fonksiyonu tam da bu iş için var ve bir kez alıştığında hayatını ciddi ölçüde kolaylaştırıyor.
DATE_FORMAT Nedir ve Neden Önemli?
DATE_FORMAT, tarih ve zaman değerlerini belirlediğin bir kalıba göre string olarak döndüren yerleşik bir MySQL/MariaDB fonksiyonu. Ham tarih verisini (2024-01-15 09:32:47 gibi) alıp sana 15 Ocak 2024, Pazartesi şeklinde geri verebilir. Bu basit görünen özellik, aslında raporlama, log analizi ve uygulama katmanı entegrasyonlarında devasa fark yaratıyor.
Fonksiyonun temel sözdizimi şu şekilde:
DATE_FORMAT(tarih_degeri, 'format_string')
Burada tarih_degeri bir DATE, DATETIME veya TIMESTAMP kolonu olabilir. format_string ise yüzde işareti ile başlayan özel karakterlerden oluşan bir kalıp.
En sık kullanılan format karakterleri şunlar:
- %Y: 4 haneli yıl (2024)
- %y: 2 haneli yıl (24)
- %m: 2 haneli ay numarası (01-12)
- %M: Ayın tam İngilizce adı (January, February…)
- %b: Ayın kısaltılmış İngilizce adı (Jan, Feb…)
- %d: 2 haneli gün (01-31)
- %e: Önde sıfır olmadan gün (1-31)
- %H: 24 saatlik formatta saat (00-23)
- %h: 12 saatlik formatta saat (01-12)
- %i: Dakika (00-59)
- %s: Saniye (00-59)
- %p: AM veya PM
- %W: Haftanın günü tam adı (Monday, Tuesday…)
- %a: Haftanın günü kısaltılmış (Mon, Tue…)
- %j: Yılın kaçıncı günü (001-366)
- %w: Haftanın günü rakam olarak (0=Sunday, 6=Saturday)
- %U: Haftanın numarası, pazar başlangıçlı (00-53)
- %V: Haftanın numarası, pazartesi başlangıçlı (01-53)
- %T: HH:MM:SS formatında zaman
- %r: 12 saatlik tam zaman (hh:mm:ss AM/PM)
Temel Kullanım Örnekleri
Önce basit bir tabloyla başlayalım. Bir web sunucusunun access loglarını veritabanında tuttuğunu düşün:
-- Örnek tablo yapısı
CREATE TABLE access_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
ip_address VARCHAR(45),
request_url VARCHAR(500),
status_code INT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
Şimdi bu tablodaki tarihleri farklı formatlarda çekelim:
-- Standart tarih formatı: GG/AA/YYYY
SELECT
ip_address,
DATE_FORMAT(created_at, '%d/%m/%Y') AS tarih,
DATE_FORMAT(created_at, '%H:%i:%s') AS saat,
status_code
FROM access_logs
ORDER BY created_at DESC
LIMIT 20;
Bu sorgu sana 15/01/2024 ve 09:32:47 gibi çıktılar verecek. Türk kullanıcılara rapor sunacaksan bu format çok daha okunaklı geliyor.
Şimdi biraz daha karmaşık bir format deneyelim:
-- Tarih ve saat bilgisini tek kolonda birleştir
SELECT
ip_address,
DATE_FORMAT(created_at, '%d.%m.%Y %H:%i') AS islem_zamani,
request_url,
status_code
FROM access_logs
WHERE status_code >= 500
ORDER BY created_at DESC;
Bu sorgu 500’lü hata kodlarını tarih ve saatle birlikte gösteriyor. Müşteriye gönderilecek bir hata raporu için birebir.
Gerçek Dünya Senaryosu 1: Günlük Log Analizi
Diyelim ki bir e-ticaret sitesinin sipariş veritabanını yönetiyorsun. Aylık satış raporları hazırlamak için siparişleri aya göre gruplamak gerekiyor:
-- Aylık sipariş özeti raporu
SELECT
DATE_FORMAT(siparis_tarihi, '%Y-%m') AS ay,
DATE_FORMAT(siparis_tarihi, '%M %Y') AS ay_adi,
COUNT(*) AS siparis_sayisi,
SUM(toplam_tutar) AS toplam_ciro,
AVG(toplam_tutar) AS ortalama_siparis
FROM siparisler
WHERE siparis_tarihi >= DATE_SUB(NOW(), INTERVAL 12 MONTH)
GROUP BY DATE_FORMAT(siparis_tarihi, '%Y-%m')
ORDER BY ay ASC;
Burada dikkat edilmesi gereken önemli bir nokta var: GROUP BY içinde de DATE_FORMAT kullanıyoruz. Eğer %M %Y ile gruplarsak Ocak 2023 ve Ocak 2024 aynı gruba düşer. %Y-%m formatı sıralama ve gruplama için daha güvenli.
Gerçek Dünya Senaryosu 2: Haftalık Raporlama
Sistem izleme araçlarından gelen metrikleri veritabanında tutuyorsun ve haftalık performans raporları üretmen gerekiyor:
-- Haftanın günlerine göre ortalama CPU kullanımı
SELECT
DATE_FORMAT(kayit_zamani, '%W') AS gun_adi,
DATE_FORMAT(kayit_zamani, '%w') AS gun_numarasi,
AVG(cpu_usage) AS ort_cpu,
MAX(cpu_usage) AS maks_cpu,
MIN(cpu_usage) AS min_cpu
FROM sunucu_metrikleri
WHERE kayit_zamani >= DATE_SUB(NOW(), INTERVAL 4 WEEK)
GROUP BY DATE_FORMAT(kayit_zamani, '%w'), DATE_FORMAT(kayit_zamani, '%W')
ORDER BY gun_numarasi;
Bu tür sorgular, hangi günlerde sistemin daha yoğun çalıştığını görmeyi sağlıyor. Backup zamanlamaları veya maintenance window planlaması için son derece kullanışlı bilgi.
DATE_FORMAT ile WHERE Koşulları
Bir yanlış anlaşılmayı hemen düzeltelim: DATE_FORMAT fonksiyonunu WHERE koşullarında filtreleme amacıyla kullanmak performans açısından kötü bir fikir. Çünkü bu durumda MySQL index kullanamaz ve full table scan yapar. Şöyle bir örnek:
-- KÖTÜ YAKLAŞIM - index kullanamaz
SELECT * FROM siparisler
WHERE DATE_FORMAT(siparis_tarihi, '%Y-%m') = '2024-01';
-- DOĞRU YAKLAŞIM - index kullanır
SELECT * FROM siparisler
WHERE siparis_tarihi >= '2024-01-01'
AND siparis_tarihi < '2024-02-01';
İkinci sorgu hem daha hızlı çalışır hem de siparis_tarihi kolonunda index varsa bundan faydalanır. DATE_FORMAT‘ı SELECT listesinde gösterim için kullan, filtreleme için native tarih karşılaştırmalarını tercih et.
Gerçek Dünya Senaryosu 3: Kullanıcı Aktivite Raporu
Bir SaaS uygulamasının kullanıcı aktivitelerini takip eden bir tablo var elimde. Saat dilimine göre aktivite dağılımını görmek istiyorum:
-- Saate göre aktivite yoğunluğu analizi
SELECT
DATE_FORMAT(aktivite_zamani, '%H:00') AS saat_dilimi,
COUNT(*) AS aktivite_sayisi,
COUNT(DISTINCT kullanici_id) AS benzersiz_kullanici
FROM kullanici_aktiviteleri
WHERE aktivite_zamani >= CURDATE() - INTERVAL 30 DAY
GROUP BY DATE_FORMAT(aktivite_zamani, '%H')
ORDER BY DATE_FORMAT(aktivite_zamani, '%H') + 0;
Burada ORDER BY DATE_FORMAT(aktivite_zamani, '%H') + 0 ifadesi string sıralama yerine numerik sıralama yapılmasını sağlıyor. Yoksa 10, 11, 12 yerine 1, 10, 11, 12, 2, 3... şeklinde sıralar ki bu hiç istemediğimiz bir durum.
CONCAT ile Türkçe Tarih Formatları
MySQL ve MariaDB’nin yerleşik ay adları İngilizce. Türkçe rapor üretmen gerekiyorsa birkaç farklı yol var. En pratik olanı ELT ve MONTH fonksiyonlarını kombinlemek:
-- Türkçe ay adlarıyla tarih formatı
SELECT
CONCAT(
DAY(siparis_tarihi),
' ',
ELT(MONTH(siparis_tarihi),
'Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran',
'Temmuz', 'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık'),
' ',
YEAR(siparis_tarihi)
) AS turkce_tarih,
musteri_adi,
toplam_tutar
FROM siparisler
ORDER BY siparis_tarihi DESC
LIMIT 10;
Bu sorgu 15 Ocak 2024 gibi çıktılar üretiyor. Müşteri raporları veya fatura tarihleri için çok kullanışlı.
Daha pratik bir yaklaşım olarak stored procedure veya view kullanabilirsin:
-- Türkçe tarih formatı için view oluşturma
CREATE VIEW v_siparisler_turkce AS
SELECT
id,
siparis_no,
DATE_FORMAT(siparis_tarihi, '%d.%m.%Y') AS siparis_tarihi_kisa,
CONCAT(
DATE_FORMAT(siparis_tarihi, '%d '),
ELT(MONTH(siparis_tarihi),
'Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran',
'Temmuz', 'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık'),
DATE_FORMAT(siparis_tarihi, ' %Y')
) AS siparis_tarihi_uzun,
musteri_adi,
toplam_tutar,
durum
FROM siparisler;
-- Artık her sorguda tekrar yazman gerekmiyor
SELECT siparis_tarihi_uzun, musteri_adi, toplam_tutar
FROM v_siparisler_turkce
WHERE durum = 'tamamlandi';
Gerçek Dünya Senaryosu 4: Sunucu Log Rotasyonu Takibi
Sysadmin olarak log dosyalarının boyutlarını ve rotasyon zamanlarını bir tabloda takip ettiğini düşün:
-- Log rotasyon raporu - son 7 günün özeti
SELECT
sunucu_adi,
DATE_FORMAT(rotasyon_zamani, '%d.%m.%Y') AS tarih,
DATE_FORMAT(rotasyon_zamani, '%H:%i') AS saat,
ROUND(dosya_boyutu / 1024 / 1024, 2) AS boyut_mb,
log_turu
FROM log_rotasyon_kayitlari
WHERE rotasyon_zamani >= NOW() - INTERVAL 7 DAY
ORDER BY rotasyon_zamani DESC;
-- Haftanın hangi gününde en fazla log üretildiğini bul
SELECT
DATE_FORMAT(rotasyon_zamani, '%W') AS gun,
COUNT(*) AS rotasyon_sayisi,
ROUND(AVG(dosya_boyutu / 1024 / 1024), 2) AS ort_boyut_mb,
ROUND(SUM(dosya_boyutu / 1024 / 1024), 2) AS toplam_boyut_mb
FROM log_rotasyon_kayitlari
WHERE rotasyon_zamani >= NOW() - INTERVAL 90 DAY
GROUP BY DATE_FORMAT(rotasyon_zamani, '%w'), DATE_FORMAT(rotasyon_zamani, '%W')
ORDER BY DATE_FORMAT(rotasyon_zamani, '%w') + 0;
Bu tür sorgular disk planlama ve kapasite yönetimi açısından değerli veriler sunuyor.
STR_TO_DATE ile Tersine Çevirme
DATE_FORMAT‘ın tam tersi olan STR_TO_DATE fonksiyonunu da bilmek gerekiyor. Dışarıdan string olarak gelen tarihleri veritabanına yazmak için kullanılır:
-- Farklı formatlardaki string tarihleri DATE tipine çevirme
INSERT INTO etkinlikler (etkinlik_adi, baslangic_tarihi) VALUES
('Yıllık Bakım', STR_TO_DATE('15/03/2024', '%d/%m/%Y')),
('Firewall Güncellemesi', STR_TO_DATE('20.03.2024 02:00', '%d.%m.%Y %H:%i')),
('Kapasite Planlaması', STR_TO_DATE('25-Mar-2024', '%d-%b-%Y'));
-- CSV import'larında sıkça karşılaşılan format
UPDATE musteri_kayitlari
SET kayit_tarihi = STR_TO_DATE(ham_tarih_string, '%m/%d/%Y')
WHERE ham_tarih_string IS NOT NULL
AND STR_TO_DATE(ham_tarih_string, '%m/%d/%Y') IS NOT NULL;
Bu özellikle legacy sistemlerden veri taşırken veya CSV import işlemlerinde hayat kurtarıcı oluyor.
DATE_FORMAT ile CASE WHEN Kombinasyonu
Raporlarda tarihlere göre koşullu etiketler eklemek istediğinde CASE WHEN ile güzel kombinasyonlar kurabilirsin:
-- Siparişleri yaşlarına göre kategorize et
SELECT
siparis_no,
DATE_FORMAT(siparis_tarihi, '%d.%m.%Y %H:%i') AS siparis_zamani,
musteri_adi,
CASE
WHEN siparis_tarihi >= NOW() - INTERVAL 1 HOUR THEN 'Son 1 saat'
WHEN siparis_tarihi >= NOW() - INTERVAL 24 HOUR THEN 'Bugün'
WHEN siparis_tarihi >= NOW() - INTERVAL 7 DAY THEN 'Bu hafta'
WHEN siparis_tarihi >= NOW() - INTERVAL 30 DAY THEN 'Bu ay'
ELSE CONCAT(DATE_FORMAT(siparis_tarihi, '%M %Y'), ' (Eski)')
END AS zaman_etiketi,
toplam_tutar,
durum
FROM siparisler
ORDER BY siparis_tarihi DESC;
Bu sorgu operasyonel dashboardlar için çok kullanışlı. Hangi siparişin ne kadar taze olduğunu görmek için ekipler bu tür sorgulara bayılıyor.
Performans Notları ve İpuçları
Üretim ortamında DATE_FORMAT kullanırken dikkat etmen gereken birkaç nokta var:
- SELECT listesinde kullan: Filtreleme için değil, gösterim için tasarlandığını unutma.
- Index’leri koru: WHERE koşullarında kolon üzerinde fonksiyon çağrısı yapmak index’i devre dışı bırakır.
- Büyük tablolarda dikkat: Milyonlarca satırlık tablolarda
DATE_FORMATher satır için çalışır. Gerekmedikçe LIMIT kullan. - Saklı prosedürlerde kapsülle: Aynı format string’i birden fazla yerde kullanıyorsan, bunu stored procedure veya view içinde koy.
- Computed columns düşün: MariaDB 5.2+ ve MySQL 5.7+’da sanal sütunlar (generated columns) ile sık kullanılan formatları önceden hesaplatabilirsin.
-- Generated column örneği - her sorguda hesaplamak yerine depolanmış versiyon
ALTER TABLE siparisler
ADD COLUMN siparis_ay_yil VARCHAR(7)
GENERATED ALWAYS AS (DATE_FORMAT(siparis_tarihi, '%Y-%m')) STORED,
ADD INDEX idx_siparis_ay_yil (siparis_ay_yil);
-- Artık bu çok daha hızlı çalışır
SELECT siparis_ay_yil, COUNT(*), SUM(toplam_tutar)
FROM siparisler
GROUP BY siparis_ay_yil
ORDER BY siparis_ay_yil;
TIME_FORMAT ve Sadece Saat Formatlama
Bazı durumlarda sadece saat bileşeniyle çalışman gerekebilir. TIME_FORMAT bunun için var:
-- Vardiya bazlı analiz
SELECT
CASE
WHEN HOUR(islem_zamani) BETWEEN 8 AND 15 THEN 'Gündüz Vardiyası'
WHEN HOUR(islem_zamani) BETWEEN 16 AND 23 THEN 'Akşam Vardiyası'
ELSE 'Gece Vardiyası'
END AS vardiya,
TIME_FORMAT(islem_zamani, '%H:%i:%s') AS islem_saati,
COUNT(*) AS islem_sayisi
FROM islem_kayitlari
WHERE DATE(islem_zamani) = CURDATE()
GROUP BY vardiya, TIME_FORMAT(islem_zamani, '%H:%i:%s')
ORDER BY islem_zamani;
Zaman Dilimi Farkındalığı
MariaDB ve MySQL sunucusu UTC’de çalışıyorsa ve raporları yerel saatte göstermen gerekiyorsa CONVERT_TZ ile DATE_FORMAT birlikte kullanılabilir:
-- UTC'deki veriyi Türkiye saatine (UTC+3) çevirerek formatla
SELECT
kullanici_adi,
DATE_FORMAT(
CONVERT_TZ(giris_zamani, '+00:00', '+03:00'),
'%d.%m.%Y %H:%i:%s'
) AS giris_zamani_tr,
DATE_FORMAT(
CONVERT_TZ(cikis_zamani, '+00:00', '+03:00'),
'%d.%m.%Y %H:%i:%s'
) AS cikis_zamani_tr
FROM oturum_kayitlari
WHERE giris_zamani >= NOW() - INTERVAL 24 HOUR
ORDER BY giris_zamani DESC;
Bu özellikle çok bölgeli sistemlerde veya sunucunuz farklı timezone’da çalışırken kritik önem taşıyor.
Sonuç
DATE_FORMAT, görünürde basit ama pratikte son derece güçlü bir fonksiyon. Raporlama sorgularından log analizine, kullanıcı arayüzü verilerinden veri taşıma operasyonlarına kadar onlarca farklı senaryoda işine yarıyor.
En önemli çıkarımları özetleyecek olursam: Fonksiyonu gösterim amaçlı kullan, filtreleme için native tarih karşılaştırmalarını tercih et. Sık kullanılan formatları view veya stored procedure içinde kapsülle. Büyük tablolarda generated columns ile performansı artır. Zaman dilimi farkındalığını ihmal etme, özellikle production sistemlerinde.
Veritabanı yönetiminde tarih formatlaması küçük bir detay gibi görünse de müşteriye sunulan raporun kalitesini, debug süreçlerinin hızını ve genel olarak veritabanıyla çalışma deneyimini doğrudan etkiliyor. DATE_FORMAT‘ı doğru ve verimli kullandığında, ham verideki o çirkin timestamp’leri anlamlı, okunabilir bilgilere dönüştürmek artık saniyeler alıyor.
