SQLite'ta bir değerin tipi, saklandığı sütuna değil değerin kendisine aittir. Her değer beş depolama sınıfından birindedir: NULL, INTEGER, REAL, TEXT veya BLOB. Sütunun bildirilen tipi yalnızca bir "eğilim" (type affinity) belirler: SQLite, gelen değeri kayıpsız dönüştürebiliyorsa o sütunun tercih ettiği sınıfa çevirir, dönüştüremiyorsa değeri olduğu gibi saklar ve hata vermez. Bir değerin gerçek sınıfını typeof() fonksiyonuyla öğrenirsiniz.

Bu yaklaşım SQLite belgelerinde "esnek tipleme" olarak adlandırılır ve bilinçli bir tasarım tercihidir. Diğer veritabanlarından gelen geliştiricileri en çok şaşırtan konu olduğu için bu yazıda kuralları tek tek, çalıştırılmış örneklerle inceliyoruz: depolama sınıfları, sütun eğiliminin belirlenmesi, karşılaştırmadan önce yapılan dönüşümler, sıralama ve CAST davranışı. SQLite'ın genel tanıtımı için SQLite nedir, serinin tamamı için SQLite öğrenme rehberi sayfasına bakabilirsiniz.

Beş depolama sınıfı

Belgelere göre INTEGER değerler büyüklüklerine göre 0, 1, 2, 3, 4, 6 veya 8 baytta işaretli tamsayı olarak, REAL değerler 8 baytlık IEEE kayan noktalı sayı olarak, TEXT değerler veritabanının kodlamasıyla (UTF-8, UTF-16BE veya UTF-16LE), BLOB değerler ise girildiği gibi bayt bayt saklanır. Diskte farklı boyutlar kullanılsa da tamsayılar belleğe okunduğu anda 8 baytlık işaretli tamsayıya çevrilir; bu yüzden pratikte "depolama sınıfı" ile "veri tipi" aynı anlamda kullanılabilir.

sql
SELECT typeof(42), typeof(4.2), typeof('42'), typeof(x'2A'), typeof(NULL);
-- çıktı: integer|real|text|blob|null

SELECT typeof(TRUE), TRUE + TRUE;
-- çıktı: integer|2

SELECT 1 = '1', 1 = CAST('1' AS INTEGER);
-- çıktı: 0|1

İlk sorgu her sabitin kendi sınıfını taşıdığını gösteriyor. İkinci sorgu ayrı bir mantıksal tip olmadığını doğruluyor: TRUE ve FALSE anahtar kelimeleri 3.23.0 sürümünden beri tanınır, ancak bunlar yalnızca 1 ve 0 tamsayılarının başka yazılışıdır. Üçüncü sorgu ise belgelerin "tuhaflıklar" sayfasında da anlatılan bir noktayı gösterir: SQLite tamsayı ile metni ayırt eder, bu yüzden 1 = '1' yanlış döner.

Tarih ve saat için de ayrı bir sınıf yoktur. Yerleşik tarih fonksiyonları tarihleri ISO 8601 biçiminde metin, Jülyen gün sayısı olarak ondalık sayı veya Unix zamanı olarak tamsayı şeklinde okuyup yazabilir. Hangisini seçeceğiniz uygulamanıza kalmıştır; ayrıntılar SQLite tarih ve saat fonksiyonları yazısında.

Type affinity: sütunun tercih ettiği tip

Type affinity: sütunun tercih ettiği tip: TEXT, NUMERIC, INTEGER, REAL

INTEGER PRIMARY KEY dışındaki her sütun, her sınıftan değeri saklayabilir. Ancak her sütunun beş eğilimden biri vardır: TEXT, NUMERIC, INTEGER, REAL veya BLOB. Belgelerdeki kurallar özetle şöyledir:

  • TEXT eğilimli sütun sayıları metne çevirerek saklar; NULL, TEXT ve BLOB dışında bir sınıf tutmaz.
  • NUMERIC eğilimli sütun, düzgün yazılmış bir tamsayı veya ondalık sayı metnini sırasıyla INTEGER veya REAL'e çevirir. Tamsayı olarak tam ifade edilebilen ondalık değerler tamsayıya döner. Sayı gibi görünmeyen metin metin olarak kalır. On altılık (hex) yazılmış metinler sayı sayılmaz.
  • INTEGER eğilimi NUMERIC ile aynı davranır; farkı yalnızca CAST ifadesinde ortaya çıkar.
  • REAL eğilimi NUMERIC gibidir, ancak tamsayıları da ondalık sayıya zorlar.
  • BLOB eğilimli sütun hiçbir sınıfı tercih etmez ve dönüşüm yapmaz.
