İçeriğe geç
academia.sh

Ders 14 / 18

Şema Değiştirme

ALTER TABLE ile sütun ekleme, adlandırma ve kaldırma; tek deyimle yapılamayan değişikliklerin motora göre değişen sınırı ve tabloyu taşınabilir biçimde yeniden kurma yordamı.

İçindekiler

Önceki ders şemayı kısıtlarıyla birlikte yazdı. Gerçek bir şema ise ilk yazıldığı hâlde kalmaz: üyelere telefon alanı gerekir, bir sütunun adı yanlış seçilmiştir, e-posta alanına sonradan bir kısıt eklenmek istenir. Üstelik bunlar tablo boşken değil, içinde veri varken yapılır.

ALTER TABLE deyimi bu değişiklikleri yazar. Deyimin kendisi standarttır, ancak hangi değişikliği tek deyimde yapabildiği motora göre değişir — SQL’de taşınabilirliğin en zayıf olduğu alanlardan biridir. Bu ders hem deyimi hem de sınırı gösterir, sonra sınırın ötesine geçmenin her motorda çalışan yolunu kurar.

Sütun Eklemek ve Yeniden Adlandırmak

En çok gereken iki değişiklik en ucuz olanlardır: sonuna sütun eklemek ve bir sütunun adını değiştirmek. Her ikisi de var olan satırların içeriğine dokunmadan yapılabilir.

sqlite3 :memory: <<'SQL'
.headers on
.mode column
CREATE TABLE uye (
  uye_id       INTEGER PRIMARY KEY,
  ad           TEXT NOT NULL,
  soyad        TEXT NOT NULL,
  eposta       TEXT UNIQUE,
  kayit_tarihi TEXT NOT NULL
);
INSERT INTO uye VALUES (1,'Ayşe','Demir','[email protected]','2023-02-14'),
                       (3,'Zeynep','Arslan',NULL,'2024-01-09');

ALTER TABLE uye ADD COLUMN telefon TEXT;
ALTER TABLE uye ADD COLUMN bildirim TEXT NOT NULL DEFAULT 'eposta';
ALTER TABLE uye RENAME COLUMN kayit_tarihi TO uyelik_tarihi;

SELECT * FROM uye;
SQL
uye_id  ad      soyad   eposta           uyelik_tarihi  telefon  bildirim
------  ------  ------  ---------------  -------------  -------  --------
1       Ayşe    Demir   [email protected]  2023-02-14              eposta  
3       Zeynep  Arslan                   2024-01-09              eposta  

Var olan iki satır yeni sütunları da aldı. telefon sütununda değer yok, çünkü kısıt da varsayılan da yazılmadı: eski satırlar boş değer taşır. bildirim sütununda ise NOT NULL kısıtı var, dolayısıyla boşluk kabul edilemez — motor eski satırları varsayılan değerle doldurdu. Sütun eklerken bu iki durumdan biri seçilmelidir: ya sütun boş kalabilir ya da eski satırların ne alacağı yazılır.

RENAME COLUMN yalnızca adı değiştirir; veriye ve kısıtlara dokunmaz. Bu, görünüşte zararsız ama etkisi geniş bir işlemdir: sütunun eski adını kullanan her sorgu, her görünüm ve her uygulama kodu kırılır. Adlandırma hatası fark edildiğinde düzeltmek ucuzdur, aylar sonra düzeltmek değildir.

Bu dersteki blokların her biri kendi başına çalışır ve üye tablosunun yalnızca örneğe gereken sütunlarını kurar; kursun asıl şeması önceki derste yazılan tanımdır. Yukarıdaki yeniden adlandırma da o tanıma işlenmez, etkisini göstermek için yapılmıştır — sonraki derslerde sütunun adı kayit_tarihi olarak sürer.

Eklemenin Sınırları

Sütun eklemek her zaman ucuz değildir. Ucuz olmasının nedeni, motorun var olan satırlara dokunmak zorunda kalmamasıdır. Bu koşulun bozulduğu üç durum vardır ve motor bunları reddeder.

