SQLite'ta metin işlemek için yerleşik skaler fonksiyonları kullanırsınız: parça almak için substr(), aramak için instr(), değiştirmek için replace(), kenar temizliği için trim() ailesi, birleştirmek için ||, concat() ve concat_ws(), biçimlendirmek için ise format() (eski adıyla printf()). Bu fonksiyonların hepsi varsayılan derlemede bulunur; ek bir eklenti yüklemeniz gerekmez.

Bu yazıda her fonksiyonun tam davranışını, Türkçe karakterlerle çalışırken ortaya çıkan tuzakları (karakter ile bayt farkı, yalnızca ASCII harflerde çalışan upper() ve lower(), baytla ölçülen biçim genişliği) ve küçük bir müşteri tablosu üzerinde gerçekçi bir temizlik örneğini bulacaksınız. Desen eşleştirme (LIKE, GLOB, REGEXP) ayrı bir konu olduğu için SQLite LIKE, GLOB ve REGEXP yazısında ele alınıyor.

Metin, karakter ve bayt: length() ile octet_length() farkı

Metin, karakter ve bayt: length() ile octet_length() farkı

SQLite metni veritabanı kodlamasıyla saklar; günümüzde bu neredeyse her zaman UTF-8'dir. UTF-8'de İngiliz alfabesindeki harfler tek bayt, Türkçedeki ç, ğ, ı, İ, ö, ş, ü gibi harfler iki bayt yer kaplar. length(X) bir metin için Unicode kod noktası sayısını, yani okurun gördüğü karakter sayısını döndürür. Bayt sayısına ihtiyacınız varsa SQLite 3.43.0 ile gelen octet_length(X) fonksiyonunu kullanırsınız.

sql
SELECT length('İstanbul'), octet_length('İstanbul');
-- çıktı: 8|9
SELECT length('ğüşıöç'), octet_length('ğüşıöç');
-- çıktı: 6|12
SELECT length(12.50), length(NULL), length(x'0102FF');
-- çıktı: 4||3

Örnekteki ayrıntılara dikkat edin. İstanbul sekiz karakterdir, ama baştaki İ iki bayt tuttuğu için bayt uzunluğu dokuzdur. Sayısal bir argüman verdiğinizde length() sayının metin hâlinin uzunluğunu verir: 12.50 değeri REAL olarak 12.5 biçiminde yazıldığı için sonuç 4 çıkar. NULL için sonuç NULL, BLOB için ise bayt sayısıdır.

Belgede vurgulanan bir performans farkı da var: metin değerlerde length() karakterleri sayabilmek için değerin tamamını belleğe okumak zorundadır. octet_length() ise bir tablo sütununa uygulandığında bayt uzunluğunu kayıt üst verisinden hesaplayabildiği için büyük metin ve BLOB sütunlarında içeriği diskten okumadan çalışır. Megabaytlarca veri tutan sütunlarda "en uzun kayıt hangisi" gibi sorular için bu fark önemlidir.

substr() ve instr(): parça almak ve aramak

substr(X, Y, Z) X metninde Y'inci karakterden başlayan Z karakterlik parçayı verir. İlk karakterin numarası 1'dir; Z verilmezse metnin sonuna kadar alınır. Y negatifse başlangıç noktası sağdan sayılır; Z negatifse Y'inci karakterden önceki |Z| karakter döndürülür. Metinlerde konumlar karakter (kod noktası) cinsindendir, BLOB değerlerde ise bayt cinsinden. SQLite 3.34 ile birlikte substring() adı da substr() için eş ad olarak kabul edilir.

instr(X, Y) Y metninin X içindeki ilk geçtiği yerin 1 tabanlı konumunu döndürür; bulunamazsa 0 verir. Argümanlardan biri NULL ise sonuç NULL'dur. İki fonksiyon birlikte kullanıldığında bir ayraçtan sonraki kısmı almak kolaylaşır: aşağıdaki son satır bir e-posta adresinden alan adını çıkarır.

