MariaDB ve MySQL’de MOD Fonksiyonu ile Kalan Bulma ve Tek Çift Sayı Kontrolü

Veritabanı yönetiminde matematiksel işlemler düşündüğünüzden çok daha fazla işe yarar. Özellikle MOD operatörü yani kalan bulma işlemi, günlük sorgu yazarken karşınıza çıkan ama çoğu zaman göz ardı edilen güçlü bir araçtır. Tek-çift sayı kontrolünden tutun da döngüsel veri dağıtımına, periyodik raporlamadan sıra numarası bazlı filtrelemeye kadar pek çok senaryoda MOD’u kullanabilirsiniz. Bu yazıda MariaDB ve MySQL üzerinde MOD fonksiyonunu ve % operatörünü tüm pratik yönleriyle ele alacağız.

MOD Nedir ve Nasıl Çalışır?

MOD, bir sayının başka bir sayıya bölünmesinden kalan değeri döndürür. Matematikteki modulo işleminin SQL karşılığıdır. MariaDB ve MySQL’de iki farklı şekilde kullanabilirsiniz.

Birinci yöntem: MOD(sayi, bolen) fonksiyon sözdizimi İkinci yöntem: sayi % bolen operatör sözdizimi

Her ikisi de aynı sonucu üretir, hangisini kullanacağınız tamamen tercihe bağlıdır. Ben genellikle okunabilirlik açısından fonksiyon sözdizimini tercih ederim, özellikle karmaşık sorgularda hangi işlemin ne amaçla yapıldığı daha net anlaşılıyor.

-- MOD fonksiyonu ile temel kullanım
SELECT MOD(10, 3);   -- Sonuç: 1
SELECT MOD(15, 5);   -- Sonuç: 0
SELECT MOD(7, 2);    -- Sonuç: 1

-- % operatörü ile aynı işlem
SELECT 10 % 3;       -- Sonuç: 1
SELECT 15 % 5;       -- Sonuç: 0
SELECT 7 % 2;        -- Sonuç: 1

Sonuçlardan da görüldüğü üzere, bölme işleminden kalan sıfır çıkıyorsa sayı tam bölünüyor demektir. Bu basit mantık, tek-çift kontrolünün temelini oluşturuyor.

Tek ve Çift Sayı Kontrolü

En temel kullanım senaryosu tek-çift kontrolüdür. Herhangi bir sayıyı 2’ye böldüğünüzde kalan 0 ise çift, 1 ise tek sayıdır.

-- Tek çift kontrolü
SELECT 
    sayi,
    CASE 
        WHEN MOD(sayi, 2) = 0 THEN 'Çift'
        ELSE 'Tek'
    END AS sayi_tipi
FROM (
    SELECT 1 AS sayi UNION ALL
    SELECT 2 UNION ALL
    SELECT 3 UNION ALL
    SELECT 4 UNION ALL
    SELECT 5
) AS sayilar;

Bu sorguyu çalıştırdığınızda her sayının yanında “Tek” veya “Çift” etiketini göreceksiniz. Ancak gerçek dünyada bu mantığı tablolarınızdaki kayıtlara uygulamanız gerekiyor.

Gerçek Tablo Üzerinde Tek-Çift Filtreleme

Diyelim ki bir e-ticaret sisteminde sipariş tablonuz var ve belirli bir A/B testi için siparişleri ikiye ayırmanız gerekiyor. Tek numaralı siparişler A grubuna, çift numaralı siparişler B grubuna gidecek.

-- Örnek tablo oluşturalım
CREATE TABLE siparisler (
    siparis_id INT AUTO_INCREMENT PRIMARY KEY,
    musteri_adi VARCHAR(100),
    toplam_tutar DECIMAL(10,2),
    siparis_tarihi DATETIME DEFAULT NOW()
);

-- Test verisi ekleyelim
INSERT INTO siparisler (musteri_adi, toplam_tutar) VALUES
('Ahmet Yılmaz', 150.00),
('Fatma Kaya', 230.50),
('Mehmet Demir', 89.90),
('Ayşe Çelik', 445.00),
('Hasan Şahin', 178.75),
('Zeynep Arslan', 320.00);

-- A/B test grubu belirleme
SELECT 
    siparis_id,
    musteri_adi,
    toplam_tutar,
    CASE 
        WHEN MOD(siparis_id, 2) = 0 THEN 'B Grubu'
        ELSE 'A Grubu'
    END AS test_grubu
FROM siparisler
ORDER BY siparis_id;

Bu yaklaşım özellikle büyük tablolarda yük dengeleme veya test senaryoları oluştururken çok işe yarar. Herhangi bir ek sütun eklemenize gerek kalmadan mevcut ID üzerinden gruplama yapabiliyorsunuz.

Periyodik Kayıt Seçimi

MOD’un en güçlü kullanım alanlarından biri periyodik kayıt seçimidir. Bir log tablosunda her 10 kayıttan birini almak istiyorsanız ya da belirli aralıklarla örnekleme yapmanız gerekiyorsa MOD tam da bu iş için biçilmiş kaftan.

-- Her 5 kayıttan birini seç (örnekleme)
SELECT 
    log_id,
    islem_tipi,
    kullanici_id,
    created_at
FROM sistem_loglari
WHERE MOD(log_id, 5) = 0
ORDER BY log_id;

-- Her 10 kayıttan birini seç
SELECT 
    log_id,
    islem_tipi,
    created_at
FROM sistem_loglari
WHERE MOD(log_id, 10) = 1
ORDER BY log_id;

Burada dikkat etmeniz gereken nokta: = 0 kullandığınızda 5, 10, 15 gibi 5’in tam katlarını alırsınız. = 1 kullandığınızda ise 1, 11, 21 gibi 5’e bölündüğünde kalanı 1 olan kayıtları alırsınız. Hangi kaydı başlangıç noktası olarak almak istediğinize göre bu değeri ayarlayabilirsiniz.

Veri Dağıtımı ve Yük Dengeleme Senaryosu

Gerçek hayatta sık karşılaştığım bir senaryo şu: Bir görev kuyruğunuz var ve bu görevleri birden fazla işçi sunucusuna (worker) dağıtmanız gerekiyor. MOD burada mükemmel bir hash mekanizması görevi görüyor.

-- Görev tablosu
CREATE TABLE gorevler (
    gorev_id INT AUTO_INCREMENT PRIMARY KEY,
    gorev_adi VARCHAR(200),
    oncelik TINYINT DEFAULT 1,
    durum ENUM('bekliyor', 'isleniyor', 'tamamlandi') DEFAULT 'bekliyor',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 4 worker sunucu için görev dağıtımı
-- Worker sayısını değiştirmek için sadece 4 rakamını değiştirmeniz yeterli
SELECT 
    gorev_id,
    gorev_adi,
    oncelik,
    MOD(gorev_id, 4) AS worker_numarasi,
    CONCAT('Worker-', MOD(gorev_id, 4) + 1) AS atanan_worker
FROM gorevler
WHERE durum = 'bekliyor'
ORDER BY oncelik DESC, gorev_id ASC;

-- Belirli bir worker'a düşen görevleri çek (örneğin Worker-2 için)
SELECT 
    gorev_id,
    gorev_adi,
    oncelik
FROM gorevler
WHERE 
    durum = 'bekliyor'
    AND MOD(gorev_id, 4) = 1  -- 0'dan başladığı için Worker-2 = MOD 1
ORDER BY oncelik DESC;

Bu yaklaşımı uygulama katmanında da kullanabilirsiniz. Uygulama sunucunuz kendi worker numarasını biliyor ve buna göre veritabanından sadece kendi payına düşen görevleri çekiyor. Güzel bir tarafı da worker sayısını artırmak istediğinizde sadece bölen sayıyı değiştirmeniz yeterli.

Tarih Bazlı MOD Kullanımı

MOD’u tarih ve zaman değerleriyle de kullanabilirsiniz. Özellikle haftalık veya aylık döngüsel analizlerde işe yarar.

-- Haftanın hangi günü olduğunu bulma (0=Pazartesi, 6=Pazar)
SELECT 
    siparis_id,
    siparis_tarihi,
    DAYOFWEEK(siparis_tarihi) AS gun_no,
    CASE MOD(DAYOFWEEK(siparis_tarihi), 7)
        WHEN 0 THEN 'Pazar'
        WHEN 1 THEN 'Pazartesi'
        WHEN 2 'Salı'
        WHEN 3 THEN 'Çarşamba'
        WHEN 4 THEN 'Perşembe'
        WHEN 5 THEN 'Cuma'
        WHEN 6 THEN 'Cumartesi'
    END AS gun_adi
FROM siparisler;

-- Ayın çift mi tek mi günü olduğunu kontrol et
SELECT 
    siparis_id,
    siparis_tarihi,
    DAY(siparis_tarihi) AS ayin_gunu,
    CASE 
        WHEN MOD(DAY(siparis_tarihi), 2) = 0 THEN 'Çift Gün'
        ELSE 'Tek Gün'
    END AS gun_tipi
FROM siparisler
WHERE MONTH(siparis_tarihi) = MONTH(CURDATE())
  AND YEAR(siparis_tarihi) = YEAR(CURDATE());

Bu tür sorgular özellikle raporlama sistemlerinde belirli gün kalıplarına göre veri analizi yaparken kullanışlı oluyor. Örneğin hafta sonları ile hafta içi performans karşılaştırması yaparken DAYOFWEEK ile MOD kombinasyonunu sıkça kullanıyorum.

Sayfalama (Pagination) İçin MOD Kullanımı

Veritabanı seviyesinde bazı özel sayfalama ihtiyaçlarında MOD kullanılabilir. Klasik LIMIT/OFFSET yerine ID tabanlı MOD ile belirli sayfaları çekmek bazen daha performanslı olabiliyor.

-- Ürün tablosu için örnek
CREATE TABLE urunler (
    urun_id INT AUTO_INCREMENT PRIMARY KEY,
    urun_adi VARCHAR(200),
    kategori VARCHAR(100),
    fiyat DECIMAL(10,2),
    stok_miktari INT
);

-- Her sayfada 10 kayıt olmak üzere 3. sayfayı getir
-- 3. sayfa = MOD değeri 0,1,2,3,4,5,6,7,8,9 olan kayıtlardan 
-- ID/10 değerinin 2 olduğu kayıtlar (0 indeksli)

-- Daha pratik yaklaşım: Stok durumuna göre gruplama
SELECT 
    urun_id,
    urun_adi,
    stok_miktari,
    CASE 
        WHEN MOD(urun_id, 3) = 0 THEN 'Depo-A'
        WHEN MOD(urun_id, 3) = 1 THEN 'Depo-B'
        WHEN MOD(urun_id, 3) = 2 THEN 'Depo-C'
    END AS depo_konumu
FROM urunler
WHERE stok_miktari > 0
ORDER BY urun_id;

-- Belirli depodaki ürünleri çek
SELECT 
    urun_id,
    urun_adi,
    fiyat,
    stok_miktari
FROM urunler
WHERE MOD(urun_id, 3) = 0  -- Sadece Depo-A ürünleri
  AND stok_miktari > 0;

Toplu Veri Güncelleme Senaryosu

Büyük tabloları toplu güncellerken MOD ile parçalara bölerek işlem yapabilirsiniz. Bu özellikle production ortamında tabloya kilitlenme (lock) oluşturmadan güncelleme yapmanız gerektiğinde çok işe yarıyor.

-- Büyük bir tabloda parçalı güncelleme
-- Önce toplam kayıt sayısını öğren
SELECT COUNT(*) FROM urunler;

-- Ardından 5 parçaya bölerek güncelle
-- 1. parça
UPDATE urunler 
SET fiyat = fiyat * 1.10
WHERE MOD(urun_id, 5) = 0
  AND kategori = 'Elektronik';

-- 2. parça (aralarında birkaç saniye bekle)
UPDATE urunler 
SET fiyat = fiyat * 1.10
WHERE MOD(urun_id, 5) = 1
  AND kategori = 'Elektronik';

-- 3. parça
UPDATE urunler 
SET fiyat = fiyat * 1.10
WHERE MOD(urun_id, 5) = 2
  AND kategori = 'Elektronik';

-- Kaç kayıt etkileneceğini önceden görmek için SELECT ile test et
SELECT COUNT(*) 
FROM urunler 
WHERE MOD(urun_id, 5) = 0 
  AND kategori = 'Elektronik';

Bu yöntemi büyük tablolarda (milyonlarca kayıt) fiyat güncelleme, durum değişikliği veya toplu silme işlemlerinde kullanıyorum. Her parçayı işledikten sonra birkaç saniye beklemek, diğer sorguların da veritabanına erişmesine imkan tanıyor ve ani yük artışlarının önüne geçiyor.

Negatif Sayılarda MOD Davranışı

Bunu mutlaka bilmeniz gereken önemli bir nokta: MariaDB ve MySQL’de negatif sayılarda MOD sonucu negatif çıkabilir. Bu bazı durumlarda beklenmedik sonuçlara yol açabilir.

-- Negatif sayılarda MOD davranışı
SELECT MOD(-7, 3);    -- Sonuç: -1 (MySQL/MariaDB'de)
SELECT MOD(7, -3);    -- Sonuç: 1
SELECT MOD(-7, -3);   -- Sonuç: -1

-- Her zaman pozitif sonuç almak için ABS kullanın
SELECT ABS(MOD(-7, 3));   -- Sonuç: 1

-- Ya da FLOOR ve matematiksel dönüşüm
SELECT -7 - 3 * FLOOR(-7/3);  -- Gerçek matematiksel modulo

-- Pratik uygulama: Müşteri ID negatif olabilecek durumlarda
SELECT 
    musteri_id,
    musteri_adi,
    ABS(MOD(musteri_id, 4)) AS grup_no
FROM musteriler;

Gerçek hayatta tablolarınızdaki ID değerleri genellikle pozitif olduğu için bu sorunla pek karşılaşmazsınız. Ama hesaplanan değerler üzerinde MOD kullanıyorsanız ya da dışarıdan gelen verilerde negatif sayı ihtimali varsa ABS() ile sarmalamayı alışkanlık haline getirin.

Performans Notları ve İndeks Kullanımı

MOD fonksiyonunu WHERE koşulunda kullandığınızda önemli bir performans sorunuyla karşılaşabilirsiniz: fonksiyon bazlı filtreleme indeksleri kullanamaz.

-- KÖTÜ: Tam tablo taraması yapar, indeks kullanmaz
SELECT * FROM siparisler
WHERE MOD(siparis_id, 2) = 0;

-- Bu sorgunun EXPLAIN çıktısını kontrol edin
EXPLAIN SELECT * FROM siparisler
WHERE MOD(siparis_id, 2) = 0;

-- Performansı artırmak için hesaplanan sütun (MariaDB 5.2+)
ALTER TABLE siparisler 
ADD COLUMN tek_cift TINYINT GENERATED ALWAYS AS (MOD(siparis_id, 2)) STORED;

-- Sonra bu sütun üzerine indeks ekle
CREATE INDEX idx_tek_cift ON siparisler(tek_cift);

-- Artık indeks kullanan sorgu
SELECT * FROM siparisler WHERE tek_cift = 0;

-- Alternatif: Sık kullanılan MOD değerlerini ayrı sütunda tut
ALTER TABLE gorevler 
ADD COLUMN worker_id TINYINT DEFAULT NULL;

UPDATE gorevler SET worker_id = MOD(gorev_id, 4);
CREATE INDEX idx_worker_id ON gorevler(worker_id);

Küçük tablolarda (birkaç bin kayıt) MOD performans sorunu yaratmaz. Ama milyonlarca kayıtlı tablolarda WHERE koşulunda MOD kullanmak full table scan’e neden olur ve sorgu hızı dramatik biçimde düşer. Bu durumda ya hesaplanmış sütun kullanın ya da MOD değerini ayrı bir sütunda saklayın.

Büyük Tablolarda MOD Kullanımı İçin Pratik Öneri

-- Büyük tablolarda BETWEEN ile MOD'u birleştirin
-- Önce ID aralığını daraltın, sonra MOD uygulayın
SELECT 
    siparis_id,
    musteri_adi,
    toplam_tutar
FROM siparisler
WHERE 
    siparis_id BETWEEN 1000 AND 2000   -- İndeks kullanır
    AND MOD(siparis_id, 2) = 0;         -- Daraltılmış set üzerinde çalışır

-- Partition kullanılıyorsa partition pruning'i aktifleştir
-- ve MOD'u partition key üzerinde kullan
EXPLAIN PARTITIONS 
SELECT * FROM buyuk_tablo 
WHERE MOD(kayit_id, 10) = 3;

Özet: MOD Kullanım Senaryoları

MOD’un ne zaman kullanacağınıza dair hızlı bir rehber:

  • Tek-çift kontrolü: MOD(id, 2) = 0 çift, MOD(id, 2) = 1 tek
  • N’de bir örnekleme: MOD(id, N) = 0 her N’inci kaydı alır
  • Gruplara bölme: MOD(id, grup_sayisi) ile 0’dan başlayan grup numarası üretir
  • Yük dengeleme: Worker sayısı kadar bölen kullanın, her worker kendi MOD değerini filtreler
  • Toplu güncelleme: Büyük tabloları parçalara bölmek için MOD ile döngü kur
  • Periyodik raporlama: Tarih alanlarına MOD uygulayarak döngüsel kalıpları bul
  • Negatif sayı koruması: Her zaman ABS(MOD(...)) kullanmayı düşünün

Sonuç

MOD operatörü ve fonksiyonu, görünürde basit bir matematik işlemi gibi dursa da doğru kullanıldığında veri dağıtımı, test senaryoları, örnekleme ve toplu işlemler gibi kritik veritabanı görevlerini zarif biçimde çözüyor. Özellikle büyük ölçekli sistemlerde parçalı güncelleme stratejisi ve worker bazlı iş dağıtımı için MOD vazgeçilmez bir araç haline geliyor.

Performans konusunu es geçmeyin. WHERE koşulunda MOD kullanırken her zaman EXPLAIN ile sorgu planını kontrol edin. Full table scan görüyorsanız hesaplanmış sütun ya da ayrı bir indekslenmiş sütun çözümü üretin. Milyonluk tablolarda bu ayrım sorgu sürenizi saniyelerden milisaniyelere indirebilir.

Son olarak, MariaDB ve MySQL arasındaki MOD davranışı büyük ölçüde aynı. Negatif sayı işlemlerinde her iki motor da aynı davranışı sergiliyor, dolayısıyla bu konuda platform geçişi yaparken ek bir dikkat gerektirmiyor.

Bir yanıt yazın

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