SQLite'ta STRICT tablo, sütun tiplerinin gerçekten zorunlu olduğu bir tablodur. CREATE TABLE ifadesinin kapanış parantezinden sonra STRICT yazdığınızda her sütun için tip belirtmek zorunlu hâle gelir, tip yalnızca INT, INTEGER, REAL, TEXT, BLOB veya ANY olabilir ve kayıpsız dönüştürülemeyen bir değer eklemeye çalışmak hata verir. Bu özellik SQLite 3.37.0 (2021) ile geldi ve her tablo için ayrı ayrı açılır.

Bu yazıda sıradan tablolarla farkları, ANY tipinin iki tablo türündeki farklı davranışını, katı tablolarda bile CHECK kısıtına neden ihtiyaç duyduğunuzu ve var olan bir tabloyu nasıl katı hâle getireceğinizi göreceksiniz. Ayrıca SQLite geliştiricilerinin esnek tiplemeyi neden savunduğunu da özetleyeceğiz. Esnek tip sisteminin temelleri veri tipleri ve type affinity yazısında, serinin tamamı SQLite öğrenme rehberinde.

Sıradan tabloda ne olur

Önce farkı görmek için sıradan bir tabloya bakalım. SQLite'ta sütunun tanımlı tipi yalnızca bir eğilim (affinity) belirler. Gelen değer bu tipe kayıpsız çevrilebiliyorsa çevrilir; çevrilemiyorsa hata verilmez, değer kendi tipiyle saklanır:

sql
CREATE TABLE gevsek(adet INTEGER, fiyat REAL, ad TEXT);
INSERT INTO gevsek VALUES ('12', '9.5', 34), ('on iki', 'ucuz', x'00');

SELECT typeof(adet), typeof(fiyat), typeof(ad) FROM gevsek;
-- çıktı:
-- integer|real|text
-- text|text|blob

İlk satırdaki metinler sayıya, sayı da metne çevrildi. İkinci satırda ise INTEGER sütununa bir metin, TEXT sütununa bir BLOB yerleşti. Esnek tiplemeyi bilen biri için bu sürpriz değildir, ama başka veritabanlarından gelen geliştiriciler genellikle bunu bir hata olarak görür.

STRICT tablo ne değiştirir

STRICT tablo ne değiştirir: INT, INTEGER, REAL, TEXT, BLOB

Aynı tabloyu STRICT ile tanımladığımızda dönüşüm kuralları yine çalışır; yani '12' metni tamsayıya, 34 sayısı metne çevrilir. Belgelere göre PostgreSQL, MySQL, SQL Server ve Oracle da benzer dönüşümleri yapar. Fark, dönüşüm kayıpsız yapılamadığında ortaya çıkar: değer saklanmaz ve SQLITE_CONSTRAINT_DATATYPE hatası döner.

sql
CREATE TABLE siki(adet INTEGER, fiyat REAL, ad TEXT) STRICT;
INSERT INTO siki VALUES ('12', '9.5', 34);      -- kayıpsız dönüşebilen değerler
SELECT typeof(adet), quote(adet), typeof(fiyat), typeof(ad), quote(ad) FROM siki;
-- çıktı: integer|12|real|text|'34'

INSERT INTO siki VALUES ('on iki', 1.0, 'x');
-- hata: cannot store TEXT value in INTEGER column siki.adet
INSERT INTO siki VALUES (1, 'ucuz', 'x');
-- hata: cannot store TEXT value in REAL column siki.fiyat
INSERT INTO siki VALUES (1, 2.0, x'00');
-- hata: cannot store BLOB value in TEXT column siki.ad
INSERT INTO siki VALUES (2.5, 1.0, 'x');
-- hata: cannot store REAL value in INTEGER column siki.adet

2.5 değeri tamsayıya çevrilirken kesir kaybolacağı için reddedildi. 3.0 ise kayıpsız çevrilir ve kabul edilir. REAL sütununa verilen tamsayı da ondalık sayıya dönüştürülür. NULL, sütunda NOT NULL kısıtı yoksa her tipte serbesttir:

sql
INSERT INTO siki VALUES (3.0, 7, 'x');
SELECT quote(adet), typeof(fiyat), quote(fiyat) FROM siki WHERE rowid = 2;
-- çıktı: 3|real|7.0