sql
SELECT substr('keydal.tr', 1, 6), substr('keydal.tr', -2), substr('keydal.tr', 8);
-- çıktı: keydal|tr|tr
SELECT substr('abcdef', 4, -2), substring('Çağrı', 2, 3);
-- çıktı: bc|ağr
SELECT instr('kullanici@ornek.com', '@'), instr('abc', 'z'), quote(instr('abc', NULL));
-- çıktı: 10|0|NULL
SELECT substr('kullanici@ornek.com', instr('kullanici@ornek.com', '@') + 1);
-- çıktı: ornek.com

Negatif uzunluk az bilinen ama işe yarayan bir özelliktir: substr('abcdef', 4, -2) dördüncü karakter olan d harfinden önceki iki karakteri, yani bc değerini verir. substring('Çağrı', 2, 3) örneği de konumların bayt değil karakter olduğunu gösterir; aksi hâlde iki baytlık ğ ortadan bölünürdü.

replace(), trim(), ltrim() ve rtrim()

replace(X, Y, Z) X içindeki her Y geçişini Z ile değiştirir. Karşılaştırma BINARY kurala göre yapılır, yani harf büyüklüğü önemlidir: replace('Ankara', 'a', 'e') baştaki büyük A harfine dokunmaz. Y boş metinse X değişmeden döner. Z metin değilse işlemden önce UTF-8 metne çevrilir.

trim(X, Y), ltrim(X, Y) ve rtrim(X, Y) fonksiyonlarında ikinci argüman silinecek karakter kümesidir, bir alt metin değildir. Y verilmezse yalnızca boşluk karakteri silinir. Kümenin anlamını en iyi son örnek gösterir: rtrim('dosya.txt.bak', '.kab') sağdan başlayarak nokta, k, a ve b karakterlerinden herhangi biri geldiği sürece silmeye devam eder ve t harfinde durur.

sql
SELECT replace('2026/11/07', '/', '-'), replace('Ankara', 'a', 'e'), replace('abc', '', 'X');
-- çıktı: 2026-11-07|Ankere|abc
SELECT '[' || trim('  merhaba  ') || ']', trim('--sqlite--', '-'), ltrim('000123', '0');
-- çıktı: [merhaba]|sqlite|123
SELECT rtrim('dosya.txt.bak', '.kab');
-- çıktı: dosya.txt

Birleştirme: ||, concat() ve concat_ws()

Birleştirme: ||, concat() ve concat_ws(): concat(X, ...), concat_ws(SEP, X, ...), Boş metin ('') NULL değildir; concat_ws() boş metni atlamaz, iki ayraç yan yana...

Standart SQL birleştirme işleci || işaretidir ve SQLite'ta da en yaygın yöntem budur. İşlecin bilinmesi gereken tek kuralı şudur: işlenenlerden biri NULL ise sonuç NULL olur. Boş olabilecek sütunları birleştirirken bu kural beklenmedik biçimde tüm satırı NULL yapar. SQLite 3.44.0 ile eklenen concat() ve concat_ws() fonksiyonları bu sorunu çözer; sürüm notlarına göre PostgreSQL, SQL Server ve MySQL ile uyumlu olarak eklenmişlerdir.

  • concat(X, ...): NULL olmayan tüm argümanların metin hâllerini uç uca ekler. Hepsi NULL ise boş metin döndürür.
  • concat_ws(SEP, X, ...): ilk argümanı ayraç olarak kullanır ve NULL olmayan argümanları bu ayraçla birleştirir. Ayraç NULL ise sonuç NULL olur; ayraç dışındaki her şey NULL ise boş metin döner.
  • Boş metin ('') NULL değildir; concat_ws() boş metni atlamaz, iki ayraç yan yana gelir.
sql
SELECT quote('a' || NULL), concat('a', NULL, 'b'), quote(concat(NULL, NULL));
-- çıktı: NULL|ab|''
SELECT concat_ws('-', '2026', NULL, '11', '07'), concat_ws(', ', 'x', '', 'y');
-- çıktı: 2026-11-07|x, , y
SELECT concat_ws(NULL, 'a', 'b') IS NULL;
-- çıktı: 1

