MySQL DATE() ve TIME() Fonksiyonlarını Doğru Kullanmak

Selamlar, bu yazımda MySQL'in DATE() ve TIME() fonksiyonlarına bakacağız. Konu basit gibi duruyor; bir DATETIME kolonunun tarih kısmını ya da saat kısmını ayırıyoruz, hepsi bu. Ama gel gör ki aynı fonksiyonu WHERE koşulunda kullanınca indeksinizi anında devre dışı bırakabiliyorsunuz. Açıkçası ilk öğrendiğimde ben de planner'in neden hep full scan yaptığını anlayamamıştım. Hadi başlayalım.

DATE() ve TIME() ne yapar?

İkisi de bir DATETIME ya da TIMESTAMP ifadesinden parça çıkaran fonksiyonlar. DATE(expr) size YYYY-MM-DD formatında bir DATE döner, TIME(expr) ise HH:MM:SS formatında bir TIME. Girdiniz NULL ise her ikisi de NULL döner, sürpriz yok.

Hadi minimum bir örnek görelim:

SELECT DATE('2026-05-27 14:30:45');
-- '2026-05-27'

SELECT TIME('2026-05-27 14:30:45');
-- '14:30:45'

SELECT DATE(NOW()), TIME(NOW());
-- bugünün tarihi ve şu anki saat

Yani fonksiyonlar yalın halde gayet iyi çalışıyor. Asıl mesele bunları bir tablonun üstünde kullanmaya başladığımızda ortaya çıkıyor.

Tarihe göre filtrelemenin klasik tuzağı

Şöyle bir tablo düşünelim:

CREATE TABLE events (
    id INT AUTO_INCREMENT PRIMARY KEY,
    event_name VARCHAR(100),
    event_time DATETIME,
    INDEX idx_event_time (event_time)
);

event_time kolonu üstünde indeks var. Şimdi sıkça yazılan ama yanlış olan sorgu:

SELECT id, event_name
FROM events
WHERE DATE(event_time) = '2026-05-27';

Mantıken doğru sonucu döner, ama DATE(event_time) ifadesi kolonu fonksiyonun içine sokuyor. MySQL bu durumda indeksi kullanamaz; çünkü her satır için fonksiyonu çalıştırıp sonuca bakması gerek. Buna non-sargable koşul deniyor: indeksin nimetlerinden faydalanamayan koşul.

Doğrusu, kolonu çıplak bırakıp aralık karşılaştırması yapmak:

SELECT id, event_name
FROM events
WHERE event_time >= '2026-05-27 00:00:00'
  AND event_time <  '2026-05-28 00:00:00';

Bu sorgu indeksi kullanır, milyonlarca satırlık tabloda bile saniye altı çalışır. Bence küçük tablolarda farkı hissetmeseniz de bu alışkanlığı en baştan edinmekte fayda var; tablo büyüdüğünde geri dönüp tüm sorguları taramak zorunda kalmıyorsunuz.

EXPLAIN ile doğrulamak isterseniz:

EXPLAIN SELECT id FROM events
WHERE event_time >= '2026-05-27 00:00:00'
  AND event_time <  '2026-05-28 00:00:00';

type kolonunda range, key kolonunda idx_event_time görüyorsanız işiniz tamam. DATE(event_time) = ... versiyonunda ise büyük ihtimalle type: ALL göreceksiniz, yani full scan.

Generated column ile pratik bir kaçış yolu

Diyelim ki uygulama kodu çoktan DATE(event_time) = ... yazıyor ve hepsini tek tek değiştiremezsiniz. Bu durumda generated column iyi bir kurtarıcı:

ALTER TABLE events
    ADD COLUMN event_date DATE
    GENERATED ALWAYS AS (DATE(event_time)) STORED,
    ADD INDEX idx_event_date (event_date);

Artık event_date kolonu üstünde indeks var ve şu sorgu doğrudan onu kullanıyor:

SELECT id, event_name
FROM events
WHERE event_date = '2026-05-27';

STORED yerine VIRTUAL da seçebilirsiniz; aralarındaki fark şu: STORED yer kaplar ama okuma hızlı, VIRTUAL yer kaplamaz ama her okumada hesaplanır. İndeksleyeceksiniz zaten, ben genelde STORED tercih ediyorum.

DATE_FORMAT ve EXTRACT karşılaştırması

DATE_FORMAT(event_time, '%Y-%m-%d') da tarih kısmını verir gibi görünüyor, ama bir farkla: dönen değer VARCHAR. Yani string karşılaştırma yapıyorsunuz, indeks yine yardımcı olmuyor ve karşılaştırma da semantik olarak DATE değil text karşılaştırması.

EXTRACT(YEAR FROM event_time) gibi kullanımlar ise belirli bir parçayı (yıl, ay, gün, saat) almak için. Tek bir parça istiyorsanız EXTRACT ya da YEAR(), MONTH(), HOUR() daha okunaklı:

SELECT
    EXTRACT(YEAR  FROM event_time) AS yil,
    EXTRACT(MONTH FROM event_time) AS ay,
    HOUR(event_time)               AS saat
FROM events;

Ama bunların hepsi de WHERE içinde kolona uygulanırsa aynı sargable problemini doğurur. Kural sabit: kolonu fonksiyonun içine sokarsanız indeksi kaybedersiniz.

Sık karşılaşılan tuzaklar

  • WHERE DATE(col) = ... yazmak: En sık yapılan hata. Aralık karşılaştırması ile değiştirin ya da generated column ekleyin.
  • DATE_FORMAT ile filtrelemek: String dönüyor, indeks devre dışı, üstüne tip dönüşümü maliyeti var. Sadece görüntüleme için kullanın.
  • Saat dilimi karışıklığı: DATETIME ile TIMESTAMP arasındaki en büyük fark TZ davranışı. TIMESTAMP UTC'ye çevirip saklar, DATETIME aynen saklar. Filtrelerken hangisinde olduğunuzu bilin.
  • BETWEEN ile gün sonu hatası: event_time BETWEEN '2026-05-27' AND '2026-05-28' gece yarısına denk gelen kayıtları atlar. Yarı-açık aralık (>= ... AND < ...) daha güvenli.

Kapanış

DATE() ve TIME() fonksiyonları parça çıkarmak için temiz araçlar; ama büyük tablolarda WHERE koşuluna soktuğunuz an performansınızı düşürürler. Bence her MySQL geliştiricisinin sargable koşul kavramını alışkanlık haline getirmesi lazım, gerisi zamanla oturuyor. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.