sql
CREATE TABLE deneme(m TEXT, s NUMERIC, t INTEGER, o REAL, b BLOB, yok);
INSERT INTO deneme VALUES ('007', '007', '007', '007', '007', '007');
INSERT INTO deneme VALUES (3.0, 3.0, 3.0, 3.0, 3.0, 3.0);
INSERT INTO deneme VALUES ('12abc', '12abc', '12abc', '12abc', '12abc', '12abc');

SELECT typeof(m), typeof(s), typeof(t), typeof(o), typeof(b), typeof(yok) FROM deneme;
-- çıktı:
-- text|integer|integer|real|text|text
-- text|integer|integer|real|real|real
-- text|text|text|text|text|text

SELECT m, s, t, o, b, yok FROM deneme;
-- çıktı:
-- 007|7|7|7.0|007|007
-- 3.0|3|3|3.0|3.0|3.0
-- 12abc|12abc|12abc|12abc|12abc|12abc

Sonuçları satır satır okuyalım. '007' metni NUMERIC, INTEGER ve REAL sütunlarda sayıya çevrildi ve baştaki sıfırlar kayboldu; TEXT ve BLOB eğilimli sütunlar ile tipi hiç belirtilmemiş sütun metni korudu. Posta kodu, telefon numarası veya ürün kodu gibi baştaki sıfırın önemli olduğu değerleri bu yüzden TEXT sütunlarda tutmalısınız. 3.0 değeri NUMERIC ve INTEGER sütunlarda tamsayı 3 oldu, REAL sütunda ondalık kaldı, TEXT sütunda '3.0' metnine dönüştü. '12abc' ise sayı olarak yorumlanamadığı için her sütunda metin olarak saklandı; SQLite bu durumda hata vermez. Belgelere göre BLOB ve NULL değerler, sütun eğiliminden hiç etkilenmez.

Bildirilen tipten eğilim nasıl çıkarılır

STRICT olmayan tablolarda eğilim, sütunun bildirilen tip adına bakılarak beş kuralla ve şu sırayla belirlenir. Kural sırası önemlidir; bir tip adı birden çok kurala uyuyorsa ilk uyan kazanır.

SıraTip adında geçenEğilimÖrnek tip adları
1INTINTEGERINT, INTEGER, BIGINT, TINYINT, INT8
2CHAR, CLOB veya TEXTTEXTVARCHAR(255), NCHAR(55), TEXT, CLOB
3BLOB veya tip yokBLOBBLOB, tipsiz sütun
4REAL, FLOA veya DOUBREALREAL, DOUBLE, FLOAT
5Diğer her şeyNUMERICNUMERIC, DECIMAL(10,5), BOOLEAN, DATE, DATETIME

Bu kuralların bazı şaşırtıcı sonuçları vardır. FLOATING POINT adındaki POINT kelimesi "INT" içerdiği için sütun INTEGER eğilimi alır. STRING hiçbir kurala uymadığı için NUMERIC olur, yani metin eğilimi taşımaz. CHARINT hem 1. hem 2. kurala uyar, ilk kural kazanır. Parantez içindeki uzunluklar ise tamamen yok sayılır; SQLite VARCHAR(5) sütununa uzun bir metni kısaltmadan yazar:

sql
CREATE TABLE tur(a FLOATING POINT, b STRING, c VARCHAR(5), d DATETIME, e CHARINT);
INSERT INTO tur VALUES ('2.0', '0042', 'çok uzun bir metin', '2026-11-07', '9');
SELECT typeof(a), a, typeof(b), b, length(c), typeof(d), typeof(e) FROM tur;
-- çıktı: integer|2|integer|42|18|text|integer

FLOATING POINT sütununa yazılan '2.0' tamsayı 2 olarak saklandı. STRING sütunundaki '0042' sayıya döndü ve baştaki sıfırları kaybetti. VARCHAR(5) sütunu 18 karakterlik metni eksiksiz tuttu. DATETIME NUMERIC eğilimlidir, ancak '2026-11-07' düzgün bir sayı olmadığı için metin kaldı. Başka bir veritabanından şema taşırken tip adlarını bu tabloya göre gözden geçirmeniz iyi olur.

Karşılaştırmadan önce yapılan dönüşümler

Karşılaştırmadan önce yapılan dönüşümler

Karşılaştırma işleçleri (=, <, IN, BETWEEN ve diğerleri) iki tarafın eğilimine göre dönüşüm yapabilir. Belgelere göre kurallar sırasıyla şöyledir: taraflardan biri INTEGER, REAL veya NUMERIC eğilimliyse ve diğeri TEXT, BLOB eğilimli ya da eğilimsizse diğer tarafa NUMERIC eğilimi uygulanır. Taraflardan biri TEXT eğilimliyse ve diğerinin eğilimi yoksa diğer tarafa TEXT eğilimi uygulanır. Aksi hâlde değerler olduğu gibi karşılaştırılır. Eğilimi olan ifadeler doğrudan tablo sütunları ve CAST ifadeleridir; sabitlerin ve +sutun ya da a+b gibi hesaplanmış ifadelerin eğilimi yoktur.

sql
CREATE TABLE stok(kod TEXT, adet INTEGER, etiket BLOB);
INSERT INTO stok VALUES ('500', '500', '500');
SELECT typeof(kod), typeof(adet), typeof(etiket) FROM stok;
-- çıktı: text|integer|text

SELECT kod < 60, adet < 60, etiket < 60 FROM stok;
-- çıktı: 1|0|0

SELECT kod = 500, adet = '500', etiket = 500, etiket = '500' FROM stok;
-- çıktı: 1|1|0|1

kod < 60 sorgusunda TEXT eğilimli sütun yüzünden 60 sayısı '60' metnine çevrildi ve metin karşılaştırmasında '500' < '60' doğru çıktı. Aynı değer INTEGER sütunda sayısal karşılaştırıldı. BLOB eğilimli etiket sütunu ise dönüşüm yapmadığı için içindeki metin tamsayı 500 ile eşleşmedi. Sayısal veriyi metin sütununda tuttuğunuzda sıralama ve aralık sorgularının yanlış sonuç vermesinin nedeni budur.

IN işlecinde de dikkat edilecek bir ayrıntı var: a IN (x, y, z) ifadesinde sağdaki değerlerin eğilimi yoktur, sütun değerleri ya da CAST ifadeleri olsalar bile. x IN (SELECT y ...) ise x = y karşılaştırması gibi ele alınır.

Sıralama ve gruplama

ORDER BY kullanıldığında farklı sınıflardan değerler sabit bir sırayla dizilir: önce NULL, sonra sayısal sıraya göre karışık INTEGER ve REAL değerler, sonra sıralama kuralına (collation) göre TEXT, en sonda bayt karşılaştırmasıyla BLOB. Sıralamadan önce sınıf dönüşümü yapılmaz. GROUP BY ise farklı sınıfları ayrı gruplar sayar; tek istisna, sayısal olarak eşit olan INTEGER ve REAL değerlerin aynı gruba girmesidir.

sql
SELECT x, typeof(x) FROM (
  SELECT 10 AS x UNION ALL SELECT 2.5 UNION ALL SELECT '1'
  UNION ALL SELECT NULL UNION ALL SELECT x'41'
) ORDER BY x;
-- çıktı:
-- |null
-- 2.5|real
-- 10|integer
-- 1|text
-- A|blob

SELECT count(*) FROM (SELECT 2 AS x UNION ALL SELECT 2.0 UNION ALL SELECT '2')
GROUP BY x ORDER BY 1;
-- çıktı:
-- 1
-- 2

İlk sorguda '1' metni, sayısal olarak daha büyük görünen 10'dan sonra geldi; çünkü her metin her sayıdan büyük kabul edilir. İkinci sorguda 2 ve 2.0 aynı grupta toplandı, '2' metni ayrı bir grup oluşturdu. UNION, INTERSECT ve EXCEPT birleşimleri de karşılaştırma yaparken eğilim uygulamaz. Metinlerin hangi kurala göre sıralandığı COLLATE ve NOCASE yazısında ayrıntılı anlatılıyor.

CAST ile açık dönüşüm

CAST(ifade AS tip), değeri tip adından çıkan eğilimin sınıfına zorla çevirir. Sütun eğiliminden farkı, dönüşümün kayıplı olsa bile her zaman yapılmasıdır. Metinden sayıya dönüşümde metnin başındaki boşluklar atlanır ve sayı olarak okunabilen en uzun ön ek alınır; geri kalanı yok sayılır, hiç ön ek yoksa sonuç 0 olur.

sql
SELECT CAST('123e+5' AS INTEGER), CAST('123e+5' AS REAL),
       CAST('  42 elma' AS INTEGER), CAST('elma' AS INTEGER);
-- çıktı: 123|12300000.0|42|0

SELECT CAST(4.0 AS INT), CAST(4.0 AS NUMERIC), CAST('4.0' AS NUMERIC),
       CAST('0x1F' AS INTEGER), 0x1F;
-- çıktı: 4|4.0|4|0|31

SELECT CAST(-3.9 AS INTEGER), CAST(3.9 AS INTEGER), CAST(9e20 AS INTEGER);
-- çıktı: -3|3|9223372036854775807

SELECT typeof(CAST(12 AS TEXT)), typeof(CAST('ab' AS BLOB));
-- çıktı: text|blob

Örneklerdeki ayrıntılar belgelerdeki kuralların birebir karşılığıdır. Tamsayıya çevirirken üs kısmı tamsayı ön ekinin parçası sayılmadığı için '123e+5' 123 oldu. CAST(4.0 AS INT) tamsayı 4 verirken CAST(4.0 AS NUMERIC) değeri değiştirmedi; INTEGER ile NUMERIC eğilimi arasındaki tek fark budur. Buna karşın '4.0' metni NUMERIC'e çevrilince tamsayı oldu. On altılık yazım yalnızca SQL metnindeki sabitlerde anlaşılır: 0x1F sabiti 31 iken aynı metin CAST ile 0 olur. Ondalık sayıdan tamsayıya geçişte değer sıfıra doğru yuvarlanır, sınırı aşan değerler en büyük veya en küçük 64 bitlik tamsayıya sabitlenir. NULL değerin CAST sonucu her zaman NULL'dur.

Aritmetik işleçler ve mantıksal değerler

Matematik işleçleri her iki tarafı sayı olarak yorumlar. Metin ondalık nokta veya üs içeriyorsa REAL, aksi hâlde INTEGER'a çevrilir; sayıya benzemeyen bir metin sessizce 0 olur. İki tamsayının bölümü tamsayıdır, sıfıra bölme ise hata değil NULL üretir. Mantıksal bağlamlarda (WHERE, HAVING, ON, CASE WHEN) sonuç önce NUMERIC'e çevrilir; sıfır yanlış, NULL bilinmeyen, diğer her şey doğrudur.

sql
SELECT '10' + 5, '3.0' + 1, 'abc' * 2, 7 / 2, 7 / 2.0, 7 % 0;
-- çıktı: 15|4.0|0|3|3.5|

SELECT 'evet' WHERE '1english';
-- çıktı: evet
SELECT 'hayır' WHERE 'english';
-- (satır dönmez: 'english' sayıya çevrilince 0 olur)

SELECT 0.1 + 0.2 = 0.3, round(0.1 + 0.2, 2) = 0.3;
-- çıktı: 0|1

'3.0' + 1 sonucunun 4 değil 4.0 olduğuna dikkat edin: belgelere göre işleçlerdeki örtük dönüşüm, CAST AS NUMERIC'ten farklı olarak ondalık görünen metni REAL olarak tutar. Son örnek kayan noktalı sayıların bilinen sınırını gösteriyor; eşitlik karşılaştırmasında round() kullanmak daha güvenlidir. Ondalık sayıların metne nasıl çevrildiği 3.53.0 sürümüyle değişti; bu yüzden yazıda ondalık sonuçların görünümüne bağlı örneklerden kaçındık. Ayrıntılar matematik fonksiyonları yazısında.