Son noktayla ilgili bir sürüm notu: boş metinlerin concat_ws() sonucuna dahil edilmesi 3.50.2 sürümünde düzeltilen bir hatadır. Daha eski bir SQLite kullanıyorsanız x, , y yerine farklı bir sonuç görebilirsiniz; değişikliğin kaydı sürüm geçmişi sayfasındadır. Satırları gruplayarak birleştirmek ise skaler bir işlem değil, toplama işlemidir: group_concat() ve string_agg() için GROUP BY ve aggregate fonksiyonlar yazısına bakın.

format() ve printf(): biçimli çıktı üretmek

format(FORMAT, ...) C dilindeki printf() mantığıyla çalışır: ilk argüman bir şablondur, şablondaki her % ifadesi sıradaki argümanla doldurulur. Fonksiyonun ilk adı printf() idi; diğer veritabanlarıyla uyum için 3.38.0 sürümünde format() olarak yeniden adlandırıldı ve eski ad, mevcut kodu bozmamak için eş ad olarak korunuyor. SQLite bu biçimlendiriciyi kendi içinde barındırdığından çıktı her platformda ve her yerel ayarda aynıdır; ondalık ayırıcı her zaman noktadır.

Bir ifade şu parçalardan oluşur: %[bayraklar][genişlik][.duyarlık]tür. Sık kullanılan türler d (tamsayı), f (ondalık), e ve g (üstel ya da otomatik), x ve X (onaltılık), o (sekizlik), s (metin) ve c türleridir. Bayraklar arasında - sola yaslar, 0 sıfırla doldurur, + pozitif sayılara işaret ekler, virgül (,) binlik ayraç koyar.

sql
SELECT format('%05d|%.2f|%-8s|%8s|', 42, 3.14159, 'ad', 'soyad');
-- çıktı: 00042|3.14|ad      |   soyad|
SELECT format('%,d', 1234567), format('%x %X %o', 255, 255, 8), format('%%%d', 50);
-- çıktı: 1,234,567|ff FF 10|%50
SELECT format('%q', 'O''Reilly'), format('%Q', 'O''Reilly'), format('%Q', NULL);
-- çıktı: O''Reilly|'O''Reilly'|NULL
SELECT format('%d adet %s', 3), format('%s-%s', 'a', 'b', 'c');
-- çıktı: 3 adet |a-b
SELECT format('%c', 'xyz'), format('%.3c', '*');
-- çıktı: x|***

Örnekler birkaç SQLite'a özgü davranışı da gösteriyor. Argüman sayısı yetmezse eksik argümanlar NULL kabul edilir; NULL sayısal biçimlerde 0, %s için boş metin olur. Fazla argümanlar sessizce yok sayılır. %c SQL tarafında bir metin alır ve ilk karakterini yazar; duyarlık verilirse o karakteri N kez tekrarlar, bu da standart printf'te olmayan bir eklentidir. Virgül bayrağı her zaman virgül kullanır; Türkçe yazımdaki nokta binlik ayracı için çıktıya replace() uygulamanız gerekir.

BiçimNe yaparNot
%qTek tırnakları ikilerSQL metin sabitinin içine koymak için
%Q%q gibi, ayrıca iki uca tırnak eklerNULL argüman tırnaksız NULL yazar
%wÇift tırnakları ikilerÇift tırnaklı tanımlayıcılar (tablo ve sütun adları) için
!Genişlik ve duyarlığı karakterle ölçerTürkçe metinde hizalama için gerekli
%,dBinlik ayraç olarak virgül eklerStandart dışı bir eklenti
%j ve %JMetni JSON metin sabiti olarak yazarSQLite 3.54.0 ve sonrası