sqlite3 :memory: <<'SQL'
CREATE TABLE uye (
  uye_id       INTEGER PRIMARY KEY,
  ad           TEXT NOT NULL,
  soyad        TEXT NOT NULL,
  eposta       TEXT UNIQUE,
  kayit_tarihi TEXT NOT NULL
);
INSERT INTO uye VALUES (1,'Ayşe','Demir','[email protected]','2023-02-14');

ALTER TABLE uye ADD COLUMN kimlik_no TEXT NOT NULL;
ALTER TABLE uye ADD COLUMN kart_no TEXT UNIQUE;
ALTER TABLE uye ADD COLUMN son_giris TEXT DEFAULT (date('now'));
SQL
Runtime error near line 10: Cannot add a NOT NULL column with default value NULL
Parse error near line 11: Cannot add a UNIQUE column
Runtime error near line 12: Cannot add a column with non-constant default

Üç ret, üç ayrı gerekçeyle gelir. Birincisi, varsayılanı olmayan zorunlu sütundur: var olan satırın o sütunda ne yazacağı belirsizdir, boş bırakılamaz da. İkincisi benzersizlik kısıtıdır: yeni sütun bütün satırlarda aynı değeri — boşluğu ya da varsayılanı — taşıyacağı için kısıt daha kurulurken ihlal edilebilir. Üçüncüsü sabit olmayan varsayılandır: değeri her satır için ayrı hesaplanan bir varsayılan, eski satırlara ne yazılacağını belirsiz bırakır.

Bu üç iletinin sözcükleri motora özgüdür. Ortak olan kural şudur: var olan satırların yeniden yazılmasını gerektiren bir ekleme tek deyimle yapılamaz. Sütun önce boş bırakılabilir hâlde eklenir, satırlar doldurulur, kısıt sonra kurulur.

Sütun Kaldırmak

Sütun kaldırmak da benzer bir sınıra takılır. İşlemin kendisi tanımlıdır, ancak kaldırılacak sütun bir kısıtın ya da dizinin parçasıysa reddedilir.

sqlite3 :memory: <<'SQL'
.headers on
.mode column
CREATE TABLE uye (
  uye_id       INTEGER PRIMARY KEY,
  ad           TEXT NOT NULL,
  soyad        TEXT NOT NULL,
  eposta       TEXT UNIQUE,
  kayit_tarihi TEXT NOT NULL,
  telefon      TEXT
);
INSERT INTO uye VALUES (1,'Ayşe','Demir','[email protected]','2023-02-14','0312 000 00 00');

ALTER TABLE uye DROP COLUMN telefon;
SELECT * FROM uye;
ALTER TABLE uye DROP COLUMN eposta;
SQL
uye_id  ad    soyad  eposta           kayit_tarihi
------  ----  -----  ---------------  ------------
1       Ayşe  Demir  [email protected]  2023-02-14  
Parse error near line 15: cannot drop UNIQUE column: "eposta"

Kısıtsız telefon sütunu kalktı; benzersizlik kısıtı taşıyan eposta kalkmadı. Sütun kaldırmanın geri alınamaz olduğunu da not etmek gerekir: sütunla birlikte içindeki bütün değerler gider. Üretimdeki bir tabloda sütun kaldırmadan önce yapılan iş, sütunu kullanmayı bırakıp bir süre beklemektir; kaldırma en son adımdır.

Tabloyu Yeniden Adlandırmak

Tablo adı değiştiğinde, o tabloya başvuran yabancı anahtarların ne olacağı ayrı bir sorudur. Kullanılan motor başvuruları izler ve günceller.

sqlite3 :memory: <<'SQL'
PRAGMA foreign_keys = ON;

CREATE TABLE sube (sube_id INTEGER PRIMARY KEY, ad TEXT NOT NULL, sehir TEXT NOT NULL);
CREATE TABLE kitap (
  kitap_id INTEGER PRIMARY KEY,
  baslik   TEXT NOT NULL,
  yazar    TEXT NOT NULL,
  sube_id  INTEGER REFERENCES sube(sube_id)
);

ALTER TABLE sube RENAME TO subeler;
.schema kitap
SQL
CREATE TABLE kitap (
  kitap_id INTEGER PRIMARY KEY,
  baslik   TEXT NOT NULL,
  yazar    TEXT NOT NULL,
  sube_id  INTEGER REFERENCES "subeler"(sube_id)
);