INSERT INTO siki VALUES (NULL, NULL, NULL);     -- NOT NULL yoksa NULL serbest
SELECT count(*) FROM siki;
-- çıktı: 3

İzin verilen tip adları

Katı tablolarda tip adı listesi kısadır ve listede olmayan her ad hata verir. VARCHAR(20), DATETIME, BOOLEAN veya NUMERIC gibi başka motorlardan alışık olduğunuz adlar kabul edilmez. Tipi hiç yazılmamış sütun da reddedilir. Belgeler ileride listeye yeni tipler eklenebileceğini belirtir.

sql
CREATE TABLE t1(a, b TEXT) STRICT;
-- hata: missing datatype for t1.a
CREATE TABLE t2(a VARCHAR(20)) STRICT;
-- hata: unknown datatype for t2.a: "VARCHAR(20)"
CREATE TABLE t3(a DATETIME) STRICT;
-- hata: unknown datatype for t3.a: "DATETIME"
CREATE TABLE t4(a INT, b ANY) STRICT;           -- geçerli
TipKabul ettiği değerlerNot
INT, INTEGERTamsayı ve NULLKayıpsız çevrilebilen metin ve ondalıklar dönüştürülür
REALOndalık sayı ve NULLTamsayılar ondalığa çevrilir
TEXTMetin ve NULLSayılar metne çevrilir, BLOB reddedilir
BLOBBLOB ve NULLDiğer tipler reddedilir
ANYHer tipKatı tabloda hiçbir dönüşüm yapılmaz

ANY tipi: katı tabloda esneklik

Bir sütunda farklı tipte veriler tutabilmek SQLite'ın uzun süredir sunduğu bir yetenektir ve katı tablolarda bunu korumak için ANY tipi eklenmiştir. Katı bir tabloda ANY sütunu değeri tam olarak geldiği tipte saklar. Sıradan bir tabloda ise ANY adı özel bir anlam taşımaz; eğilim kurallarına göre sayıya benzeyen metinler sayıya çevrilir. Belgelerdeki örneği kendi tablolarımızla tekrarlayalım:

sql
CREATE TABLE sa(a ANY) STRICT;
CREATE TABLE ga(a ANY);
INSERT INTO sa VALUES ('000123');
INSERT INTO ga VALUES ('000123');

SELECT 'strict', typeof(a), quote(a) FROM sa;
-- çıktı: strict|text|'000123'
SELECT 'normal', typeof(a), quote(a) FROM ga;
-- çıktı: normal|integer|123

Bu fark, telefon numarası veya posta kodu gibi başında sıfır olan değerleri saklarken önemlidir. Sıradan tabloda '000123' metni 123 sayısına dönüşüp baştaki sıfırları kaybetti. Bu tür değerler için en güvenli seçim, katı tabloda TEXT tipidir.

Birincil anahtar ve NOT NULL

Birincil anahtar ve NOT NULL

Katı tablolarda birincil anahtar sütunları örtük olarak NOT NULL kabul edilir; rowid tablolarındaki "birincil anahtarda NULL" tuhaflığı burada yoktur. INTEGER PRIMARY KEY ise yine rowid'in takma adıdır ve NULL verildiğinde otomatik numara üretilir. INT PRIMARY KEY takma ad değildir; bu kural katı tablolarda da aynıdır. Ayrıntılar PRIMARY KEY ve rowid yazısında.

sql
CREATE TABLE kod(k TEXT PRIMARY KEY, v INT) STRICT;
INSERT INTO kod VALUES (NULL, 1);
-- hata: NOT NULL constraint failed: kod.k

CREATE TABLE sid(id INTEGER PRIMARY KEY, ad TEXT NOT NULL) STRICT;
INSERT INTO sid VALUES (NULL, 'otomatik');
SELECT id, ad FROM sid;
-- çıktı: 1|otomatik

Değişmeyenler: kısıtlar, indeksler, dosya biçimi

Belgeler katı tabloda neyin değişmediğini de açıkça listeler: CHECK, NOT NULL, UNIQUE ve FOREIGN KEY kısıtları, DEFAULT ve COLLATE ifadeleri, hesaplanan sütunlar, ON CONFLICT ifadeleri, indeksler ve AUTOINCREMENT sıradan tablolardaki gibi çalışır. Diskteki veri biçimi de aynıdır. PRAGMA integrity_check ve PRAGMA quick_check ise katı tablolarda ek olarak her sütundaki değerin tipini denetler.

