SQLite'ta ALTER TABLE sınırlı bir alt kümeyi destekler: tabloyu yeniden adlandırma (RENAME TO), sütunu yeniden adlandırma (RENAME COLUMN), sütun ekleme (ADD COLUMN) ve sütun silme (DROP COLUMN). SQLite 3.53.0 ile buna NOT NULL ve CHECK kısıtlarını ekleyip kaldırma da katıldı. Sütun tipini değiştirmek, birincil anahtar eklemek veya yabancı anahtar tanımlamak gibi diğer değişiklikler için tabloyu yeni tanımla oluşturup veriyi kopyalayan on iki adımlı yöntemi kullanmanız gerekir.
Bu yazıda her komutun kurallarını, hangi durumlarda reddedildiğini, güvenli tablo yeniden oluşturma adımlarını ve sık yapılan sıralama hatasını çalıştırılmış örneklerle anlatıyoruz. Değişiklikleri sürüm numarasıyla yönetmek için şema göçü ve user_version yazısına, serinin tamamı için SQLite öğrenme rehberine bakabilirsiniz.
ALTER TABLE neden bu kadar sınırlı

Çoğu veritabanı motoru şemayı ayrıştırılmış hâlde sistem tablolarında tutar; ALTER TABLE yalnızca bu tabloları günceller. SQLite ise şemayı sqlite_schema tablosunda, sizin yazdığınız CREATE ifadelerinin özgün metni olarak saklar. Her ALTER TABLE komutu bu metni düzenler ve ardından bütün şemayı yeniden ayrıştırmayı dener; şema hâlâ geçerliyse komut başarılı olur. Belgelere göre bu tercih şemanın dosyada az yer kaplamasını, iç gösterimin sürümler arasında değişebilmesini ve dosya biçiminin kolay belgelenmesini sağlar. Bedeli ise şema değişikliklerinin zorlaşmasıdır.
Metni düzenleme yaklaşımının hoş bir yan etkisi var: yeniden adlandırma ve kısıtsız sütun ekleme tablo içeriğine hiç dokunmaz. Bu yüzden 10 milyon satırlık bir tabloda da tek satırlık bir tabloda da aynı hızda çalışır. CHECK kısıtlı sütun eklemek, NOT NULL kısıtlı hesaplanan sütun eklemek ve sütun silmek ise veriyi okumayı veya yeniden yazmayı gerektirdiği için tablo büyüklüğüyle orantılı zaman alır.
RENAME TO ve RENAME COLUMN
RENAME TO tabloya yeni bir ad verir; tabloyu ekli başka bir veritabanına taşımak için kullanılamaz. Tablonun indeksleri ve tetikleyicileri tabloya bağlı kalır. SQLite 3.25.0'dan beri tetikleyici gövdelerindeki ve görünüm tanımlarındaki başvurular da güncellenir; 3.26.0'dan beri yabancı anahtar tanımları, foreign_keys ayarından bağımsız olarak her zaman yeni adı gösterecek biçimde düzeltilir. SQLite 3.25.0 ile gelen RENAME COLUMN ise sütun adını tablo tanımında ve onu kullanan bütün indeks, tetikleyici ve görünümlerde değiştirir; değişiklik bir görünümde veya tetikleyicide anlam belirsizliğine yol açacaksa komut hata verir ve hiçbir şey değişmez.
PRAGMA legacy_alter_table = OFF; -- standart davranışı garanti altına alır
CREATE TABLE uye(id INTEGER PRIMARY KEY, eposta TEXT NOT NULL, ad TEXT);
CREATE INDEX uye_eposta ON uye(eposta);
CREATE VIEW uye_liste AS SELECT id, eposta FROM uye;
CREATE TABLE oturum(id INTEGER PRIMARY KEY, uye_id INTEGER REFERENCES uye(id));
INSERT INTO uye VALUES (1, 'ada@example.com', 'Ada');
ALTER TABLE uye RENAME TO kullanici;
ALTER TABLE kullanici RENAME COLUMN eposta TO mail;
SELECT type, name, sql FROM sqlite_schema ORDER BY type, name;
-- çıktı:
-- index|uye_eposta|CREATE INDEX uye_eposta ON "kullanici"(mail)
-- table|kullanici|CREATE TABLE "kullanici"(id INTEGER PRIMARY KEY, mail TEXT NOT NULL, ad TEXT)
-- table|oturum|CREATE TABLE oturum(id INTEGER PRIMARY KEY, uye_id INTEGER REFERENCES "kullanici"(id))
-- view|uye_liste|CREATE VIEW uye_liste AS SELECT id, mail FROM "kullanici"
SELECT * FROM uye_liste;
-- çıktı: 1|ada@example.comİndeks, görünüm ve alt tablodaki yabancı anahtar yeni tablo adını ve yeni sütun adını gösteriyor; görünüm de çalışmaya devam ediyor. Yeniden adlandırılan tablonun adının şema metninde tırnak içine alındığına dikkat edin; bu SQLite'ın metni düzenlerken kullandığı biçimdir ve davranışı etkilemez. Görünümler hakkında daha fazlası için CREATE VIEW yazısına bakabilirsiniz.
ADD COLUMN: kurallar ve kısıtlar

Yeni sütun her zaman listenin sonuna eklenir ve CREATE TABLE içindeki sütun tanımlarının neredeyse tamamını kullanabilir. Var olan satırlar yeni sütunda varsayılan değeri alır:
CREATE TABLE malzeme(id INTEGER PRIMARY KEY, ad TEXT NOT NULL, fiyat INTEGER);
INSERT INTO malzeme(ad, fiyat) VALUES ('kalem', 15), ('defter', 40);
ALTER TABLE malzeme ADD COLUMN stok INTEGER NOT NULL DEFAULT 0;
ALTER TABLE malzeme ADD COLUMN aciklama TEXT;
ALTER TABLE malzeme ADD COLUMN etiket TEXT DEFAULT 'yeni';
SELECT id, ad, fiyat, stok, quote(aciklama), etiket FROM malzeme;
-- çıktı:
-- 1|kalem|15|0|NULL|yeni
-- 2|defter|40|0|NULL|yeniBazı tanımlar ise bu yolla eklenemez. Belgelerdeki liste şöyle: sütun PRIMARY KEY veya UNIQUE olamaz; varsayılan değeri CURRENT_TIME, CURRENT_DATE, CURRENT_TIMESTAMP ya da parantez içinde bir ifade olamaz; NOT NULL verilirse NULL olmayan bir varsayılan değer gerekir; yabancı anahtar denetimi açıkken REFERENCES içeren sütunun varsayılanı NULL olmalıdır; STORED hesaplanan sütun eklenemez. Hepsini sırayla deneyelim:
ALTER TABLE malzeme ADD COLUMN kod TEXT UNIQUE;
-- hata: Cannot add a UNIQUE column
ALTER TABLE malzeme ADD COLUMN kod2 TEXT PRIMARY KEY;
-- hata: Cannot add a PRIMARY KEY column
ALTER TABLE malzeme ADD COLUMN eklenme TEXT DEFAULT CURRENT_TIMESTAMP;
-- hata: Cannot add a column with non-constant default
ALTER TABLE malzeme ADD COLUMN eklenme2 TEXT DEFAULT (date('2026-01-01'));
-- hata: Cannot add a column with non-constant default
ALTER TABLE malzeme ADD COLUMN zorunlu TEXT NOT NULL;
-- hata: Cannot add a NOT NULL column with default value NULLBu sınırların çoğunun kolay bir çözümü var. Benzersiz bir sütun gerekiyorsa önce sütunu ekleyip değerleri doldurun, sonra CREATE UNIQUE INDEX ile benzersizliği indeksle sağlayın. Zaman damgası gerekiyorsa sütunu sabit bir varsayılanla ekleyip mevcut satırları UPDATE ile doldurun; yeni satırların otomatik zaman damgası alması gerekiyorsa tabloyu on iki adımlı yöntemle yeniden oluşturun. İndeks oluşturma için index oluşturma yazısına bakın.
CHECK kısıtlı sütunlar mevcut veriyle denetlenir
SQLite 3.37.0'dan beri, CHECK kısıtı olan bir sütun veya NOT NULL kısıtlı bir hesaplanan sütun eklediğinizde kısıt bütün mevcut satırlara uygulanır ve tek bir satır bile uymazsa ekleme yapılmaz. Aşağıdaki ikinci komutta yeni sütun mevcut satırlarda NULL olacağı için kısıt sağlanamadı:
ALTER TABLE malzeme ADD COLUMN indirim INTEGER DEFAULT 0 CHECK (indirim BETWEEN 0 AND 100);
ALTER TABLE malzeme ADD COLUMN kdv INTEGER CHECK (kdv IS NOT NULL); -- mevcut satırlarda NULL
-- hata: CHECK constraint failed
SELECT sql FROM sqlite_schema WHERE name = 'malzeme';
-- çıktı: CREATE TABLE malzeme(id INTEGER PRIMARY KEY, ad TEXT NOT NULL, fiyat INTEGER, stok INTEGER NOT NULL DEFAULT 0, aciklama TEXT, etiket TEXT DEFAULT 'yeni', indirim INTEGER DEFAULT 0 CHECK (indirim BETWEEN 0 AND 100))Şema metninin sonuna yalnızca başarılı olan sütun eklendi. Hesaplanan sütun eklemenin ayrıntıları hesaplanan sütunlar yazısında. Belgeler bir de uyumluluk notu düşer: ADD COLUMN çalıştırılmış bir veritabanı, 2005 tarihli 3.1.3 ve öncesi sürümlerce okunamaz.
DROP COLUMN: ne zaman başarısız olur
SQLite 3.35.0 ile gelen DROP COLUMN, sütunu tablo tanımından çıkarır ve içeriği yeniden yazarak o sütunun verisini temizler. Komut yalnızca sütun şemanın başka hiçbir yerinde kullanılmıyorsa çalışır. Belgelere göre başarısızlık nedenleri şunlardır: sütunun birincil anahtar veya onun parçası olması, UNIQUE kısıtı taşıması, indekslenmiş olması, bir kısmi indeksin WHERE ifadesinde geçmesi, başka bir sütunun ya da tablonun CHECK kısıtında kullanılması, bir yabancı anahtarda yer alması, bir hesaplanan sütunun ifadesinde kullanılması ve bir tetikleyicide ya da görünümde geçmesi.
CREATE TABLE musteri(
id INTEGER PRIMARY KEY,
eposta TEXT UNIQUE,
ad TEXT,
sehir TEXT,
faks TEXT,
yas INTEGER CHECK (yas >= 18)
);
CREATE INDEX musteri_sehir ON musteri(sehir);
CREATE VIEW istanbul AS SELECT id, ad FROM musteri WHERE sehir = 'İstanbul';
INSERT INTO musteri VALUES (1, 'a@ornek.com', 'Ada', 'İzmir', '0232', 30);
ALTER TABLE musteri DROP COLUMN faks; -- başarılı
ALTER TABLE musteri DROP COLUMN yas; -- kendi CHECK kısıtıyla birlikte gider
ALTER TABLE musteri DROP COLUMN id;
-- hata: cannot drop PRIMARY KEY column: "id"
ALTER TABLE musteri DROP COLUMN eposta;
-- hata: cannot drop UNIQUE column: "eposta"
ALTER TABLE musteri DROP COLUMN sehir;
-- hata: error in index musteri_sehir after drop column: no such column: sehir
ALTER TABLE musteri DROP COLUMN ad;
-- hata: error in view istanbul after drop column: no such column: adHata mesajları sorunun nerede olduğunu açıkça söylüyor. Sütunun kendi tanımına bağlı CHECK kısıtı ise sorun çıkarmadı; yas sütunu kısıtıyla birlikte kaldırıldı. İndeksi ve görünümü kaldırdıktan sonra silme başarılı olur. Gerekiyorsa indeksi ve görünümü sütunsuz yeni tanımlarıyla yeniden oluşturursunuz:
DROP VIEW istanbul;
DROP INDEX musteri_sehir;
ALTER TABLE musteri DROP COLUMN sehir;
SELECT * FROM musteri;
-- çıktı: 1|a@ornek.com|AdaSQLite 3.53 ve 3.54 ile gelenler
SQLite 3.53.0 sürüm notlarına göre ALTER TABLE artık NOT NULL ve CHECK kısıtlarını ekleyip kaldırabiliyor. Belgedeki söz dizimi şemasında bunun için ALTER COLUMN ... SET NOT NULL, ALTER COLUMN ... DROP NOT NULL, adlandırılmış bir CHECK kısıtı ekleyen ADD CONSTRAINT ve DROP CONSTRAINT biçimleri yer alıyor. Kullandığımız çalıştırma ortamı daha eski olduğu için aşağıdaki örneği çalıştırmadık; yazımın ayrıntıları için resmî söz dizimi şemasına bakın:
-- SQLite 3.53.0 ve sonrası gerekir (yalnızca belgeden, çalıştırılmadı)
ALTER TABLE malzeme ALTER COLUMN aciklama SET NOT NULL;
ALTER TABLE malzeme ALTER COLUMN aciklama DROP NOT NULL;
ALTER TABLE malzeme ADD CONSTRAINT fiyat_pozitif CHECK (fiyat > 0);
ALTER TABLE malzeme DROP CONSTRAINT fiyat_pozitif;Belgeler SET NOT NULL için bir ayrıntıyı özellikle vurgular: sütun zaten NOT NULL ise yeni kısıt eskisinin yerine geçer ve farklı bir ON CONFLICT davranışı getirebilir. CREATE TABLE ile bir sütuna birden fazla NOT NULL yazılabildiği için DROP NOT NULL en az birini kaldırmayı garanti eder, hepsini değil. Kısıt eklerken mevcut satırların nasıl denetlendiğini önce bir kopya üzerinde denemeniz iyi olur. 3.54.0 sürümüyle ise ALTER TABLE, ROWID, _ROWID_ veya OID adlı sütunları ekleme, silme ya da yeniden adlandırma girişimlerinde hata vermeye başladı. Bu sürümlerin diğer yenilikleri için SQLite 3.53 yenilikleri ve SQLite 3.54 yenilikleri yazılarına bakın.
Diğer değişiklikler için on iki adımlı yöntem

