SQLite'ta tablo, CREATE TABLE ad (sütun tanımları, tablo kısıtları) biçiminde oluşturulur. Her sütun tanımı bir ad, isteğe bağlı bir tip adı ve sıfır ya da daha fazla kısıttan oluşur. Desteklenen kısıtlar PRIMARY KEY, NOT NULL, UNIQUE, CHECK ve FOREIGN KEY'dir; DEFAULT ve COLLATE da teknik olarak veriyi kısıtlamasalar bile sütun kısıtı olarak yazılır. Tablo zaten varsa hata almamak için IF NOT EXISTS, bir sorgunun sonucundan tablo üretmek için CREATE TABLE ... AS SELECT kullanılır.

Bu yazıda her kısıtı çalıştırılmış örneklerle ve hata mesajlarıyla birlikte ele alıyor, SQLite'a özgü inceliklere, örneğin PRIMARY KEY sütunlarının NULL kabul edebilmesine ve CREATE TABLE AS ile kopyalanan tabloların kısıtlarını kaybetmesine dikkat çekiyoruz. Sütun tiplerinin aslında yalnızca bir eğilim belirlediğini bilmiyorsanız önce SQLite veri tipleri ve type affinity yazısını okuyun. Serinin tamamı için SQLite öğrenme rehberine bakabilirsiniz.

CREATE TABLE ifadesinin parçaları

CREATE TABLE ifadesinin parçaları

Bir CREATE TABLE ifadesi şunları belirler: tablonun adı, hangi veritabanında (main, temp veya ATTACH ile bağlanmış bir veritabanı) oluşturulacağı, her sütunun adı, bildirilen tipi, varsayılan değeri ve varsayılan sıralama kuralı, isteğe bağlı birincil anahtar, kısıtlar, hesaplanan sütunlar ve tablonun WITHOUT ROWID ya da STRICT olup olmadığı. Tam söz dizimi için resmî belgedeki söz dizimi diyagramına bakmanızı öneririz. Aşağıdaki tablo, kısıtların çoğunu bir arada kullanan gerçekçi bir örnektir:

sql
CREATE TABLE urun(
  id       INTEGER PRIMARY KEY,
  sku      TEXT NOT NULL UNIQUE,
  ad       TEXT NOT NULL COLLATE NOCASE,
  fiyat    REAL NOT NULL CHECK (fiyat >= 0),
  stok     INTEGER NOT NULL DEFAULT 0,
  durum    TEXT NOT NULL DEFAULT 'aktif',
  eklendi  TEXT DEFAULT CURRENT_TIMESTAMP,
  CONSTRAINT durum_gecerli CHECK (durum IN ('aktif', 'pasif'))
);
INSERT INTO urun(sku, ad, fiyat) VALUES ('KLM-01', 'Kalem', 12.5);
SELECT id, sku, ad, fiyat, stok, durum, length(eklendi) FROM urun;
-- çıktı: 1|KLM-01|Kalem|12.5|0|aktif|19

Yalnızca üç sütuna değer verdik; geri kalanlar varsayılanlarını aldı. id sütunu INTEGER PRIMARY KEY olduğu için otomatik olarak 1 değerini aldı. eklendi sütunundaki CURRENT_TIMESTAMP, ekleme anının UTC zamanını YYYY-AA-GG SS:DD:ss biçiminde, yani 19 karakterlik bir metin olarak yazdı; çıktıda zamanın kendisi değişeceği için yalnızca uzunluğunu gösterdik. COLLATE NOCASE ise ad sütunundaki karşılaştırmaların ASCII harflerde büyük-küçük harf ayrımı yapmamasını sağlar; Türkçe harflerdeki sınırları COLLATE yazısında anlatıyoruz.

NOT NULL, UNIQUE ve CHECK kısıtları

Kısıtlar INSERT ve UPDATE sırasında denetlenir. Kurala uymayan bir satır geldiğinde SQLite ifadeyi durdurur ve hangi kısıtın bozulduğunu söyleyen bir hata döndürür:

