UNIQUE: "Aynı Değerden İki Tane Olmasın"
SQLite'ta UNIQUE kısıtlaması, bir sütundaki (ya da birden fazla sütunun birlikte oluşturduğu bir grubun) değerlerinin satırlar arasında tekrar etmemesi gerektiğini belirtir. Yani "iki kullanıcı aynı e-postaya sahip olamaz" ya da "bir ürün kodu en fazla bir kez geçer" demenin yolu budur.
Üçüncü INSERT ifadesi UNIQUE kısıtlaması başarısız oldu: users.email hatasıyla patlar. SQLite, her yazma işleminde kısıtlamayı kontrol eder ve mükerrer kayıt oluşturacak her şeyi reddeder. İlk iki satır kaydedilir; üçüncüsü hiçbir zaman tabloya düşmez.
Perde arkasında UNIQUE, bir unique index olarak tutulur — yani SQLite'ın hızlı arama için kullandığı veri yapısının aynısı. Bu sayede kontrol oldukça ucuza gelir ve sütun otomatik olarak indekslenmiş olur.
Sütun Düzeyi ve Tablo Düzeyi Sözdizimi
UNIQUE kısıtlamasını iki şekilde yazabilirsiniz: ya doğrudan sütunun yanına satır içi olarak, ya da tablo tanımının sonunda ayrı bir ifade olarak:
Tek bir sütun için iki yazım da aynı kapıya çıkar — hangisi daha iyi okunuyorsa onu seç. Ama iş birden fazla sütunu birlikte unique yapmaya geldiğinde, tablo seviyesindeki yazım kaçınılmaz hale geliyor.
Composite UNIQUE: SQLite'ta birden fazla sütunu birlikte unique yapmak
Bazen tek bir sütun kendi başına benzersiz olmaz; ama sütunların kombinasyonu benzersiz olmalıdır. Bir kullanıcı birden çok kursa kaydolabilir, bir kurs da birden çok kullanıcıya sahip olabilir — fakat aynı (user_id, course_id) çiftinin tabloda iki kez görünmemesi gerekir:
Kısıtlama tek başına sütunlara değil, çiftin kendisine uygulanıyor. 1 numaralı kullanıcı birçok kursa kayıt olabilir, 100 numaralı kurs birçok kullanıcıya sahip olabilir — ama her kombinasyon yalnızca bir kez.
Çoka-çok ilişkilerdeki ara (join) tablolarında bu, klasik ekmek-su deseni.
UNIQUE ile PRIMARY KEY arasındaki fark
Kulağa benzer geliyorlar ve aralarında bir bağ var, ama aynı şey değiller:
- Bir tabloda en fazla bir tane
PRIMARY KEYbulunur.UNIQUEkısıtlaması ise birden fazla olabilir. PRIMARY KEY, satırın kimliğidir — yabancı anahtarların işaret ettiği,rowid'in takma adı olduğu şeydir.UNIQUEise sadece "bu değer (ya da kombinasyon) tekrar etmesin" demektir.- Normal bir tabloda
UNIQUEbir sütunNULLdeğer tutabilir;PRIMARY KEYtutamaz (atlayacağımız tarihsel bir istisna dışında).
Sıkça karşılaşılan bir kalıp şöyle:
id, veritabanının geri kalanının referans olarak kullandığı alandır. email ve username ise kimlik oldukları için değil, uygulama gereği benzersiz olmak zorunda oldukları için UNIQUE işaretlenir. Kullanıcı e-postasını değiştirdiğinde id aynı kalır — zaten ikisini ayrı tutmanın asıl amacı budur.
NULL Tuhaflığı
Bu konu neredeyse herkesi ilk seferde tuzağa düşürür. SQLite'ta UNIQUE olarak tanımlanmış bir sütun, istediğiniz kadar NULL değer kabul eder:
Üç tane NULL, sorun değil. Ama iki tane 'ada@example.com' olunca çakışma çıkıyor.
Sebebi şu: SQL, NULL değerini "bilinmiyor" olarak görür ve iki "bilinmeyen" birbirine eşit sayılmaz; dolayısıyla teklik kontrolü bunları yinelenmiş kabul edemez. Eğer en fazla bir NULL olmasını istiyorsanız en temiz çözüm NOT NULL UNIQUE kullanmaktır. NULL değerlere izin vermek istiyor ama diğer sütun kombinasyonu başına yalnızca bir tane olsun diyorsanız, partial index devreye girer (ilerideki indeks bölümünde anlatacağız).
Çakışmaları Yönetmek: SQLite ON CONFLICT Kullanımı
Varsayılan olarak bir UNIQUE ihlali çalıştırdığınız ifadeyi iptal eder. Ama bazen başka bir davranış istersiniz: var olan satırı değiştirmek, yenisini görmezden gelmek ya da belirli sütunları güncellemek gibi. SQLite bunu istemenin iki yolunu sunuyor.
İlki, ON CONFLICT ile doğrudan kısıtlamanın içine yazılır:
theme ikinci kez eklendiğinde, mevcut satır silinir ve yerini yenisi alır. Diğer seçenekler ise şunlar: IGNORE (sessizce atla), ABORT (varsayılan davranış), FAIL ve ROLLBACK.
İkinci yöntem ise ifade bazlı çalışır ve upsert söz dizimini kullanır. Belirli sütunları güncelleyebildiği için genellikle çok daha esnektir:
İlk INSERT satırı oluşturur. Sonraki ikisi UNIQUE kısıtlamasına takılır ve DO UPDATE dalına düşerek count değerini bir artırır. Bu, INSERT ... ON CONFLICT upsert kalıbıdır — ileride ona ayrılmış ayrı bir sayfa var.
UNIQUE Kısıtlaması ile UNIQUE Index Arasındaki Fark
CREATE UNIQUE INDEX ifadesi, UNIQUE kısıtlamasıyla aynı işi görür. Hatta UNIQUE kısıtlaması tanımladığınızda SQLite arka planda zaten bir unique index oluşturur — yani aslında aynı mekanizmanın farklı kılıklara bürünmüş halleridir.
Hangi durumda hangisini tercih etmeli:
- Tablonun tanımının bir parçası olarak benzersizlik istiyorsanız constraint kullan. Kuralı doğrudan sütunların yanında, belge gibi görürsünüz.
- Kısmi indeks (
WHEREkoşullu) istiyorsanız, indekse özel bir isim vermek istiyorsanız ya da mevcut bir tabloyu baştan yazmadan kural eklemek istiyorsanız unique index tercih et. SQLite'taALTER TABLEile constraint eklenemez ama indeks her zaman eklenebilir.
Yazma işlemlerindeki davranış ikisinde de aynıdır. Seçim, kuralın şemada nerede dursun istediğinle ilgilidir.
Var Olan Tabloya UNIQUE Eklemek
SQLite'taki ALTER TABLE bilinçli olarak kısıtlı tutulmuştur — ALTER TABLE ... ADD CONSTRAINT diye bir şey yok. Pratikte iki yol var:
- Seçenek —
UNIQUEkısıtlamasını gerçekten tablo tanımının içine gömmek istiyorsanız — tabloyu yeniden oluşturma dansını yapmanız gerekir: kısıtlamalı yeni bir tablo oluştur, verileri taşı, eskisini sil, yenisini yeniden adlandır. Bunu bir sonraki sayfada ele alıyoruz.
Küçük bir uyarı: Hâlihazırda yinelenen değerler içeren bir sütuna teklik eklemeye çalışırsanız CREATE UNIQUE INDEX komutu hata verir. Önce yinelenen satırları temizleyin, sonra indeksi ekleyin.
UNIQUE hatası: "UNIQUE constraint failed" mesajını okumak
Hata mesajı, hangi kısıtlamanın patladığını size doğrudan söyler:
Hata: UNIQUE kısıtlaması başarısız oldu: users.email
Hata: UNIQUE kısıtlaması başarısız oldu: enrollments.user_id, enrollments.course_id
İlk biçim, users.email üzerinde tek sütunlu bir kısıtlamadır. İkincisi ise bileşik (composite) bir kısıtlamadır — iki sütunun birlikte yazılma sebebi, kombinasyonun zaten var olmasıdır. Bu hatayı gördüğünüzde:
- Çakışan değerin hangi satırda olduğunu bulun (
SELECT ... WHERE email = '...'). - O satırı güncellemek mi, eklemeyi atlamak mı, yoksa farklı bir değer kullanmak mı istediğinize karar verin.
- Eğer tekrar eden kayıtlar beklenen bir durumsa ve bunları birleştirmek istiyorsanız,
INSERT ... ON CONFLICT DO UPDATEkullanmaya geçin.
Hata bu kadar gürültülü patlıyor çünkü çoğu zaman gerçekten haberdar olmak istersiniz — sessizce eklenen tekrar kayıtlar, başarısız bir yazma işleminden çok daha kötü olurdu.
Sırada: Tabloları Silme ve Değiştirme
UNIQUE kısıtlaması, mevcut bir tabloya basit bir ALTER TABLE ile eklenemez. İşte SQLite'ın şema değişiklikleri için kendine has bir dansı olmasının sebebi de bu — tablonun yeniden yazılması. Bir sonraki sayfanın konusu da tam olarak bu olacak; ayrıca tabloları temiz bir şekilde silmenin temellerine de değineceğiz.
Sıkça Sorulan Sorular
SQLite'ta UNIQUE kısıtlaması nasıl eklenir?
Sütun tanımına UNIQUE ekleyebilirsiniz (email TEXT UNIQUE) ya da birden fazla sütunda benzersizlik istiyorsanız tablo seviyesinde UNIQUE(col1, col2) yazabilirsiniz. SQLite arka planda otomatik olarak benzersiz bir indeks oluşturur ve kopya üretecek her INSERT veya UPDATE işlemini reddeder.
SQLite'ta UNIQUE ile PRIMARY KEY arasındaki fark nedir?
Bir tabloda yalnızca tek bir PRIMARY KEY olabilir, ama istediğiniz kadar UNIQUE kısıtlaması tanımlayabilirsiniz. Ayrıca PRIMARY KEY aynı zamanda NOT NULL anlamına gelir (strict tablolarda ve INTEGER PRIMARY KEY durumunda), oysa UNIQUE sütunlar birden fazla NULL değer kabul edebilir. Kısacası: satırın kimliği için PRIMARY KEY, tekrar etmemesi gereken diğer alanlar için UNIQUE kullanın.
SQLite neden UNIQUE sütunda birden fazla NULL'a izin veriyor?
Çünkü SQL'de NULL 'bilinmiyor' anlamına gelir ve iki bilinmeyen birbirine eşit sayılmaz. Bu yüzden UNIQUE bir sütuna istediğiniz kadar NULL satır ekleyebilirsiniz; yalnızca NULL olmayan değerlerin benzersiz olması gerekir. Eğer en fazla bir NULL istiyorsanız sütuna NOT NULL ekleyin ya da partial unique index kullanın.
'UNIQUE constraint failed' hatası nasıl düzeltilir?
Bu hata, yaptığınız INSERT veya UPDATE işleminin UNIQUE (ya da PRIMARY KEY) sütunda kopya değer oluşturacağı anlamına gelir. Çözüm için ya eklediğiniz değeri değiştirin, ya çakışan mevcut satırı önce silin ya da INSERT ... ON CONFLICT (yani upsert) kullanarak SQLite'a çakışma anında ne yapması gerektiğini söyleyin.