%q ve %Q biçimleri, SQL ifadesi üreten betiklerde (ör. bir tablodan diğerine aktarılacak INSERT satırları) tırnak kaçışını sizin yerinize yapar. Yine de uygulama kodundan gelen kullanıcı girdisini SQL metnine bu yolla gömmek yerine parametreli sorgu kullanmalısınız; nedenini SQL injection önleme yazımızda anlattık.

Türkçe metinde genişlik: bayt mı karakter mi?

Belgede açıkça belirtildiği üzere genişlik ve duyarlık varsayılan olarak bayt cinsinden ölçülür. Yalnızca ASCII içeren metinde fark görmezsiniz, ancak Türkçe harfler iki bayt olduğu için hizalı bir liste üretirken sütunlar kayar. Alternatif biçim 2 bayrağı olan ! eklendiğinde genişlik ve duyarlık karakterle ölçülür.

sql
SELECT format('[%-6s]', 'çay'), format('[%!-6s]', 'çay'), format('%!.3s', 'çğüşö');
-- çıktı: [çay  ]|[çay   ]|çğü

çay üç karakter ama dört bayttır. %-6s altı bayta tamamlamak için iki boşluk ekler, %!-6s ise altı karaktere tamamlamak için üç boşluk ekler. Aynı kural duyarlık için de geçerlidir: %.3s ilk üç baytı keser ve çok baytlı bir harfi ortadan bölebilir; %!.3s ilk üç karakteri güvenle alır.

Karakter kodları: unicode(), char(), hex(), unhex(), unistr() ve quote()

unicode(X) metnin ilk karakterinin Unicode kod noktasını, char(X1, X2, ...) ise verilen kod noktalarından oluşan metni döndürür. hex(X) argümanını BLOB olarak yorumlar ve büyük harfli onaltılık gösterimini üretir. Burada bir tuzak var: argüman sayıysa önce metne çevrilir, sonra o metnin baytları yazılır. Bu yüzden hex(12345678) sayının ikili gösterimini değil, "12345678" metninin baytlarını verir.

SQLite 3.41.0 ile gelen unhex(X, Y) bunun tersidir: onaltılık metni BLOB değerine çevirir. X içinde onaltılık olmayan ve Y kümesinde de bulunmayan bir karakter varsa sonuç NULL olur; basamaklar her zaman çiftler hâlinde bitişik olmalıdır. 3.50.0 ile eklenen unistr(X) ise \u00e7 gibi ters eğik çizgi kaçışlarını gerçek karakterlere çevirir. SQL metin sabitlerinde C tarzı kaçışlar desteklenmediği için, kontrol karakteri veya klavyede olmayan bir harf yazmanın düzgün yolu budur. quote(X) değeri bir SQL sabiti olarak yazar: metni tırnaklar, BLOB değerini onaltılık sabit biçimine çevirir.

sql
SELECT unicode('ş'), char(351, 105, 107), hex('ş'), hex(12345678);
-- çıktı: 351|şik|C59F|3132333435363738
SELECT unhex('C59F') = CAST('ş' AS BLOB), CAST(unhex('C59F') AS TEXT), unhex('XY') IS NULL;
-- çıktı: 1|ş|1
SELECT unistr('\u00e7ay'), quote('it''s'), quote(x'ABCD'), quote(12);
-- çıktı: çay|'it''s'|X'ABCD'|12

upper() ve lower(): yalnızca ASCII harfler

Türkçe veriyle çalışan geliştiricileri en çok şaşırtan nokta budur. Belgeye göre yerleşik lower() yalnızca ASCII harfleri küçültür, upper() da yalnızca ASCII küçük harfleri büyütür. ç, ğ, ı, ö, ş, ü ve bunların büyükleri olduğu gibi kalır. Ayrıca ASCII i harfi her zaman I olur; Türkçedeki noktalı büyük İ karşılığı üretilmez. Aşağıdaki sonuçlar standart bir SQLite derlemesinden alınmıştır.