sql
INSERT INTO urun(sku, ad, fiyat) VALUES ('KLM-01', 'Kurşun kalem', 9);
-- hata: UNIQUE constraint failed: urun.sku
INSERT INTO urun(sku, ad, fiyat) VALUES ('DFT-01', 'Defter', -5);
-- hata: CHECK constraint failed: fiyat >= 0
INSERT INTO urun(sku, ad, fiyat, durum) VALUES ('DFT-01', 'Defter', 40, 'silindi');
-- hata: CHECK constraint failed: durum_gecerli
INSERT INTO urun(sku, fiyat) VALUES ('DFT-01', 40);
-- hata: NOT NULL constraint failed: urun.ad
INSERT INTO urun(sku, ad, fiyat, stok) VALUES ('DFT-01', 'Defter', 40, NULL);
-- hata: NOT NULL constraint failed: urun.stok
SELECT count(*) FROM urun;
-- çıktı: 1

Hata mesajları kısıt türünü ve sütunu açıkça söyler. CHECK kısıtına CONSTRAINT ad ile bir isim verdiğinizde mesajda ifadenin kendisi yerine bu ad görünür; çok sayıda CHECK kısıtı olan tablolarda hangi kuralın bozulduğunu anlamak böylece kolaylaşır. Son örnek önemli bir ayrıntıyı gösteriyor: sütuna açıkça NULL yazarsanız varsayılan değer devreye girmez ve NOT NULL kısıtı bozulur. Varsayılan değer yalnızca sütun INSERT ifadesinde hiç geçmediğinde kullanılır.

Belgelerdeki kurallar özetle şöyledir. NOT NULL yalnızca sütun tanımına yazılabilir, tablo kısıtı olarak yazılamaz. UNIQUE kısıtında NULL değerler birbirinden farklı sayılır; bu yüzden UNIQUE bir sütunda birden çok NULL bulunabilir. CHECK ifadesi her ekleme ve güncellemede değerlendirilir ve sonuç NUMERIC'e çevrilir; sonuç 0 ise kısıt bozulmuş sayılır, NULL ya da sıfırdan farklı herhangi bir değer ise geçer. Bu nedenle CHECK (fiyat >= 0) NULL bir fiyatı engellemez; NULL istemiyorsanız NOT NULL da eklemelisiniz. CHECK ifadesi alt sorgu içeremez. UNIQUE ve PRIMARY KEY kısıtları ise çoğu durumda arka planda benzersiz bir indeks oluşturularak uygulanır.

DEFAULT: sabit, ifade ve tarih

DEFAULT değeri NULL, bir metin, bir BLOB, işaretli bir sayı veya parantez içinde sabit bir ifade olabilir. Ayrıca büyük-küçük harf duyarsız CURRENT_TIME, CURRENT_DATE ve CURRENT_TIMESTAMP anahtar kelimeleri kullanılabilir; bunlar sırasıyla SS:DD:ss, YYYY-AA-GG ve ikisinin birleşimi biçiminde UTC zamanı üretir. Parantez içindeki ifade, eklenen her satır için ayrıca değerlendirilir. Belgelere göre ifadenin sabit sayılması için alt sorgu, sütun ya da tablo referansı, bağlı parametre veya çift tırnaklı metin içermemesi gerekir:

sql
CREATE TABLE olay(
  id   INTEGER PRIMARY KEY,
  kod  TEXT DEFAULT (lower(hex(randomblob(4)))),
  yil  INTEGER DEFAULT (2000 + 26),
  gun  TEXT DEFAULT CURRENT_DATE
);
INSERT INTO olay DEFAULT VALUES;
INSERT INTO olay DEFAULT VALUES;
SELECT id, length(kod), yil, length(gun) FROM olay;
-- çıktı:
-- 1|8|2026|10
-- 2|8|2026|10
SELECT count(DISTINCT kod) FROM olay;
-- çıktı: 2

CREATE TABLE hatali(x DEFAULT (abs(y)));
-- hata: default value of column [x] is not constant

İki satırın kod değerleri farklı çıktı; çünkü parantezli ifade her satırda yeniden hesaplandı. INSERT ... DEFAULT VALUES biçimi bütün sütunları varsayılanlarıyla doldurur. Başka bir sütuna başvuran varsayılan ise tablo oluşturulurken reddedildi. Bir sütunun değerini diğer sütunlardan hesaplamak istiyorsanız doğru araç hesaplanan sütunlardır; bunları generated columns yazısında anlatıyoruz.

PRIMARY KEY ve NULL tuzağı