Bu, tip denetiminin tek başına veri doğruluğunu sağlamadığı anlamına gelir. Aşağıdaki örnekte tarih için bir CHECK kısıtı yazdık, ama kısıt beklediğimiz gibi çalışmadı:

sql
CREATE TABLE siparis(
  id     INTEGER PRIMARY KEY,
  tarih  TEXT NOT NULL CHECK (tarih = date(tarih)),
  tutar  INTEGER NOT NULL CHECK (tutar >= 0),
  notlar ANY
) STRICT;

INSERT INTO siparis(tarih, tutar, notlar) VALUES ('2026-11-07', 1250, 'kapıda ödeme');
INSERT INTO siparis(tarih, tutar, notlar) VALUES ('2026-11-07', 99, 42);
INSERT INTO siparis(tarih, tutar) VALUES ('07.11.2026', 100);   -- dikkat: kabul edildi!
INSERT INTO siparis(tarih, tutar) VALUES ('2026-11-08', '12,50');
-- hata: cannot store TEXT value in INTEGER column siparis.tutar

SELECT id, tarih, tutar, typeof(notlar) FROM siparis;
-- çıktı:
-- 1|2026-11-07|1250|text
-- 2|2026-11-07|99|integer
-- 3|07.11.2026|100|null

'07.11.2026' değeri tanınan bir tarih biçimi olmadığı için date() fonksiyonu NULL döndürür. tarih = NULL karşılaştırması da NULL verir. Belgelere göre CHECK ifadesi NULL sonuç verirse bu bir ihlal sayılmaz; yalnızca sıfır sonucu ihlaldir. Çözüm, NULL'u da değer gibi karşılaştıran IS işlecini kullanmaktır:

sql
CREATE TABLE siparis2(
  id    INTEGER PRIMARY KEY,
  tarih TEXT NOT NULL CHECK (tarih IS date(tarih))
) STRICT;

INSERT INTO siparis2(tarih) VALUES ('2026-11-07');
INSERT INTO siparis2(tarih) VALUES ('07.11.2026');
-- hata: CHECK constraint failed: tarih IS date(tarih)
INSERT INTO siparis2(tarih) VALUES ('2026-02-30');
-- hata: CHECK constraint failed: tarih IS date(tarih)

SELECT date('2026-02-30'), date('07.11.2026') IS NULL;
-- çıktı: 2026-03-02|1

Bu sürüm 30 Şubat gibi var olmayan bir günü de yakaladı; çünkü date() bu değeri normalleştirip 2 Mart olarak döndürür ve sonuç girdiyle eşleşmez. NULL karşılaştırmalarının inceliği için NULL davranışı, tarih fonksiyonları için tarih ve saat fonksiyonları yazılarına bakabilirsiniz.

Mantıksal değerler

Katı tablolarda BOOLEAN tipi yoktur. SQLite mantıksal değerleri zaten tamsayı olarak saklar; TRUE ve FALSE anahtar kelimeleri 1 ve 0 anlamına gelir. Sütunu INTEGER yapıp bir CHECK kısıtıyla iki değere sınırlamak yaygın bir çözümdür:

sql
CREATE TABLE bayrak(aktif INTEGER NOT NULL CHECK (aktif IN (0, 1))) STRICT;
INSERT INTO bayrak VALUES (TRUE), (FALSE);
INSERT INTO bayrak VALUES (2);
-- hata: CHECK constraint failed: aktif IN (0, 1)
SELECT aktif FROM bayrak;
-- çıktı:
-- 1
-- 0

Katı tabloları tanımak

SQLite 3.37.0 ile gelen PRAGMA table_list, strict sütununda tablonun katı olup olmadığını gösterir. Bütünlük denetimi de katı tablolardaki tip uyumsuzluklarını raporlar:

sql
SELECT name, strict FROM pragma_table_list
 WHERE schema = 'main' AND name IN ('sa', 'ga', 'siparis') ORDER BY name;
-- çıktı:
-- ga|0
-- sa|1
-- siparis|1