sql
SELECT upper('istanbul ığdır'), lower('İZMİR ÇORUM');
-- çıktı: ISTANBUL ığDıR|İzmİr Çorum
SELECT upper('sqlite abc'), lower('ŞEKER') = 'şeker';
-- çıktı: SQLITE ABC|0

Belge, ASCII dışı harflerde büyük-küçük harf dönüşümü için ICU eklentisini yüklemenizi önerir. SSS sayfası da tam Unicode desteğinin kütüphane boyutunu neredeyse ikiye katlayacağını, bu nedenle varsayılan derlemede bulunmadığını açıklar. Bu bir uyarı da getirir: ICU ile derlenmiş bir komut satırı aracı veya kütüphane aynı sorgulara farklı cevap verir. Örneğin bu yazıyı hazırlarken kullandığımız ICU etkin komut satırı aracında upper('sqlite abc') sonucu SQLİTE ABC çıktı. Uygulamanızın hangi derlemeyle çalıştığını bilmeden harf dönüşümüne güvenmeyin.

Uygulamalı örnek: müşteri verisini temizlemek

Bir formdan gelen müşteri kayıtlarını düşünün: telefon numaraları boşluk, parantez ve tire içeriyor, e-posta adreslerinden alan adını raporlamak istiyorsunuz ve sonuçları hizalı bir metin olarak dışa aktarmanız gerekiyor. Aşağıdaki oturum bu işlerin tamamını yalnızca yerleşik fonksiyonlarla yapar.

sql
CREATE TABLE musteri(
  id      INTEGER PRIMARY KEY,
  ad      TEXT,
  eposta  TEXT,
  telefon TEXT
);
INSERT INTO musteri(ad, eposta, telefon) VALUES
  ('Ayşe Yılmaz', 'ayse@ornek.com', ' 0532 111 22 33 '),
  ('Can Er', 'can.er@firma.com.tr', '(0533)-444-55-66');

SELECT id,
       substr(eposta, instr(eposta, '@') + 1) AS alan_adi,
       replace(replace(replace(replace(trim(telefon), ' ', ''), '(', ''), ')', ''), '-', '') AS tel
FROM musteri;
-- çıktı:
-- 1|ornek.com|05321112233
-- 2|firma.com.tr|05334445566

SELECT format('%!-12s|%3d', ad, id) FROM musteri;
-- çıktı:
-- Ayşe Yılmaz |  1
-- Can Er      |  2

SELECT format('%-12s|%3d', ad, id) FROM musteri;
-- çıktı:
-- Ayşe Yılmaz|  1
-- Can Er      |  2

SELECT format('INSERT INTO yedek VALUES(%d, %Q);', id, ad) FROM musteri;
-- çıktı:
-- INSERT INTO yedek VALUES(1, 'Ayşe Yılmaz');
-- INSERT INTO yedek VALUES(2, 'Can Er');

Telefon sütununda iç içe replace() çağrıları her istenmeyen karakteri sırayla siler; trim() baştaki ve sondaki boşlukları önceden temizler. İkinci ve üçüncü sorgular aynı hizalamayı iki farklı biçimle dener: "Ayşe Yılmaz" on bir karakter ama on üç bayttır. %!-12s karakterle ölçtüğü için sona bir boşluk ekler ve dikey çizgiler alt alta gelir. %-12s ise on üç baytı zaten on ikiden uzun saydığı için hiç boşluk eklemez ve satırlar kayar. Son sorgu, %Q sayesinde tırnakları doğru kaçışlanmış INSERT satırları üretir.

Temizlenmiş değeri kalıcı yapmak istiyorsanız aynı ifadeyi bir UPDATE içinde kullanabilir veya her okumada hesaplanan bir sütun tanımlayabilirsiniz. İkinci yol için hesaplanan sütunlar yazısına bakın; veri değiştirme komutlarının ayrıntıları ise INSERT, UPDATE ve DELETE yazısında.

Sık yapılan hatalar