Bir tabloda en fazla bir PRIMARY KEY bulunabilir. Tek sütunlu anahtar sütun tanımına, çok sütunlu anahtar ise PRIMARY KEY (a, b) biçiminde tablo kısıtı olarak yazılır. Anahtar yalnızca sütun adları içerebilir, ifade içeremez. Tek sütunlu ve bildirilen tipi tam olarak INTEGER olan anahtar özeldir: satırın rowid değerinin takma adı olur ve NULL yazıldığında otomatik bir tamsayı alır. INT veya BIGINT gibi başka tip adları bu özelliği vermez. Bu konunun ayrıntıları PRIMARY KEY, rowid ve AUTOINCREMENT yazısında.

SQL standardına göre PRIMARY KEY her zaman NOT NULL anlamına gelir. SQLite'ın eski sürümlerindeki bir hata yüzünden bu kural uygulanmaz ve geriye uyumluluk için hata düzeltilmemiştir. INTEGER PRIMARY KEY, WITHOUT ROWID tablo, STRICT tablo veya açıkça NOT NULL yazılmış sütunlar dışında, PRIMARY KEY sütunu NULL kabul eder. Benzersizlik denetiminde NULL değerler birbirinden farklı sayıldığı için birden çok NULL anahtar bile eklenebilir:

sql
CREATE TABLE kod(k TEXT PRIMARY KEY, aciklama TEXT);
INSERT INTO kod VALUES (NULL, 'ilk'), (NULL, 'ikinci');
SELECT count(*) FROM kod WHERE k IS NULL;
-- çıktı: 2

CREATE TABLE kod2(k TEXT PRIMARY KEY NOT NULL, aciklama TEXT);
INSERT INTO kod2 VALUES (NULL, 'ilk');
-- hata: NOT NULL constraint failed: kod2.k

Kural basittir: INTEGER dışındaki her PRIMARY KEY sütununa NOT NULL ekleyin. Diğer veritabanlarından şema taşırken bu ayrıntı kolayca gözden kaçar.

Çakışma çözümü: ON CONFLICT yan tümcesi

Çakışma çözümü: ON CONFLICT yan tümcesi: ABORT (varsayılan), ROLLBACK, FAIL, IGNORE

Bir kısıt bozulduğunda ne olacağını çakışma çözüm algoritması belirler. Varsayılan ABORT'tur: ifade durdurulur ve o ifadenin yaptığı değişiklikler geri alınır, ama aynı işlem içindeki önceki ifadeler korunur. PRIMARY KEY, UNIQUE ve NOT NULL kısıtlarına tanım sırasında ON CONFLICT ile başka bir varsayılan verebilirsiniz: ROLLBACK bütün işlemi geri alır, FAIL ifadeyi durdurur ama o ana kadarki değişiklikleri bırakır, IGNORE sorunlu satırı sessizce atlar, REPLACE çakışan eski satırı silip yenisini yazar. CHECK kısıtları için algoritma her zaman ABORT'tur.

AlgoritmaBozan ifadeye ne olurÖnceki ifadeler ve işlem
ABORT (varsayılan)Durur, kendi değişiklikleri geri alınırKorunur, işlem açık kalır
ROLLBACKDururBütün işlem geri alınır
FAILDurur, o ana kadarki değişiklikleri kalırKorunur, işlem açık kalır
IGNORESorunlu satır atlanır, ifade devam ederEtkilenmez
REPLACEÇakışan eski satır silinir, yenisi yazılırEtkilenmez

Belgelere göre REPLACE, NOT NULL ihlalinde NULL yerine sütunun varsayılan değerini yazar; sütunun varsayılanı yoksa ABORT gibi davranır. FAIL ve IGNORE yabancı anahtar ihlallerinde ABORT gibi çalışır. Aşağıdaki örnekte UNIQUE sütuna tanım sırasında IGNORE algoritması verdik:

sql
CREATE TABLE etiket(ad TEXT UNIQUE ON CONFLICT IGNORE, renk TEXT);
INSERT INTO etiket VALUES ('acil', 'kırmızı');
INSERT INTO etiket VALUES ('acil', 'mavi');
SELECT ad, renk FROM etiket;
-- çıktı: acil|kırmızı

İkinci ekleme hata vermeden atlandı ve ilk satır korundu. Tablo tanımına gömülen bu davranış, o tabloya yazan her ifadeyi etkilediği için dikkatli kullanılmalıdır; çoğu zaman tek bir ifadeye özel INSERT OR IGNORE yazmak daha anlaşılırdır. INSERT ve UPDATE içindeki OR yan tümcesi tablo tanımındaki algoritmanın önüne geçer. Bu ON CONFLICT ile 3.24.0 sürümünde gelen UPSERT söz dizimindeki ON CONFLICT farklı şeylerdir; UPSERT için SQLite UPSERT yazısına bakın.

Ad kuralları, IF NOT EXISTS ve TEMP tablolar

sqlite_ ile başlayan adlar iç kullanıma ayrılmıştır ve tablo adı olarak kullanılamaz. Aynı adda bir tablo, indeks veya görünüm varken yeni tablo oluşturmak hatadır. IF NOT EXISTS yazdığınızda aynı adda bir tablo veya görünüm varsa ifade hiçbir şey yapmadan başarıyla biter; mevcut tablonun yapısı sizin yazdığınız tanıma uymasa bile değiştirilmez. Aynı adda bir indeks varsa IF NOT EXISTS de hatayı engellemez. Aynı adda bir tetikleyici bulunması ise sorun değildir.

sql
-- kod tablosu bir önceki bölümde oluşturuldu
CREATE TABLE sqlite_benim(x);
-- hata: object name reserved for internal use: sqlite_benim
CREATE TABLE kod(x);
-- hata: table kod already exists
CREATE TABLE IF NOT EXISTS kod(x);
-- (hata yok, hiçbir şey değişmez)
SELECT sql FROM sqlite_schema WHERE name = 'kod';
-- çıktı: CREATE TABLE kod(k TEXT PRIMARY KEY, aciklama TEXT)

CREATE INDEX ad_idx ON kod(aciklama);
CREATE TABLE IF NOT EXISTS ad_idx(x);
-- hata: there is already an index named ad_idx

TEMP veya TEMPORARY anahtar kelimesiyle oluşturulan tablo, bağlantıya özel geçici veritabanına yazılır ve bağlantı kapanınca kaybolur. Bu tablolar sqlite_schema yerine sqlite_temp_schema içinde listelenir. TEMP ile temp dışında bir şema adını birlikte kullanmak hatadır.

sql
CREATE TEMP TABLE gecici(x);
SELECT name FROM sqlite_temp_schema;
-- çıktı: gecici

CREATE TABLE ... AS SELECT

Bu biçim, bir sorgunun sonucuyla yeni bir tablo oluşturup doldurur. Sütun adları sorgunun sonuç sütunlarından gelir. Bildirilen tipler ise sonuç ifadelerinin eğiliminden türetilir: TEXT için "TEXT", NUMERIC için "NUM", INTEGER için "INT", REAL için "REAL", eğilimsiz ifadeler için boş tip. Belgelere göre böyle oluşturulan tablonun PRIMARY KEY'i ve hiçbir kısıtı olmaz, varsayılan değerler NULL, sıralama kuralları BINARY olur ve satırlara 1'den başlayan ardışık rowid değerleri verilir:

sql
CREATE TABLE satis(id INTEGER PRIMARY KEY, urun TEXT NOT NULL, adet INTEGER, fiyat REAL);
INSERT INTO satis(urun, adet, fiyat) VALUES ('Kalem', 3, 12.5), ('Defter', 1, 40), ('Kalem', 2, 12.5);

CREATE TABLE ozet AS
  SELECT urun, sum(adet) AS toplam_adet, sum(adet * fiyat) AS ciro, 'TL' AS birim
  FROM satis GROUP BY urun;
SELECT sql FROM sqlite_schema WHERE name = 'ozet';
-- çıktı:
-- CREATE TABLE ozet(
--   urun TEXT,
--   toplam_adet,
--   ciro,
--   birim
-- )
SELECT rowid, * FROM ozet ORDER BY rowid;
-- çıktı:
-- 1|Defter|1|40.0|TL
-- 2|Kalem|5|62.5|TL

CREATE TABLE kopya AS SELECT * FROM satis WHERE 0;
SELECT sql FROM sqlite_schema WHERE name = 'kopya';
-- çıktı:
-- CREATE TABLE kopya(
--   id INT,
--   urun TEXT,
--   adet INT,
--   fiyat REAL
-- )

Sonuç dikkat çekici: sum() sonuçlarının ve 'TL' sabitinin eğilimi olmadığı için bu sütunlar tipsiz kaldı. kopya tablosunda ise id sütunu INT tipini aldı; yani artık bir INTEGER PRIMARY KEY değil, NOT NULL kısıtları da kayboldu. WHERE 0 ile yapıyı boş kopyalamak yaygın bir alışkanlıktır, ama özgün tablonun yerine geçecek bir kopya istiyorsanız tabloyu .schema çıktısındaki tam tanımıyla oluşturup veriyi INSERT INTO ... SELECT ile aktarmalısınız. Ekleme biçimleri INSERT, UPDATE, DELETE yazısında anlatılıyor.

Yeni bir tablo tasarlarken kısa kontrol listesi

Yeni bir tablo tasarlarken kısa kontrol listesi: Birincil anahtar INTEGER PRIMARY KEY mi olacak, yoksa doğal bir anahtar mı?..., Hangi sütunlar asla boş kalmamalı? Bunlara NOT...

Yukarıdaki kuralları bir araya getirdiğimizde, üretim ortamına gidecek bir tabloyu yazmadan önce sorulması gereken birkaç soru ortaya çıkar. Bu liste belgelerdeki davranışlardan türetilmiş pratik bir özettir:

  • Birincil anahtar INTEGER PRIMARY KEY mi olacak, yoksa doğal bir anahtar mı? Doğal anahtar seçtiyseniz NOT NULL eklediniz mi?
  • Hangi sütunlar asla boş kalmamalı? Bunlara NOT NULL, gerekiyorsa mantıklı bir DEFAULT verin.
  • Hangi iş kuralları veritabanında da korunmalı? Fiyatın negatif olmaması veya durumun belirli değerler alması gibi kuralları adlandırılmış CHECK kısıtlarıyla yazın.
  • Sütun tipleri istediğiniz eğilimi veriyor mu? Kod ve kimlik gibi değerler TEXT, miktarlar INTEGER veya REAL olmalı.
  • Katı tip denetimi gerekiyor mu? Gerekiyorsa tabloyu STRICT olarak oluşturun.

Sık yapılan hatalar

  • PRIMARY KEY sütununa NOT NULL eklememek: INTEGER dışındaki anahtar sütunlar NULL kabul eder.
  • CHECK'in NULL'u engellediğini sanmak: NULL sonuçlu CHECK geçer; NOT NULL ayrıca gereklidir.
  • Açık NULL ile varsayılan beklemek: Sütunu INSERT listesinden çıkarın; NULL yazarsanız DEFAULT kullanılmaz.
  • IF NOT EXISTS ile şemanın güncelleneceğini sanmak: Tablo varsa tanım tamamen yok sayılır. Şema değişiklikleri için ALTER TABLE ve şema göçü yazılarına bakın.
  • Yabancı anahtarın kendiliğinden çalıştığını sanmak: FOREIGN KEY kısıtları ancak PRAGMA foreign_keys=ON ile denetlenir; ayrıntılar foreign key yazısında.

Sık Sorulan Sorular

Tablo kısıtı ile sütun kısıtı arasında fark var mı?

CHECK için belgelere göre pratikte fark yoktur. Birden çok sütunu kapsayan PRIMARY KEY ve UNIQUE kısıtları ise yalnızca tablo kısıtı olarak yazılabilir, NOT NULL ise yalnızca sütun kısıtı olabilir.

Sonradan bir sütuna kısıt ekleyebilir miyim?

ALTER TABLE ile sütun ekleyebilir ve yeniden adlandırabilirsiniz. 3.53.0 sürüm notlarına göre ALTER TABLE ile NOT NULL ve CHECK kısıtı ekleyip kaldırmak da mümkün hâle geldi; daha eski sürümlerde tabloyu yeniden oluşturmanız gerekir. Yöntemler ALTER TABLE yazısında.

Tabloyu nasıl silerim?

DROP TABLE ad; ile. Tablo yoksa hata almamak için DROP TABLE IF EXISTS ad; yazın. Belgelere göre silinen tablo geri getirilemez ve tabloya bağlı indeksler ile tetikleyiciler de silinir.

Bir tablonun tanımını sonradan nasıl görürüm?

Komut satırında .schema tablo veya SQL ile SELECT sql FROM sqlite_schema WHERE name = 'tablo'; kullanın. Sütun ayrıntıları için tabloları listeleme yazısındaki pragma fonksiyonlarına bakın.

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 veri tipleri · SQLite PRIMARY KEY ve rowid · SQLite INSERT, UPDATE, DELETE · SQLite STRICT tablolar · SQLite foreign key