CHECK Constraint, Her Satırın Uyması Gereken Kuraldır
SQLite'ta CHECK constraint, bir tabloya iliştirdiğiniz mantıksal (boolean) bir ifadedir. SQLite bu ifadeyi her INSERT ve UPDATE işleminde değerlendirir; ifade false dönerse işlem reddedilir. Yani "fiyat negatif olamaz" ya da "status şu üç değerden biri olmak zorunda" gibi iş kurallarını doğrudan şemanın içine gömmenin pratik bir yoludur.
İlk iki satır sorunsuz ekleniyor. Üçüncüsü ise CHECK constraint failed hatası fırlatıyor ve reddediliyor — tabloya hiç ulaşmıyor bile. Bu kısıtlama, kuralı her yazıcı için uyguluyor: ister uygulamanız olsun, ister bir migration script'i, ister CLI'dan kurcalayan biri.
Sütun seviyesi mi, tablo seviyesi mi?
CHECK kısıtlamasını iki farklı yere yazabilirsiniz: bir sütun tanımının hemen ardına (sütun seviyesi) ya da tüm sütunlardan sonra (tablo seviyesi). Davranışları aynıdır; fark sadece hangisinin daha okunabilir göründüğüyle ilgilidir.
İlk rezervasyon sorunsuz eklenir. İkincisi ise başarısız olur — bitiş tarihi başlangıçtan önce. Tek sütunluk kuralları sütun seviyesinde yazmak daha okunaklı; iki ya da daha fazla sütunu karşılaştıran kurallar ise tablo seviyesinde daha anlamlı duruyor.
Değerleri Belirli Bir Listeyle Sınırlamak
Sık karşılaşılan kullanımlardan biri, bir sütunun yalnızca sabit bir değer kümesindeki değerleri almasını zorunlu kılmaktır. SQLite'ta yerleşik bir enum tipi olmadığı için bu iş CHECK ... IN (...) kalıbıyla halledilir:
Üçüncü satır hata verir; çünkü 'pending' izin verilen değerler arasında yok. İlerleyen bir aşamada yeni bir durum eklemeniz gerekirse tabloyu baştan oluşturmak zorunda kalacaksınız (buna birazdan değineceğiz), o yüzden listeyi sabitlemeden önce iki kere düşün. Ama rol adları veya sipariş durumları gibi gerçekten sabit kalacak değer kümeleri için tam da ihtiyacın olan kısıt budur.
Check Constraint'lere İsim Vermek
Varsayılan olarak kısıtlar isimsizdir. Hata mesajı sadece "CHECK constraint failed" der ve ifadeyi gösterir; tabloda tek bir CHECK varsa sorun değil ama beş tane varsa kafa karıştırıcı olur. CONSTRAINT ile isim ver:
Hata mesajında artık kısıtlama adı da göründüğü için, hangi kuralın çiğnendiğini anında görebiliyorsunuz. İsim vermek sana birkaç fazladan karakterden başka bir şeye mal olmaz; ama production'da ilk kez bir şeyler ters gittiğinde bu yatırımın karşılığını fazlasıyla alırsınız.
CHECK ve NULL: Dikkat Edilmesi Gereken Tuzak
CHECK ifadesi yalnızca açıkça false döndüğünde başarısız olur; ifade true veya NULL ise geçer. Bu ilk başta tuhaf gelebilir, ancak NULL ile yapılan neredeyse her karşılaştırmanın sonucunun true ya da false değil, NULL olduğunu hatırlayınca yerine oturuyor.
NULL içeren satır sorunsuz ekleniyor — çünkü NULL >= 0 ifadesi false değil, NULL döndürür ve bu yüzden CHECK kısıtı tetiklenmez. Eğer hem negatif sayıları hem de boş değerleri yasaklamak istiyorsanız, NOT NULL ile CHECK'i birlikte kullanmanız gerekir:
Artık CHECK çalışmadan önce INSERT zaten NOT NULL kısıtı yüzünden patlıyor. Aslında bu iki kısıt birbirini tamamlıyor: NOT NULL değerin olup olmadığını, CHECK ise değerin biçimini denetliyor.
CHECK içinde işinize yarayacak hazır fonksiyonlar
CHECK ifadesinde SQLite'ın yerleşik fonksiyonlarının büyük bölümünü rahatça kullanabilirsiniz. Pratikte sık karşılaşacaklarınızdan birkaçı:
Üç farklı hata: bozuk bir e-posta formatı, fazla kısa bir kullanıcı adı ve küçük harfli ülke kodu. LIKE basit kalıplar için yeterli; length(), upper(), lower() ve aritmetik işlemler de serbest. Tek bir kuralımız var: ifadenin deterministik olması gerekir. random() veya current_timestamp gibi şeyleri CHECK içinde kullanırsanız, satırdan satıra değişebilen kurallar elde edersiniz; bu da genelde istediğiniz şey değildir.
CHECK constraint mi, trigger mı?
CHECK ve trigger, ikisi de hatalı veriyi reddedebilir; yeni başlayanlar hangisini seçeceğine sık sık takılır. Pratik bir kural koyalım:
- Kural yalnızca yazılmakta olan satıra bağlıysa CHECK kullanın. "Bu sütun şu sütunla karşılaştırılır", "bu değer şu aralıkta olmalı", "bu string şu kalıba uymalı" gibi durumlar.
- Kural başka satırlara, başka tablolara bağlıysa ya da tek bir boolean ifadeyle anlatamayacağınız kadar karmaşıksa trigger kullanın (özellikle
RAISEçağıran birBEFORE INSERT/UPDATEtrigger'ı).
CHECK daha hızlı, daha basittir ve şemada görünür — CREATE TABLE'ı okuyan herkes kuralı doğrudan görebilir. CHECK'in ifade edemediği bir durum varsa ancak o zaman trigger'a geçin.
ALTER ile CHECK constraint silinemez
İşin can sıkıcı tek tarafı bu. SQLite'ta ALTER TABLE ... DROP CONSTRAINT diye bir komut yok. Bir CHECK'i kaldırmak ya da değiştirmek için tabloyu yeniden inşa etmeniz gerekir:
BEGIN;
CREATE TABLE products_new (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
price REAL NOT NULL CHECK (price >= 0 AND price <= 1000000)
);
INSERT INTO products_new SELECT * FROM products;
DROP TABLE products;
ALTER TABLE products_new RENAME TO products;
COMMIT;
Tüm işi bir transaction içine alın ki ortada bir hata olursa veritabanınız sağlam kalsın. Yeniden oluşturduğunuz tabloya başka tablolardan foreign key geliyorsa iş biraz uzar: önce foreign_keys ayarını kapatın, tabloyu yeniden kurun, tekrar açın ve son olarak bütünlüğü doğrulayın. Bu konuyu müfredatın ilerleyen kısmındaki migration dokümanında ayrıntılı işleyeceğiz.
Sırada: UNIQUE Kısıtlamaları
CHECK, bir satırın içindeki değerlerin biçimini doğrular. Sıradaki kısıtlama olan UNIQUE ise satırlar arasındaki ilişkiyi denetler: bir sütunda ya da sütun grubunda iki satırın aynı değere sahip olmamasını garantiler. Hemen ardından bunu ele alıyoruz.
Sıkça Sorulan Sorular
SQLite'ta CHECK constraint nedir?
CHECK constraint, tabloya bağlanan ve her satırın sağlamak zorunda olduğu bir boolean ifadedir. SQLite, her INSERT veya UPDATE sırasında bu ifadeyi değerlendirir; sonuç false ise işlemi reddeder. 'Fiyat pozitif olmalı' gibi bir kuralı uygulama tarafında kod yazmadan dayatmanın en pratik yoludur.
SQLite CHECK constraint birden fazla sütuna referans verebilir mi?
Evet. Bunu sütuna iliştirmek yerine tablo seviyesinde tanımlamanız yeterli. Örneğin sütun listesinden sonra yazacağınız CHECK (start_date <= end_date) ifadesi her iki sütuna da erişebilir. Sütun seviyesindeki check'ler de teknik olarak başka sütunlara bakabilir, ancak birden fazla sütun söz konusu olduğunda tablo seviyesi çok daha okunaklı durur.
CHECK constraint NULL değerlerde neden devreye girmiyor?
CHECK ifadesi true veya NULL döndüğünde geçer; sadece açıkça false olduğunda başarısız olur. Yani CHECK (age >= 0) ifadesi NULL bir age değerini kabul eder, çünkü NULL >= 0 sonucu false değil NULL'dur. NULL'u da yasaklamak istiyorsanız CHECK'in yanına bir de NOT NULL eklemeniz gerekir.
SQLite'ta CHECK constraint silinebilir veya değiştirilebilir mi?
Doğrudan değil. SQLite ALTER TABLE ... DROP CONSTRAINT komutunu desteklemiyor. Bir CHECK'i değiştirmek için ya PRAGMA writable_schema ile sqlite_schema tablosunu düzenleyeceksiniz (riskli, ileri seviye yöntem) ya da klasik yolu izleyeceksiniz: yeni constraint'lerle yeni bir tablo oluştur, veriyi taşı, eski tabloyu sil, yeni tabloyu yeniden adlandır. Constraint'lere isim vermek bu rebuild script'ini çok daha okunaklı hale getirir.