Sık yapılan hatalar: NULL ile || kullanmak, Bayt ile karakteri karıştırmak, trim() ailesinin ikinci argümanını alt metin sanmak, upper() ve lower() ile Türkçe harf dönüşümü...
  • NULL ile || kullanmak: bir işlenen NULL ise tüm sonuç NULL olur. Boş olabilen sütunlarda concat(), concat_ws() veya coalesce() kullanın.
  • Bayt ile karakteri karıştırmak: length() karakter, octet_length() bayt sayar; format() genişliği ise ! olmadan bayt ölçer.
  • trim() ailesinin ikinci argümanını alt metin sanmak: argüman bir karakter kümesidir, sıra ve bütünlük aranmaz.
  • upper() ve lower() ile Türkçe harf dönüşümü beklemek: standart derleme yalnızca ASCII harfleri dönüştürür.
  • hex() ile sayının ikili gösterimini beklemek: sayılar önce metne çevrilir.
  • C tarzı kaçış yazmak: SQLite metin sabitlerinde ters eğik çizgi özel anlam taşımaz; satır sonu için char(10) veya unistr() kullanın. Metin içindeki tek tırnak iki tek tırnakla yazılır.

Yeni sürümlerde: %J ve %j

SQLite 3.54.0 sürüm notları format() fonksiyonuna JSON metin sabiti üreten %J ve %j dönüşümlerinin eklendiğini bildirir. %J metni çift tırnaklarla sarar ve gerekli kaçışları yapar; NULL argüman için tırnaksız null yazar. %j aynı kaçışı yapar ama tırnak eklemez, böylece daha büyük bir JSON metninin parçası olarak kullanılabilir. Aşağıdaki örnek bu sürümü gerektirir; elimizdeki çalışma ortamları daha eski olduğu için çıktısını göstermiyoruz.

sql
-- SQLite 3.54.0 ve sonrası gerekir (format() için %J ve %j)
SELECT format('{"ad": %J}', 'Ali "Usta"');
SELECT format('%J', NULL);

JSON üretmenin daha yapısal yolu json_object() ve json_quote() fonksiyonlarıdır; bunlar SQLite JSON fonksiyonları yazısında anlatılıyor.

Sık Sorulan Sorular

SQLite'ta bir metni ayraçla parçalara bölen split() fonksiyonu var mı?

Yerleşik fonksiyon listesinde böyle bir fonksiyon yoktur. Tek bir ayraçtan önceki ve sonraki kısmı instr() ve substr() ile alabilirsiniz. Değişken sayıda parçaya bölmek için özyinelemeli bir CTE yazılabilir; bunun mantığı WITH ve recursive CTE yazısında anlatılıyor. Veriniz JSON dizisi biçimindeyse json_each() daha basit bir çözümdür.

printf() ile format() arasında fark var mı?

Hayır. printf() fonksiyonun eski adıdır ve format() için eş ad olarak korunur. İkisi aynı biçimlendiriciyi çalıştırır; yeni kodda format() yazmanız diğer veritabanlarıyla uyum açısından daha okunaklıdır.

length() neden sayıda beklenmedik bir değer döndürüyor?

Sayısal bir değerin uzunluğu, o sayının metin gösteriminin uzunluğudur. REAL değerler metne çevrilirken gereksiz sıfırlar atılır, bu yüzden 12.50 dört karakter sayılır. Sütun tipinin değeri nasıl sakladığı için veri tipleri ve type affinity yazısına bakabilirsiniz.

Türkçe harflerde büyük-küçük harf duyarsız arama nasıl yapılır?

Standart derlemede ne lower() ne NOCASE ne de LIKE ASCII dışındaki harfleri eşler. Seçenekler şunlardır: ICU eklentisi ya da uygulamanın tanımladığı fonksiyon ve sıralama kuralları, veya aranacak metni önceden normalize edip ayrı bir sütunda tutmak. Ayrıntılı karşılaştırma LIKE yazısında ve COLLATE yazısında.

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 · LIKE, GLOB ve REGEXP · COLLATE ve NOCASE · JSON fonksiyonları · Matematik fonksiyonları · NULL davranışı ve coalesce