Katı tip denetimi gerekiyorsa: STRICT

Esnek tipleme her projeye uymayabilir. 3.37.0 sürümünden beri tabloyu STRICT seçeneğiyle oluşturarak diğer veritabanlarındaki gibi katı denetim isteyebilirsiniz. Bu tablolarda değer, sütun tipine kayıpsız çevrilemiyorsa ekleme reddedilir:

sql
CREATE TABLE olcum(deger INTEGER) STRICT;
INSERT INTO olcum VALUES ('42');
INSERT INTO olcum VALUES ('kırk iki');
-- hata: cannot store TEXT value in INTEGER column olcum.deger
SELECT deger, typeof(deger) FROM olcum;
-- çıktı: 42|integer

'42' metni yine tamsayıya çevrilerek kabul edildi, ama sayıya benzemeyen metin hata verdi. STRICT tabloların izin verdiği tip adları, ANY tipi ve esnek tiplemenin gerekçesi SQLite STRICT tablolar yazısında anlatılıyor.

Sık yapılan hatalar

Sık yapılan hatalar: Kodları sayı sütununda tutmak, Sayıları metin sütununda tutmak, VARCHAR(n) ile uzunluk sınırı beklemek, Tip adını başka veritabanından kopyalamak
  • Kodları sayı sütununda tutmak: Baştaki sıfırlar kaybolur. Posta kodu, telefon ve ürün kodu için TEXT kullanın.
  • Sayıları metin sütununda tutmak: Sıralama ve aralık sorguları metin karşılaştırmasıyla çalışır; '500' değeri '60' değerinden küçük sayılır.
  • VARCHAR(n) ile uzunluk sınırı beklemek: SQLite uzunluğu yok sayar. Sınır gerekiyorsa CHECK(length(sutun) <= n) ekleyin; kısıtlar CREATE TABLE yazısında.
  • Tip adını başka veritabanından kopyalamak: FLOATING POINT veya STRING gibi adlar beklemediğiniz eğilimi verir.
  • Hatalı veriyi fark etmemek: Sayı sütununa yazılan bozuk metin hata vermeden saklanır. Uygulamada doğrulama yapın, gerekirse typeof() ile kontrol sorgusu çalıştırın veya STRICT tablo kullanın.

Sık Sorulan Sorular

SQLite'ta BOOLEAN veya DATETIME tipi var mı?

Ayrı bir depolama sınıfı olarak yoktur. Mantıksal değerler 0 ve 1 tamsayılarıyla, tarihler metin, ondalık sayı veya tamsayı olarak saklanır. BOOLEAN ve DATETIME tip adlarını yine de yazabilirsiniz; ikisi de NUMERIC eğilimi alır.

Bir sütundaki değerlerin gerçek tiplerini nasıl görürüm?

SELECT typeof(sutun), count(*) FROM tablo GROUP BY 1; sorgusu her sınıftan kaç değer bulunduğunu gösterir. Beklemediğiniz bir sınıf görürseniz veri girişini kontrol edin.

Eğilim karşılaştırmaları nasıl etkiler?

Bir sütunu sabitle karşılaştırırken sütunun eğilimi sabite uygulanır. Bu yüzden TEXT sütunda sayılar metin olarak, INTEGER sütunda metinler sayı olarak karşılaştırılır. İki sabit arasında ise dönüşüm yapılmaz.

Görünüm ve alt sorgu sütunlarının eğilimi nedir?

Doğrudan bir tablo sütununa karşılık gelen görünüm sütunu o sütunun eğilimini alır; a+c veya bir sabit gibi ifadelerin eğilimi yoktur. Belgeler, birleşik (UNION gibi) sorgularda farklı eğilimleri karıştırmamanızı, aksi hâlde hangi eğilimin uygulanacağının belirsiz olduğunu belirtir.

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 · SQLite STRICT tablolar · SQLite CREATE TABLE · SQLite COLLATE ve NOCASE · SQLite NULL davranışı