PRAGMA integrity_check;
-- çıktı: ok

Tablo seçenekleri virgülle birleştirilebilir; bir tablo aynı anda STRICT ve WITHOUT ROWID olabilir. Bu kombinasyon WITHOUT ROWID tablolar yazısında gösteriliyor. Belgelere göre ayrıştırıcı aynı seçeneğin iki kez yazılmasına şimdilik ses çıkarmıyor, ancak bu ileride değişebileceği için buna güvenmemelisiniz.

Var olan tabloyu STRICT yapmak

ALTER TABLE ile bir tabloyu katı hâle getirmek mümkün değildir. Yapmanız gereken, tabloyu yeni tanımla oluşturup veriyi kopyalamaktır. Kopyalamadan önce hangi satırların yeni kurallara uymayacağını bulmak işinizi kolaylaştırır. typeof() ile tip dağılımına bakmak yeterlidir:

sql
CREATE TABLE eski(id INTEGER PRIMARY KEY, fiyat INTEGER);
INSERT INTO eski(fiyat) VALUES (10), ('20'), ('yirmi'), (12.5), (NULL);

SELECT typeof(fiyat) AS tur, count(*) FROM eski GROUP BY tur ORDER BY tur;
-- çıktı:
-- integer|2
-- null|1
-- real|1
-- text|1

SELECT id, quote(fiyat) FROM eski WHERE typeof(fiyat) NOT IN ('integer', 'null');
-- çıktı:
-- 3|'yirmi'
-- 4|12.5

Bu satırları düzelttikten veya CAST ile dönüştürdükten sonra veriyi yeni tabloya aktarabilirsiniz. Tablonun indeksleri, tetikleyicileri ve görünümleriyle birlikte güvenli biçimde yeniden oluşturulması için ALTER TABLE yazısındaki on iki adımlı yöntemi izleyin; orada katı bir tabloya geçişin tam örneği de var. Sürümlü şema değişikliklerini yönetmek için şema göçü ve user_version yazısına bakın.

Uyumluluk: eski sürümler ne yapar

STRICT anahtar kelimesini yalnızca SQLite 3.37.0 ve sonrası tanır. Daha eski bir sürüm böyle bir tablo içeren veritabanını açtığında şemayı ayrıştıramaz ve hata verir. Dosya biçimi değişmediği için, katı tablo içermeyen bir veritabanı yeni bir sürümle oluşturulmuş olsa bile çok eski sürümlerce okunabilir. Uygulamanızın kullandığı SQLite kütüphanesinin sürümünü bu yüzden kontrol edin; dil bağlayıcıları çoğu zaman kendi kopyalarını taşır.

Belgeler bir ayrıntıya daha dikkat çeker: 3.37.0 öncesi sürümler PRAGMA writable_schema=ON ayarıyla şema hatalarını yok sayarak katı tablolara okuyup yazabilir. Bu durumda STRICT kelimesi görmezden gelinir ve tip denetimi yapılmaz. Komut satırındaki .dump komutu da bu ayarı açar. Böyle bir yolla yanlış tipte veri yazılırsa veritabanını yeni bir sürümle açıp PRAGMA quick_check çalıştırdığınızda bu sorunlar raporlanır. Bozulma ve kurtarma konuları için bozuk veritabanı kurtarma yazısına bakın.

Esnek tipleme gerçekten kötü mü

SQLite geliştiricileri esnek tiplemeyi bir hata değil, bilinçli bir özellik olarak görür ve bunu ayrı bir belgede savunur. Öne sürülen kullanım alanları şunlardır: farklı tipte ayarları tek bir tabloda tutan öznitelik tabloları, json_each gibi sonuç sütunu her satırda farklı tip taşıyabilen yapılar, Excel çıktısı gibi karışık tipli "kirli" verinin temizlenmeden önce içeri alınması, dinamik dillerle çalışma ve sütunun anlamının göç gerektirmeden genişletilebilmesi. Belgedeki örnekte saniye cinsinden tutulan zaman damgalarına sonradan milisaniye eklemek, tablo değiştirilmeden yapılabiliyor.