kitap tablosunun tanımı, ona hiç dokunulmadığı hâlde değişti. Bu davranış motora göre değişir: kimi motorlar başvuruyu izler, kimileri eski adı tutmayı sürdürür ve şema tutarsız kalır. Tablo adı değiştirmenin öncesinde, o tabloya kimin başvurduğu çıkarılmalıdır.

Tabloyu Yeniden Kurmak

Bir sütunun tipini değiştirmek, var olan bir sütuna kısıt eklemek, bir kısıtı kaldırmak ya da sütun sırasını düzeltmek — bunların hiçbiri kullanılan motorda tek deyimle yapılamaz. Kimi motorlarda bir kısmı yapılabilir, ancak yapılabildiği yerde bile tablo büyükse işlem satırları yeniden yazar.

Her motorda çalışan yol, tabloyu yeniden kurmaktır. Yordam dört adımdır: istenen tanımla yeni bir tablo yaratılır, veri kopyalanır, eski tablo düşürülür, yeni tablo eski adı alır. Dördü tek bir işlemin içinde yapılır ki yarıda kalan bir adım şemayı bozuk bırakmasın.

sqlite3 :memory: <<'SQL'
.headers on
.mode column
PRAGMA foreign_keys = OFF;

CREATE TABLE uye (
  uye_id       INTEGER PRIMARY KEY,
  ad           TEXT NOT NULL,
  soyad        TEXT NOT NULL,
  eposta       TEXT UNIQUE,
  kayit_tarihi TEXT NOT NULL,
  durum        TEXT NOT NULL DEFAULT 'etkin'
                 CHECK (durum IN ('etkin', 'askida', 'kapali')),
  bildirim     TEXT NOT NULL DEFAULT 'eposta'
);
INSERT INTO uye (uye_id, ad, soyad, eposta, kayit_tarihi) VALUES
  (1,'Ayşe','Demir','[email protected]','2023-02-14'),
  (3,'Zeynep','Arslan',NULL,'2024-01-09');

BEGIN;

CREATE TABLE uye_yeni (
  uye_id       INTEGER PRIMARY KEY,
  ad           TEXT NOT NULL,
  soyad        TEXT NOT NULL,
  eposta       TEXT UNIQUE CHECK (eposta IS NULL OR eposta LIKE '%@%'),
  kayit_tarihi TEXT NOT NULL,
  durum        TEXT NOT NULL DEFAULT 'etkin'
                 CHECK (durum IN ('etkin', 'askida', 'kapali')),
  bildirim     TEXT NOT NULL DEFAULT 'eposta'
                 CHECK (bildirim IN ('eposta', 'sms', 'yok'))
);

INSERT INTO uye_yeni (uye_id, ad, soyad, eposta, kayit_tarihi, durum, bildirim)
SELECT uye_id, ad, soyad, eposta, kayit_tarihi, durum, bildirim FROM uye;

DROP TABLE uye;
ALTER TABLE uye_yeni RENAME TO uye;

COMMIT;

PRAGMA foreign_key_check;
SELECT * FROM uye;
SQL
uye_id  ad      soyad   eposta           kayit_tarihi  durum  bildirim
------  ------  ------  ---------------  ------------  -----  --------
1       Ayşe    Demir   [email protected]  2023-02-14    etkin  eposta  
3       Zeynep  Arslan                   2024-01-09    etkin  eposta  

Yeni tanımdaki iki denetim koşulu da ALTER TABLE ile eklenemezdi, çünkü ikisi de var olan sütunlara bağlanıyor: biri eposta, diğeri dersin başında eklenen bildirim sütunu. O sütun eklenirken varsayılan verilebilmişti ama kısıt verilememişti; kısıt ancak burada, tablo yeniden kurulurken tanıma girdi. Veri korundu, iki satır da yeni tanıma uydu — boş e-postalı satır dahil, çünkü koşul boşluğu ayrıca karşılıyor.

Kopyalama adımının bir yan yararı vardır: SELECT listesi yazıldığı için sütunlar dönüştürülebilir. Bir tarih sütunu metinden sayıya çevriliyorsa dönüşüm burada yapılır; eski sütun düşürülüyorsa listeye alınmaz.

