MySQL ve MariaDB’de IN Operatörü ile Çoklu Değer Kontrolü
Veritabanı sorgularında en sık karşılaşılan ihtiyaçlardan biri, bir sütunun birden fazla değerle eşleşip eşleşmediğini kontrol etmektir. Çoğu geliştirici ve sysadmin bu durumu uzun OR zincirleriyle çözmeye çalışır, ancak MariaDB ve MySQL’in sunduğu IN operatörü bu işi hem daha okunabilir hem de çok daha verimli hale getirir. Bu yazıda IN operatörünü gerçek dünya senaryolarıyla derinlemesine inceleyeceğiz.
IN Operatörü Nedir ve Neden Kullanmalısınız
IN operatörü, bir sütun değerinin belirtilen bir liste içindeki değerlerden herhangi biriyle eşleşip eşleşmediğini kontrol eder. En basit haliyle şöyle düşünebilirsiniz: bir kullanıcı rolünü kontrol etmek için “admin veya moderator veya editor mi?” diye sormak yerine, IN ('admin', 'moderator', 'editor') ile tek seferde sorguyu tamamlarsınız.
Peki neden OR yerine IN kullanmalısınız? Önce şu karşılaştırmaya bakın:
-- OR ile uzun ve okunması zor versiyon
SELECT * FROM kullanicilar
WHERE rol = 'admin' OR rol = 'moderator' OR rol = 'editor' OR rol = 'superuser';
-- IN ile temiz ve okunabilir versiyon
SELECT * FROM kullanicilar
WHERE rol IN ('admin', 'moderator', 'editor', 'superuser');
İkinci sorgu sadece daha temiz görünmekle kalmaz, MariaDB sorgu optimize edici tarafından da daha iyi işlenir. Özellikle indekslenmiş sütunlarda IN operatörü, optimize edici tarafından çok daha verimli bir yürütme planı oluşturmak için kullanılabilir.
Temel Sözdizimi ve Kullanım
IN operatörünün temel sözdizimi son derece basittir:
SELECT sutun_adi FROM tablo_adi WHERE sutun_adi IN (deger1, deger2, deger3, ...);
Gerçek bir senaryo üzerinden ilerleyelim. Bir e-ticaret sistemini yönettiğinizi düşünün. Sipariş durumlarını takip eden bir tablonuz var ve belirli durumlardaki siparişleri çekmeniz gerekiyor:
-- Bekleyen, işlemde olan ve kargoya verilen siparişleri getir
SELECT
siparis_id,
musteri_adi,
toplam_tutar,
durum,
olusturma_tarihi
FROM siparisler
WHERE durum IN ('beklemede', 'isleniyor', 'kargoda')
ORDER BY olusturma_tarihi DESC;
Bu sorgu, üç farklı durumdaki siparişleri tek seferde getirir. Eğer bunu OR ile yazsaydınız, hem daha uzun olurdu hem de ileride yeni bir durum eklemek daha zahmetli hale gelirdi.
NOT IN ile Hariç Tutma
IN operatörünün tersi olan NOT IN, belirli değerleri sonuç kümesinden çıkarmak için kullanılır. Sistem yöneticileri için bu özellikle yararlıdır. Örneğin, belirli sunucu gruplarını raporlardan hariç tutmak istiyorsunuz:
-- Test ve geliştirme ortamlarını hariç tut, sadece production sunucularını getir
SELECT
sunucu_adi,
ip_adresi,
isletim_sistemi,
son_yedek_tarihi
FROM sunucular
WHERE ortam NOT IN ('test', 'gelistirme', 'staging', 'demo')
AND aktif = 1
ORDER BY sunucu_adi;
NOT IN kullanırken dikkat etmeniz gereken kritik bir nokta var: NULL değerleri. Eğer listenizde veya kontrol ettiğiniz sütunda NULL değer varsa, beklenmedik sonuçlar alabilirsiniz. Bunu şöyle açıklayalım:
-- YANLIS: NULL iceren listede NOT IN beklenmedik davranir
SELECT * FROM urunler WHERE kategori_id NOT IN (1, 2, NULL);
-- Bu sorgu hic sonuc dondurmeyebilir!
-- DOGRU: NULL kontrolunu ayri yap
SELECT * FROM urunler
WHERE kategori_id NOT IN (1, 2)
AND kategori_id IS NOT NULL;
Bu, birçok deneyimli DBA’nın bile zaman zaman düştüğü bir tuzaktır. NULL değeriyle yapılan herhangi bir karşılaştırma UNKNOWN döndürür ve NOT IN operatörü listenizde tek bir NULL görse bile hiçbir satır döndürmeyebilir.
Alt Sorgu (Subquery) ile IN Kullanımı
IN operatörünün gerçek gücü, statik listeler yerine alt sorgularla birleştirildiğinde ortaya çıkar. Bu kombinasyon, karmaşık iş kurallarını son derece temiz bir şekilde ifade etmenizi sağlar.
Gerçek dünya senaryosu: Bir hosting firması için sunucu izleme sistemi kuruyorsunuz. Son 24 saat içinde alarm üreten sunucuların tam bilgisini çekmeniz gerekiyor:
-- Son 24 saat icinde alarm ureten sunuculari getir
SELECT
s.sunucu_id,
s.hostname,
s.ip_adresi,
s.musteri_id,
s.paket_turu
FROM sunucular s
WHERE s.sunucu_id IN (
SELECT DISTINCT sunucu_id
FROM alarm_kayitlari
WHERE alarm_zamani >= NOW() - INTERVAL 24 HOUR
AND alarm_seviyesi IN ('kritik', 'yuksek')
)
ORDER BY s.hostname;
Bu örnekte iç içe iki IN operatörü kullandık. Dış sorgu, iç sorgunun döndürdüğü sunucu ID’lerini kullanırken, iç sorgu da kendi içinde alarm seviyelerini filtrelemek için IN kullanıyor.
Bir başka yaygın senaryo: Belirli departmanlardaki çalışanların performans kayıtlarını çekmek istiyorsunuz:
-- IT ve Operasyon departmanlarindaki aktif calisanlarin performans kayitlari
SELECT
c.ad,
c.soyad,
c.email,
p.degerlendirme_tarihi,
p.puan,
p.degerlendirici
FROM calisanlar c
INNER JOIN performans_kayitlari p ON c.calisan_id = p.calisan_id
WHERE c.departman_id IN (
SELECT departman_id
FROM departmanlar
WHERE departman_adi IN ('Bilgi Teknolojileri', 'Operasyon', 'DevOps')
AND aktif = 1
)
AND c.durum = 'aktif'
AND p.yil = YEAR(CURDATE())
ORDER BY c.soyad, c.ad;
Sayısal Değerlerle IN Kullanımı
IN operatörü sadece metin değerleriyle değil, sayısal değerlerle de mükemmel çalışır. Bunu database yönetim senaryolarında sıkça kullanırsınız:
-- Belirli musteri ID'lerine ait tum veritabani kaynaklarini getir
SELECT
kaynak_tipi,
kaynak_adi,
disk_boyutu_gb,
aylik_maliyet,
musteri_id
FROM musteri_kaynaklari
WHERE musteri_id IN (1042, 1087, 1103, 1245, 1389)
ORDER BY musteri_id, kaynak_tipi;
Sayısal değerlerde tırnak işareti kullanmak zorunda değilsiniz ve bu da sorguyu biraz daha temiz gösterir. Ancak dikkat edin: tırnak işareti kullanırsanız MariaDB ve MySQL bu değerleri otomatik olarak dönüştürür, ama bu implicit type conversion performans sorunlarına yol açabilir. Her zaman doğru veri tipini kullanın.
-- Yuksek kaynak kullanan process ID'lerini sonlandir (ornek senaryo)
-- Once hangi process'lerin etkilenecegini gormek icin:
SELECT
id,
user,
host,
db,
command,
time,
state,
info
FROM information_schema.PROCESSLIST
WHERE id IN (
SELECT trx_mysql_thread_id
FROM information_schema.INNODB_TRX
WHERE trx_started < NOW() - INTERVAL 1 HOUR
)
AND command != 'Sleep';
Bu sorgu, bir saati aşkın süredir devam eden InnoDB transaction’larının process bilgilerini getirir. Gerçek bir production ortamında uzun süren transaction’ları tespit etmek için bu tür sorgular çok değerlidir.
Tarih ve Zaman Değerleriyle IN Kullanımı
Tarihlerle IN kullanımı biraz daha özeldir çünkü genellikle aralık sorguları için BETWEEN daha mantıklıdır. Ancak belirli tarihleri kontrol etmek istediğinizde IN işe yarar:
-- Belirli resmi tatil gunlerinde olusturulan siparisleri bul
SELECT
siparis_id,
musteri_adi,
toplam_tutar,
DATE(olusturma_tarihi) as siparis_tarihi
FROM siparisler
WHERE DATE(olusturma_tarihi) IN (
'2024-01-01', -- Yilbasi
'2024-04-23', -- Ulusal Egemenlik
'2024-05-01', -- Isci Bayrami
'2024-05-19', -- Ataturk'u Anma
'2024-08-30', -- Zafer Bayrami
'2024-10-29' -- Cumhuriyet Bayrami
)
ORDER BY olusturma_tarihi;
Güne göre filtreleme yaparken DAYOFWEEK() fonksiyonuyla kombinasyon da çok kullanışlıdır:
-- Hafta ici (Pazartesi=2, Sali=3, Carsamba=4, Persembe=5, Cuma=6) yapilan islemleri getir
SELECT
islem_id,
kullanici_adi,
islem_tipi,
islem_zamani
FROM sistem_logları
WHERE DAYOFWEEK(islem_zamani) IN (2, 3, 4, 5, 6)
AND islem_tipi IN ('giris', 'kritik_degisiklik', 'yetki_degisikligi')
AND islem_zamani >= DATE_SUB(NOW(), INTERVAL 30 DAY)
ORDER BY islem_zamani DESC;
Performans Konusunda Dikkat Edilmesi Gerekenler
IN operatörü verimli olsa da, yanlış kullanıldığında performans sorunlarına yol açabilir. Sysadmin olarak bilmeniz gereken birkaç kritik nokta var.
İndeks kullanımı: IN operatörü, indekslenmiş sütunlarda etkili şekilde indeksi kullanır. EXPLAIN komutuyla bunu doğrulayabilirsiniz:
-- Sorgu yurütme planini incele
EXPLAIN SELECT * FROM siparisler
WHERE musteri_id IN (101, 205, 307, 412)
AND durum IN ('beklemede', 'isleniyor');
-- Sonucta key sutununda indeks gorunmuyorsa performans sorunu var demektir
-- Gerekli durumda bileşik indeks ekle:
ALTER TABLE siparisler
ADD INDEX idx_musteri_durum (musteri_id, durum);
Liste boyutu: IN listesi çok büyüdüğünde (yüzlerce veya binlerce değer), performans ciddi şekilde düşebilir. Bu durumda birkaç alternatif yaklaşım düşünülmelidir:
- Değerleri geçici bir tabloya atın ve
JOINkullanın - Alt sorgu kullanın
- Büyük listeleri parçalara bölün ve uygulama katmanında birleştirin
-- Binlerce ID icin gecici tablo yaklasimi
CREATE TEMPORARY TABLE hedef_musteri_idler (musteri_id INT PRIMARY KEY);
INSERT INTO hedef_musteri_idler VALUES (101), (205), (307); -- ... tum ID'ler
SELECT m.*
FROM musteriler m
INNER JOIN hedef_musteri_idler h ON m.musteri_id = h.musteri_id
WHERE m.aktif = 1;
DROP TEMPORARY TABLE hedef_musteri_idler;
Alt sorgu yerine JOIN tercih edin: Alt sorgu ile IN kullanımı bazen optimize edici tarafından tam olarak optimize edilemeyebilir. Çok büyük veri setlerinde JOIN daha iyi performans verebilir:
-- IN ile alt sorgu versiyonu (buyuk veride yavas olabilir)
SELECT * FROM siparisler
WHERE musteri_id IN (SELECT musteri_id FROM musteriler WHERE sehir = 'Istanbul');
-- JOIN versiyonu (genellikle daha hizli)
SELECT s.*
FROM siparisler s
INNER JOIN musteriler m ON s.musteri_id = m.musteri_id
WHERE m.sehir = 'Istanbul';
Gerçek Dünya: Log Analizi ve Güvenlik Sorguları
Sysadmin olarak en sık ihtiyaç duyduğunuz senaryolardan biri güvenlik loglarının analizidir. Yetkisiz erişim denemeleri, belirli IP adreslerinden gelen trafik veya şüpheli kullanıcı aktivitelerini IN operatörüyle çok hızlıca tespit edebilirsiniz:
-- Kara listedeki IP'lerden gelen basarili girisler (ciddi guvenlik alarmi!)
SELECT
l.log_id,
l.kullanici_adi,
l.ip_adresi,
l.giris_zamani,
l.session_id,
l.tarayici_bilgisi
FROM giris_loglari l
WHERE l.ip_adresi IN (
SELECT ip_adresi FROM guvenlik_kara_listesi
WHERE aktif = 1
AND ekleme_tarihi >= DATE_SUB(NOW(), INTERVAL 90 DAY)
)
AND l.giris_sonucu = 'basarili'
AND l.giris_zamani >= DATE_SUB(NOW(), INTERVAL 7 DAY)
ORDER BY l.giris_zamani DESC;
Yedekleme sistemi yönetimi için de IN operatörü çok kullanışlıdır. Hangi sunucuların yedeğinin alınmadığını veya hangi yedeklerin doğrulama sürecinden geçmediğini kontrol etmek için:
-- Yedegi alinmamis veya dogrulama basarisiz olan kritik sunucular
SELECT
s.hostname,
s.ip_adresi,
s.kritiklik_seviyesi,
MAX(y.yedek_tarihi) as son_yedek,
MAX(y.dogrulama_durumu) as son_dogrulama
FROM sunucular s
LEFT JOIN yedekler y ON s.sunucu_id = y.sunucu_id
AND y.yedek_tarihi >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
WHERE s.kritiklik_seviyesi IN ('kritik', 'yuksek', 'orta')
AND s.aktif = 1
GROUP BY s.sunucu_id, s.hostname, s.ip_adresi, s.kritiklik_seviyesi
HAVING MAX(y.yedek_tarihi) IS NULL
OR MAX(y.dogrulama_durumu) IN ('basarisiz', 'belirsiz', 'dogrulanmadi')
ORDER BY
FIELD(s.kritiklik_seviyesi, 'kritik', 'yuksek', 'orta'),
s.hostname;
Bu sorgu, HAVING clause içinde de IN operatörünü kullanarak gruplama sonrası filtreleme yapıyor. FIELD() fonksiyonu ise kritiklik seviyesine göre özel sıralama sağlıyor.
MariaDB ve MySQL Arasındaki Farklar
Genel olarak IN operatörü her iki sistemde de aynı şekilde çalışır, ancak bazı ince farklar vardır:
- MariaDB 10.3+ ve MySQL 8.0+: Her iki sistem de
INile alt sorgulardaLATERALjoin optimizasyonlarını daha iyi destekler - MariaDB’nin
INoptimizasyonu: MariaDB, bazı durumlardaINlistelerinirangetipinde tarama yerineeq_refolarak optimize edebilir, bu da performans avantajı sağlar - MySQL 8.0 hash set optimizasyonu: MySQL 8.0 ile birlikte büyük
INlisteleri için hash set kullanımı iyileştirilmiştir
Hangi sistemi kullandığınızı ve versiyon bilgisini öğrenmek için:
-- Versiyon ve sistem bilgisi
SELECT VERSION(), @@version_comment;
-- Ya da terminal uzerinden:
mysql --version
mariadb --version
IN Operatörünü Stored Procedure’larda Kullanmak
Tekrar eden sorgu ihtiyaçları için stored procedure içinde IN operatörünü dinamik olarak kullanmak isteyebilirsiniz. MariaDB ve MySQL’de dinamik IN listesi oluşturmak için PREPARE ve EXECUTE kullanabilirsiniz:
-- Dinamik IN listesiyle stored procedure ornegi
DELIMITER //
CREATE PROCEDURE GetSunucuDurumu(IN sunucu_listesi TEXT)
BEGIN
SET @sorgu = CONCAT(
'SELECT hostname, ip_adresi, cpu_kullanim, ram_kullanim, disk_kullanim ',
'FROM sunucu_metrikleri ',
'WHERE hostname IN (', sunucu_listesi, ') ',
'AND olcum_zamani >= NOW() - INTERVAL 5 MINUTE ',
'ORDER BY cpu_kullanim DESC'
);
PREPARE stmt FROM @sorgu;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END //
DELIMITER ;
-- Kullanimi:
CALL GetSunucuDurumu("'web01', 'web02', 'db01', 'cache01'");
Not: Dinamik SQL oluştururken SQL injection riskine karşı her zaman dikkatli olun. Kullanıcıdan gelen değerleri doğrudan SQL’e eklemek yerine, mümkün olduğunda parametreli sorgular veya hazırlanmış ifadeler kullanın.
Pratik İpuçları ve Sık Yapılan Hatalar
IN operatörünü kullanırken dikkat etmeniz gereken bazı önemli noktaları özetleyelim:
- Boş liste kullanmayın:
WHERE id IN ()gibi boş bir liste hata verir. Uygulamanızın boş liste göndermediğinden emin olun veya bu durumu kontrol edin - Veri tipi tutarlılığı: Sütunun veri tipiyle liste değerlerinin tipini eşleştirin.
VARCHARsütun için sayısal değerler implicit dönüşüme yol açar - Çok büyük listelerden kaçının: 1000’den fazla değer içeren
INlisteleri performans sorunlarına yol açabilir. Bu durumda geçici tablo veyaJOINyaklaşımı daha verimlidir - NULL farkındalığı:
NOT INkullanırken sütundaki ve listedeki NULL değerlerine dikkat edin - İndeks kontrolü: Sık kullandığınız
INsorgularındaEXPLAINile indeks kullanımını doğrulayın
-- Boş liste kontrolü uygulama katmanında yapilmali,
-- ama SQL tarafında da koruma ekleyebilirsiniz:
SELECT * FROM urunler
WHERE kategori_id IN (SELECT kategori_id FROM kategoriler WHERE aktif = 1)
-- Alt sorgu en az bir sonuc dondurmezse, dis sorgu da sonuc dondurmez
-- Bu bos liste sorununu otomatik olarak cozer
Sonuç
IN operatörü, MariaDB ve MySQL dünyasında en kullanışlı ve çok yönlü araçlardan biridir. Basit çoklu değer kontrolünden karmaşık alt sorgulara, güvenlik analizlerinden sistem izlemeye kadar pek çok senaryoda hayatınızı kolaylaştırır. Doğru kullanıldığında hem sorgularınızı daha okunabilir hem de bakımını daha kolay hale getirir.
Ancak her araçta olduğu gibi, IN operatörünü de körü körüne kullanmak yerine performans etkilerini göz önünde bulundurarak uygulamalısınız. EXPLAIN planlarını düzenli kontrol etmek, indeks stratejinizi doğru belirlemek ve çok büyük listeler için alternatif yaklaşımlar düşünmek sizi gerçek anlamda üst düzey bir veritabanı yöneticisi yapar.
Bir sonraki sorgularınızda OR zinciri yazmak yerine IN operatörünü deneyin. Farkı hemen göreceksiniz.