Sütun tipini değiştirmek, sütunların sırasını değiştirmek, birincil anahtar veya UNIQUE eklemek ya da kaldırmak, yabancı anahtar eklemek ya da bir tabloyu STRICT veya WITHOUT ROWID yapmak için belgeler şu sırayı önerir:
- Yabancı anahtar denetimi açıksa
PRAGMA foreign_keys=OFFile kapatın. - Bir işlem başlatın.
- Tabloya bağlı indeks, tetikleyici ve görünümlerin tanımlarını
sqlite_schematablosundan not edin. - İstediğiniz yeni tanımla, mevcut adlarla çakışmayan geçici adlı bir tablo oluşturun.
- Veriyi
INSERT INTO yeni SELECT ... FROM eskiile aktarın. - Eski tabloyu
DROP TABLEile kaldırın. - Yeni tabloyu
ALTER TABLE ... RENAME TOile eski adına getirin. - İndeks, tetikleyici ve görünümleri yeniden oluşturun.
- Değişiklikten etkilenen görünümleri kaldırıp yeni tanımlarıyla oluşturun.
- Denetim başta açıksa
PRAGMA foreign_key_checkile ilişkilerin bozulmadığını doğrulayın. - İşlemi onaylayın.
- Yabancı anahtar denetimini yeniden açın.
Aşağıda fiyatı metin olarak tutulan bir kitap tablosunu, fiyatı tamsayı olan katı bir tabloya dönüştürüyoruz. Tabloya bir alt tablo da bağlı. Adım numaralarını yorum olarak yazdık; görünüm ve tetikleyici olmadığı için 9. adım boş geçiyor:
-- Başlangıç durumu: uygulama yabancı anahtar denetimiyle çalışıyor
PRAGMA foreign_keys = ON;
CREATE TABLE kitap(id INTEGER PRIMARY KEY, ad TEXT, fiyat TEXT);
CREATE INDEX kitap_ad ON kitap(ad);
CREATE TABLE odunc(id INTEGER PRIMARY KEY, kitap_id INTEGER REFERENCES kitap(id));
INSERT INTO kitap VALUES (1, 'Kuyucaklı Yusuf', '15'), (2, 'Sait Faik Seçmeler', '40');
INSERT INTO odunc VALUES (10, 2);
-- 1. Yabancı anahtar denetimini kapatın (işlem dışında)
PRAGMA foreign_keys = OFF;
-- 2. İşlemi başlatın
BEGIN;
-- 3. Tabloya bağlı indeks, tetikleyici ve görünümleri not edin
SELECT type, name, sql FROM sqlite_schema WHERE tbl_name = 'kitap' AND type <> 'table';
-- çıktı: index|kitap_ad|CREATE INDEX kitap_ad ON kitap(ad)
-- 4. Yeni tanımla geçici adlı tabloyu oluşturun
CREATE TABLE kitap_yeni(
id INTEGER PRIMARY KEY,
ad TEXT NOT NULL,
fiyat INTEGER NOT NULL CHECK (fiyat >= 0)
) STRICT;
-- 5. Veriyi aktarın
INSERT INTO kitap_yeni(id, ad, fiyat) SELECT id, ad, CAST(fiyat AS INTEGER) FROM kitap;
-- 6. Eski tabloyu kaldırın
DROP TABLE kitap;
-- 7. Yeni tabloyu eski adla yeniden adlandırın
ALTER TABLE kitap_yeni RENAME TO kitap;
-- 8. İndeks, tetikleyici ve görünümleri yeniden oluşturun
CREATE INDEX kitap_ad ON kitap(ad);
-- 10. Yabancı anahtarları denetleyin (satır dönmemeli)
PRAGMA foreign_key_check;
-- 11. İşlemi onaylayın
COMMIT;
-- 12. Denetimi yeniden açın
PRAGMA foreign_keys = ON;
SELECT id, ad, fiyat, typeof(fiyat) FROM kitap;
-- çıktı:
-- 1|Kuyucaklı Yusuf|15|integer
-- 2|Sait Faik Seçmeler|40|integer
DELETE FROM kitap WHERE id = 2; -- ilişki hâlâ çalışıyor
-- hata: FOREIGN KEY constraint failedDönüşümden sonra fiyatlar tamsayı oldu, indeks yerinde ve alt tablonun yabancı anahtarı yeni tabloyu gösteriyor. Yabancı anahtar denetiminin 1. adımda kapatılmasının nedeni, eski tabloyu kaldırırken alt tablodaki satırların örtük silmeyi ve ihlal hatasını tetiklememesidir. Denetim işlem içinde değiştirilemediği için bu adım BEGIN öncesinde yapılmalıdır. Ayrıntılar foreign key yazısında ve transaction yazısında.
Sıra neden önemli
Belgeler yaygın bir kısayolun yanlış olduğunu açıkça belirtir: önce eski tabloyu geçici bir ada taşıyıp sonra yeni tabloyu asıl adla oluşturmak. 3.25.0 ve 3.26.0 sürümleriyle gelen kapsamlı yeniden adlandırma, ilk adımda alt tablolardaki, görünümlerdeki ve tetikleyicilerdeki başvuruları geçici ada taşır. Eski tablo kaldırıldığında bu başvurular var olmayan bir tabloyu gösterir:
CREATE TABLE kategori(id INTEGER PRIMARY KEY, ad TEXT);
CREATE TABLE yazi(id INTEGER PRIMARY KEY, kategori_id INTEGER REFERENCES kategori(id));
-- YANLIŞ SIRA: önce eski tabloyu yeniden adlandırmak
ALTER TABLE kategori RENAME TO kategori_eski;
SELECT sql FROM sqlite_schema WHERE name = 'yazi';
-- çıktı: CREATE TABLE yazi(id INTEGER PRIMARY KEY, kategori_id INTEGER REFERENCES "kategori_eski"(id))
CREATE TABLE kategori(id INTEGER PRIMARY KEY, ad TEXT NOT NULL);
DROP TABLE kategori_eski;
INSERT INTO yazi VALUES (1, 1);
-- hata: no such table: main.kategori_eskiDoğru yöntemde yeni tablo geçici adla oluşturulur ve en son asıl ada taşınır; böylece başvurular hiç bozulmaz.
| Doğru sıra | Yanlış sıra |
|---|---|
| Yeni tabloyu geçici adla oluştur | Eski tabloyu geçici ada taşı |
| Veriyi kopyala | Yeni tabloyu asıl adla oluştur |
| Eski tabloyu kaldır | Veriyi kopyala |
| Yeni tabloyu asıl ada taşı | Eski tabloyu kaldır |
Daha hızlı ama riskli yol: writable_schema
Disk üzerindeki içeriği etkilemeyen bazı değişiklikler, örneğin CHECK, FOREIGN KEY veya NOT NULL kısıtını kaldırmak ya da varsayılan değeri değiştirmek için belgeler PRAGMA writable_schema=ON ile sqlite_schema tablosundaki metni doğrudan güncelleyen ve ardından PRAGMA schema_version değerini bir artıran daha kısa bir yöntem de anlatır. Bu yöntemde yapılacak bir yazım hatası veritabanını bozuk ve okunamaz hâle getirebilir. Belgeler işlemi önce boş bir deneme veritabanında sınamayı ve öncesinde yedek almayı önerir. Yedekleme için yedekleme ve VACUUM INTO, bozulma durumları için bozuk veritabanı kurtarma yazılarına bakın.
Sık yapılan hatalar
- Tabloyu önce yeniden adlandırıp sonra yenisini oluşturmak: Başvurular geçici ada taşınır ve eski tablo kaldırılınca bozulur.
- Yabancı anahtar ayarını işlem içinde kapatmaya çalışmak: Etkisiz kalır; BEGIN öncesinde kapatın.
- Silmeden önce indeksleri ve görünümleri kontrol etmemek: DROP COLUMN, sütunu kullanan her nesne için hata verir.
- UNIQUE veya zaman damgası varsayılanlı sütunu ADD COLUMN ile eklemeye çalışmak: Önce sütunu ekleyip ardından indeks ya da güncelleme ile tamamlayın.
- legacy_alter_table ayarını gözden kaçırmak: Açıksa görünümler ve tetikleyiciler eski adı göstermeye devam eder.
Sık Sorulan Sorular
SQLite'ta sütun tipini nasıl değiştiririm?
Doğrudan bir komut yoktur. On iki adımlı yöntemle tabloyu yeni tiple oluşturup veriyi CAST kullanarak aktarın. Esnek tip sistemi sayesinde çoğu zaman tipi değiştirmeye gerek de kalmaz; ayrıntılar veri tipleri yazısında.
Sütunların sırasını değiştirebilir miyim?
ALTER TABLE ile hayır. Yeni sütunlar her zaman sona eklenir. Sırayı değiştirmek için tabloyu yeniden oluşturmanız gerekir; sorgularda sütunları istediğiniz sırada listelemek çoğu zaman yeterlidir.
ALTER TABLE işlem içinde çalıştırılabilir mi?
Evet. On iki adımlı yöntem zaten bütün değişiklikleri tek bir işlemde toplar; bir adım başarısız olursa ROLLBACK ile her şey eski hâline döner.
Büyük bir tabloda sütun eklemek uzun sürer mi?
Kısıtsız bir sütun eklemek yalnızca şema metnini değiştirdiği için tablo büyüklüğünden bağımsızdır. CHECK kısıtlı sütunlar, NOT NULL kısıtlı hesaplanan sütunlar ve sütun silme ise veriyi okumak ya da yeniden yazmak zorunda olduğu için tablo büyüdükçe uzar.
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 · Şema göçü ve user_version · Foreign key · STRICT tablolar · Hesaplanan sütunlar · SQLite 3.53 yenilikleri