Belge, katı tip denetiminin yalnızca bariz hataları yakaladığını, ad ile soyadının yer değiştirmesi gibi ince hataları yakalayamadığını ve bariz hataların hatalı veri kaynağını gösteren bir işaret olarak da işe yarayabileceğini savunur. Yazar, katı tip denetiminin bir uygulama hatasını önlediğine dair somut bir örnekle karşılaşmadığını da belirtir. Bu bir görüş yazısıdır; hangi yaklaşımı seçeceğiniz ekibinize ve verinize bağlıdır. Pratikte ikisini birlikte kullanabilirsiniz: dış kaynaklardan gelen veriyi önce esnek bir ara tabloya alıp temizler, sonra katı tablolara aktarırsınız. CSV içe aktarma bunun tipik örneğidir; ayrıntılar CSV içe ve dışa aktarma yazısında.

Hangi durumda hangisini seçmeli

Belgelerden çıkan ölçütleri bir araya getirirsek karar şöyle verilebilir. Veritabanına birden fazla program veya ekip yazıyorsa ve her sütunun tek bir tip taşıması bekleniyorsa katı tablo, yanlış tipteki değeri ilk yazma anında durdurur ve sorunun kaynağını hemen gösterir. Şemanızı ileride PostgreSQL veya MySQL gibi katı bir motora taşıma ihtimaliniz varsa da işe yarar; belgeler, SQLite'ın esnekliğinden farkında olmadan yararlanan uygulamaların taşındıklarında bozulabildiğini açıkça yazar. Karşılaştırma için PostgreSQL nedir yazısına bakabilirsiniz.

Öte yandan ayar tabloları, ham veri alanları ve tipi zamanla değişebilecek alanlar için esnek tipleme daha az zahmet çıkarır. Katı bir tabloda bile bu tür tek bir sütunu ANY olarak tanımlayıp geri kalan sütunları katı tutabilirsiniz. Böylece iki yaklaşımı aynı tabloda birleştirmiş olursunuz.

Sık yapılan hatalar

Sık yapılan hatalar: Başka motorların tip adlarını kullanmak, Tip denetimini doğrulama sanmak, CHECK içinde = ile NULL üreten ifade karşılaştırmak, Başında sıfır olan kodları...
  • Başka motorların tip adlarını kullanmak: VARCHAR, DATETIME, BOOLEAN katı tablolarda hata verir; altı izinli addan birini seçin.
  • Tip denetimini doğrulama sanmak: STRICT yalnızca depolama tipini denetler; biçim ve aralık için CHECK yazın.
  • CHECK içinde = ile NULL üreten ifade karşılaştırmak: NULL sonuç ihlal sayılmaz; IS kullanın veya ifadeyi NOT NULL ile güvenceye alın.
  • Başında sıfır olan kodları INTEGER veya sıradan ANY sütununda tutmak: Sıfırlar kaybolur; TEXT kullanın.
  • Eski SQLite kütüphanelerini unutmak: 3.37.0 öncesi bir sürüm katı tablo içeren dosyayı açamaz.

Sık Sorulan Sorular

STRICT tablolar daha mı yavaş?

Belgeler bu konuda bir ölçüm vermez. Dosya biçimi aynıdır; fark, yazma sırasında yapılan tip denetimidir. Performans sizin için kritikse kendi iş yükünüzle ölçün; genel ayarlar için performans ayarları yazısına bakın.

Bütün veritabanını STRICT yapan bir ayar var mı?

Hayır. Katı tip denetimi her tablo için CREATE TABLE ifadesinde ayrı ayrı açılır.

Tarihleri STRICT tabloda nasıl saklamalıyım?

SQLite'ın ayrı bir tarih tipi yoktur. Tarihleri ISO biçiminde TEXT, Unix zamanı olarak INTEGER ya da Jülyen günü olarak REAL tutabilirsiniz. Biçimi korumak için yukarıdaki gibi IS kullanan bir CHECK kısıtı ekleyin.

STRICT kelimesini sütun adı olarak kullanabilir miyim?

Evet. Belgelere göre STRICT yalnızca söz diziminin belirli bir yerinde anahtar kelime olarak yorumlanır ve tanımlayıcı olarak kullanılabilir.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: SQLite öğrenme rehberi · Veri tipleri ve type affinity · CREATE TABLE ve kısıtlamalar · WITHOUT ROWID tablolar · ALTER TABLE · Hesaplanan sütunlar