Kopyalama sırasında yeni tanımın kısıtları uygulanır. Eski veri yeni kısıtı ihlal ediyorsa deyim başarısız olur ve işlem geri alınır — istenen budur. Şema değişikliği, veriyi kısıta uydurma işiyle birlikte planlanır.

PRAGMA foreign_key_check çağrısı hiçbir şey yazdırmadı: kırık başvuru yok. Bu çağrı yordamın son adımıdır ve atlanmamalıdır.

Yeniden Kurmanın Riski

Yabancı anahtar denetimi yordam boyunca kapatılır, çünkü DROP TABLE adımı ile RENAME TO adımı arasında hedef tablo geçici olarak yoktur. Denetimin kapalı olması, kopyalamada yapılan bir hatanın sessizce geçmesi demektir.

sqlite3 :memory: <<'SQL'
.headers on
.mode column
PRAGMA foreign_keys = OFF;

CREATE TABLE sube (sube_id INTEGER PRIMARY KEY, ad TEXT NOT NULL, sehir TEXT NOT NULL);
CREATE TABLE kitap (
  kitap_id INTEGER PRIMARY KEY,
  baslik   TEXT NOT NULL,
  sube_id  INTEGER NOT NULL REFERENCES sube(sube_id)
);
INSERT INTO sube VALUES (1,'Merkez','Ankara'),(3,'Kadıköy','İstanbul');
INSERT INTO kitap VALUES (1,'Körlük',1),(5,'Sessiz Ev',3);

BEGIN;
CREATE TABLE sube_yeni (
  sube_id INTEGER PRIMARY KEY,
  ad      TEXT NOT NULL,
  sehir   TEXT NOT NULL,
  acik    INTEGER NOT NULL DEFAULT 1
);
INSERT INTO sube_yeni (sube_id, ad, sehir)
SELECT sube_id, ad, sehir FROM sube WHERE sehir = 'Ankara';
DROP TABLE sube;
ALTER TABLE sube_yeni RENAME TO sube;
COMMIT;

PRAGMA foreign_key_check;
SQL
table  rowid  parent  fkid
-----  -----  ------  ----
kitap  5      sube    0   

Kopyalama deyimindeki koşul yüzünden bir şube taşınmadı ve ona bağlı kitap öksüz kaldı. İşlem başarıyla kesinleşti, hiçbir hata verilmedi; kırığı yalnızca denetim çağrısı gösterdi. Yordamın bu son adımı, sessiz veri kaybının tek uyarısıdır.

Uygulamada üç alışkanlık bu riski küçültür. Yeniden kurma yordamı, üretim verisinin bir kopyası üzerinde önce denenir. Kopyalama deyiminden sonra satır sayıları karşılaştırılır. Ve yordam bir işlemin içinde çalıştırılır, böylece adımlardan biri başarısız olduğunda şema eski hâline döner.

Özet

  • ALTER TABLE sütun ekleme, yeniden adlandırma ve kaldırmayı tek deyimde yapar; hangi değişikliğin desteklendiği motora göre değişir.
  • Var olan satırların yeniden yazılmasını gerektiren eklemeler reddedilir: varsayılanı olmayan zorunlu sütun, benzersiz sütun ve sabit olmayan varsayılan.
  • Sütun kaldırmak geri alınamaz ve kısıtın parçası olan sütunlarda reddedilir; güvenli sıra, önce kullanımı bırakmak sonra kaldırmaktır.
  • Tek deyimle yapılamayan değişiklikler için taşınabilir yol tabloyu yeniden kurmaktır: yeni tablo, kopyalama, düşürme, yeniden adlandırma — hepsi tek işlem içinde.
  • Yeniden kurma sırasında yabancı anahtar denetimi kapalı olduğu için, yordamın son adımı başvuru bütünlüğünü sınamaktır.

Sonraki Adım

Şema kurulmuş ve değiştirilebilir durumda. Sıradaki soru, içine veriyi nasıl koyduğumuz. Sonraki ders satır eklemeyi ele alır: tek satırlık ekleme, tek deyimde çok satır, sorgu sonucunu doğrudan tabloya yazma ve bunların birbirinden ayrıldığı yer olan başarım. Yirmi bin satırın tek tek mi yoksa tek işlem içinde mi eklendiği, ölçülebilir bir farktır.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat