Ders 13 / 18
Tablo Oluşturma
Sütun tanımının parçaları, alan ve tablo düzeyi kısıtlar, anahtarların şemada ifadesi ve kısıt ihlallerinin motorda ürettiği hata iletileri.
İçindekiler
Kursun buraya kadarki bölümü var olan veriyi okumakla geçti: sütun seçtik, koşul yazdık, tabloları birleştirdik, grupladık ve küme işlemleriyle sonuç kümelerini birbirine ekledik. Her sorgu, önceden kurulmuş bir şemayı veri olarak aldı. Bu ders o varsayımı kaldırır ve sıradaki soruyu sorar: şemanın kendisi nasıl yazılır.
SQL Dil Aileleri dersinde ayrılan ailelerden ilki — veri tanımlama dili — bu konunun
alanıdır. Kütüphane şeması o derste kurulmuş ve üç kısıt türü tanıtılmıştı: PRIMARY KEY,
NOT NULL ve REFERENCES. Orada bu bildirimlerin ne söylediği anlatıldı; bu ders
neyi engellediklerini gösterir ve listeye üçün dışındakileri ekler — varsayılan
değerler, değer aralığı denetimleri, benzersizlik ve adlandırılmış kısıtlar.
Tablo tanımı, o tabloya girebilecek satırların kuralını belirler. Kural şemada durursa motor onu her yazma denemesinde uygular; kural yalnızca uygulama kodunda durursa, o koda uğramayan her yol kuralı deler. Bu dersin konusu bu farktır.
Tablo Tanımının Parçaları
CREATE TABLE deyiminin gövdesi virgülle ayrılmış öge listesidir. Her öge ya bir sütun
tanımıdır ya da bir tablo düzeyi kısıttır. Sütun tanımı üç parçadan oluşur: ad, tip ve
sıfır veya daha çok kısıt (constraint).
Şemanın en küçük tablosu şubelerdir.
sqlite3 :memory: <<'SQL' .headers on .mode column CREATE TABLE sube ( sube_id INTEGER PRIMARY KEY, ad TEXT NOT NULL, sehir TEXT NOT NULL ); INSERT INTO sube VALUES (1,'Merkez','Ankara'),(2,'Bahçelievler','Ankara'); INSERT INTO sube (sube_id, ad, sehir) VALUES (3,'Kadıköy','İstanbul'); SELECT * FROM sube; SQL
sube_id ad sehir ------- ------------ -------- 1 Merkez Ankara 2 Bahçelievler Ankara 3 Kadıköy İstanbul
Komut satırındaki :memory: argümanı, veritabanını diske değil belleğe kurar: örnek
biter bitmez ortadan kalkar. Bu dersin bütün blokları böyle çalışır ve geride dosya
bırakmaz. Sorgu derslerindeki kutuphane.db dosyasına dokunulmaz.
Boşluğu Yasaklamak ve Varsayılan Değer
Veri Modelleme ve İlişkisel Kuram kursunda boş değerin “bilinmiyor” anlamına geldiği ve üç
değerli mantığı tetiklediği gösterilmişti. Bir sütun için boşluk anlamsızsa, bu şemada
söylenir: NOT NULL.
DEFAULT ise ekleme sırasında değer verilmeyen sütunun neyle doldurulacağını belirtir.
UNIQUE de sütundaki değerlerin birbirini tekrarlamamasını ister. Üyeler tablosu üçünü
birden kullanır. Sorgu derslerindeki tanıma göre iki değişiklik vardır: e-posta artık
tekil olmak zorundadır ve üyelik durumunu tutan bir sütun eklenmiştir.
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, durum TEXT NOT NULL DEFAULT 'etkin' ); INSERT INTO uye (uye_id, ad, soyad, eposta, kayit_tarihi) VALUES (1,'Ayşe','Demir','[email protected]','2023-02-14'); INSERT INTO uye (uye_id, ad, soyad, eposta, kayit_tarihi) VALUES (2,'Mehmet',NULL,'[email protected]','2023-05-30'); INSERT INTO uye (uye_id, ad, soyad, eposta, kayit_tarihi) VALUES (3,'Zeynep','Arslan','[email protected]','2024-01-09'); SELECT * FROM uye; SQL
Runtime error near line 14: NOT NULL constraint failed: uye.soyad (19) Runtime error near line 16: UNIQUE constraint failed: uye.eposta (19) uye_id ad soyad eposta kayit_tarihi durum ------ ---- ----- --------------- ------------ ----- 1 Ayşe Demir [email protected] 2023-02-14 etkin
Üç ekleme denendi, biri geçti. Birinci satır durum sütununu hiç yazmadığı hâlde etkin
değerini aldı: varsayılan değer devreye girdi. İkincisi soyadı boş bıraktı ve NOT NULL
kısıtına takıldı. Üçüncüsü zaten kullanılan bir e-posta adresini tekrarladı.
Hata iletisinin yapısı önemlidir: hangi kısıt türü, hangi tablo, hangi sütun. Kısıt ihlalinde motor satırı yazmaz; deyim başarısız olur ve etkisi tümüyle geri alınır. Kısmen yazılmış satır diye bir şey yoktur.
Tekillik ve Boş Değer
PRIMARY KEY, İlişkisel Kuram kursunda tanımlanan birincil anahtarın şemadaki
karşılığıdır ve iki kısıtın bileşimidir: benzersizlik artı boş olmama. UNIQUE yalnız
birincisini ister.
Aradaki farkı boş değer belirginleştirir. Standart SQL’de benzersiz bir sütun birden çok
boş değer taşıyabilir, çünkü iki bilinmeyen değerin eşit olduğu söylenemez. Şemadaki
eposta sütunu bunun örneğidir: kimi üyelerin e-postası kayıtlı değildir.
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 (3,'Zeynep','Arslan',NULL,'2024-01-09'); INSERT INTO uye VALUES (5,'Selin','Aydın',NULL,'2024-11-05'); INSERT INTO uye VALUES (6,'Burak','Şahin','[email protected]','2025-01-18'); SELECT count(*) AS satir, count(eposta) AS epostasi_olan FROM uye; SQL
satir epostasi_olan ----- ------------- 3 1
İki satır aynı anda boş e-postayla durabildi. Bu davranış motora göre değişebilen bir
noktadır: kimi motorlar birden çok boş değeri kabul eder, kimileri bu kararı tanımlayıcıya
bırakan ek yazımlar sunar. Bir sütunun hem tekil hem zorunlu olması isteniyorsa
NOT NULL UNIQUE ikilisi yazılır — ki bu, birincil anahtarın tanımıdır.
Anahtar birden çok sütundan da oluşabilir. Bir üyenin aynı kitabı iki kez sıraya yazdırmasını engelleyen kural, tablo düzeyinde bileşik bir birincil anahtardır.
sqlite3 :memory: <<'SQL' .headers on .mode column CREATE TABLE rezervasyon ( uye_id INTEGER NOT NULL, kitap_id INTEGER NOT NULL, tarih TEXT NOT NULL, PRIMARY KEY (uye_id, kitap_id) ); INSERT INTO rezervasyon VALUES (1, 5, '2025-07-01'); INSERT INTO rezervasyon VALUES (2, 5, '2025-07-02'); INSERT INTO rezervasyon VALUES (1, 6, '2025-07-03'); INSERT INTO rezervasyon VALUES (1, 5, '2025-07-04'); SELECT * FROM rezervasyon; SQL
Runtime error near line 13: UNIQUE constraint failed: rezervasyon.uye_id, rezervasyon.kitap_id (19) uye_id kitap_id tarih ------ -------- ---------- 1 5 2025-07-01 2 5 2025-07-02 1 6 2025-07-03
Aynı kitabı farklı üyeler ve aynı üye farklı kitaplar için sıraya girebildi; tekrarlanan tek şey çiftin kendisiydi. Bileşik anahtarda tekillik sütunların ayrı ayrı değil, birlikte oluşturduğu değer üzerinde tanımlıdır.
Değer Aralığını Daraltmak
CHECK, satır yazılırken doğrulanan bir koşul yazar. Koşul yanlış sonuç verirse satır
kabul edilmez. Üyelik durumunun üç değerden biri olması gibi bir kural, uygulama koduna
değil şemaya böyle taşınır.
sqlite3 :memory: <<'SQL' .headers on .mode column CREATE TABLE uye ( uye_id INTEGER PRIMARY KEY, ad TEXT NOT NULL, soyad TEXT NOT NULL, kayit_tarihi TEXT NOT NULL, durum TEXT NOT NULL DEFAULT 'etkin' CHECK (durum IN ('etkin', 'askida', 'kapali')) ); INSERT INTO uye (uye_id, ad, soyad, kayit_tarihi, durum) VALUES (1,'Ayşe','Demir','2023-02-14','askida'); INSERT INTO uye (uye_id, ad, soyad, kayit_tarihi, durum) VALUES (2,'Mehmet','Kaya','2023-05-30','pasif'); SELECT * FROM uye; SQL
Runtime error near line 14: CHECK constraint failed: durum IN ('etkin', 'askida', 'kapali') (19)
uye_id ad soyad kayit_tarihi durum
------ ---- ----- ------------ ------
1 Ayşe Demir 2023-02-14 askida
Sütun tanımının içine yazılan bir denetim koşulu yalnız o sütuna bakabilir. İki sütunu
birlikte sınayan bir kural gerektiğinde kısıt, sütun listesinin sonuna tablo düzeyi
kısıt olarak yazılır. CONSTRAINT ad ön eki kısıta ad verir; ad, hata iletisinde
görünür ve hatanın hangi kurala ait olduğunu okunur kılar.
sqlite3 :memory: <<'SQL' .headers on .mode column CREATE TABLE odunc ( odunc_id INTEGER PRIMARY KEY, kitap_id INTEGER NOT NULL, uye_id INTEGER NOT NULL, alis_tarihi TEXT NOT NULL, iade_tarihi TEXT, CONSTRAINT iade_alistan_sonra CHECK (iade_tarihi IS NULL OR iade_tarihi >= alis_tarihi) ); INSERT INTO odunc VALUES (1, 1, 1, '2025-01-10', '2025-01-24'); INSERT INTO odunc VALUES (3, 1, 2, '2025-02-11', NULL); INSERT INTO odunc VALUES (4, 3, 3, '2025-03-01', '2025-02-15'); SELECT * FROM odunc; SQL
Runtime error near line 15: CHECK constraint failed: iade_alistan_sonra (19) odunc_id kitap_id uye_id alis_tarihi iade_tarihi -------- -------- ------ ----------- ----------- 1 1 1 2025-01-10 2025-01-24 3 1 2 2025-02-11
Koşuldaki iade_tarihi IS NULL OR parçası ihmal edilemez. Boş değerle yapılan
karşılaştırma doğru değil bilinmeyen sonuç verir; kısıt yalnızca ikinci koşuldan
oluşsaydı, iade edilmemiş ödünçler de reddedilirdi. Boş değerin üç değerli mantığı, kısıt
yazımında en sık tökezlenen yerdir.
Tablolar Arası Bağ
REFERENCES yazımı, bir sütundaki değerin başka bir tablonun anahtarında bulunmasını
şart koşar. Bu, başvuru bütünlüğünün şemadaki ifadesidir: var olmayan bir şubeye kitap
yazılamaz.
Yabancı anahtar denetiminin açık olup olmadığı motora göre değişir. Kullanılan komut satırı aracında denetim öntanımlı olarak kapalıdır ve bağlantı başına açılır.
sqlite3 :memory: <<'SQL' .headers on .mode column 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, basim_yili INTEGER CHECK (basim_yili BETWEEN 1450 AND 2100), sube_id INTEGER REFERENCES sube(sube_id) ); INSERT INTO sube VALUES (1,'Merkez','Ankara'); INSERT INTO kitap VALUES (1,'Körlük','José Saramago',1995,9); SELECT * FROM kitap; SQL
kitap_id baslik yazar basim_yili sube_id -------- ------ ------------- ---------- ------- 1 Körlük José Saramago 1995 9
Şemada yazan kısıt uygulanmadı; olmayan şube numarasına sahip satır tabloya girdi. Aynı
blok PRAGMA foreign_keys = ON; satırıyla açıldığında sonuç değişir.
sqlite3 :memory: <<'SQL' .headers on .mode column 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, basim_yili INTEGER CHECK (basim_yili BETWEEN 1450 AND 2100), sube_id INTEGER REFERENCES sube(sube_id) ); INSERT INTO sube VALUES (1,'Merkez','Ankara'); INSERT INTO kitap VALUES (1,'Körlük','José Saramago',1995,9); INSERT INTO kitap VALUES (2,'Tutunamayanlar','Oğuz Atay',1972,1); INSERT INTO kitap VALUES (7,'Tehlikeli Oyunlar','Oğuz Atay',1973,NULL); SELECT * FROM kitap; SQL
Runtime error near line 15: FOREIGN KEY constraint failed (19) kitap_id baslik yazar basim_yili sube_id -------- ----------------- --------- ---------- ------- 2 Tutunamayanlar Oğuz Atay 1972 1 7 Tehlikeli Oyunlar Oğuz Atay 1973
Olmayan şubeye bağlanan kitap reddedildi, geçerli şubeye bağlanan kabul edildi. Üçüncü satırın şube numarası boştur ve o da kabul edildi: yabancı anahtar, sütun boş olduğunda denetim yapmaz — “hangi şubede olduğu bilinmiyor” ifadesi, “olmayan bir şubede” ifadesinden farklıdır.
PRAGMA yazımı standart SQL’in parçası değildir, kullanılan motora aittir. Aktarılabilir
olan ders şudur: bir kısıtın şemada yazılı olması, denetlendiği anlamına gelmez. Yeni
bir veritabanıyla çalışmaya başlarken yabancı anahtar denetiminin durumu sınanır; kapalı
bir denetim, aylar sonra fark edilen öksüz satırlar demektir.
Kütüphane Şeması
Dersteki parçalar bir araya geldiğinde konunun geri kalanında kullanılacak şema çıkar.
sqlite3 :memory: <<'SQL' .headers on .mode column 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, basim_yili INTEGER CHECK (basim_yili BETWEEN 1450 AND 2100), sube_id INTEGER REFERENCES sube(sube_id) ); 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')) ); CREATE TABLE odunc ( odunc_id INTEGER PRIMARY KEY, kitap_id INTEGER NOT NULL REFERENCES kitap(kitap_id), uye_id INTEGER NOT NULL REFERENCES uye(uye_id), alis_tarihi TEXT NOT NULL, iade_tarihi TEXT, CONSTRAINT iade_alistan_sonra CHECK (iade_tarihi IS NULL OR iade_tarihi >= alis_tarihi) ); INSERT INTO sube VALUES (1,'Merkez','Ankara'),(3,'Kadıköy','İstanbul'); INSERT INTO kitap VALUES (1,'Körlük','José Saramago',1995,1),(5,'Sessiz Ev','Orhan Pamuk',1983,3); 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'); INSERT INTO odunc VALUES (1,1,1,'2025-01-10','2025-01-24'),(4,5,3,'2025-03-01',NULL); SELECT u.ad, u.soyad, k.baslik, s.ad AS sube, o.alis_tarihi FROM odunc AS o JOIN uye AS u ON u.uye_id = o.uye_id JOIN kitap AS k ON k.kitap_id = o.kitap_id JOIN sube AS s ON s.sube_id = k.sube_id ORDER BY o.odunc_id; SQL
ad soyad baslik sube alis_tarihi ------ ------ --------- ------- ----------- Ayşe Demir Körlük Merkez 2025-01-10 Zeynep Arslan Sessiz Ev Kadıköy 2025-03-01
Dört tablonun da birincil anahtarı, veriden gelmeyen bir tam sayıdır. Kitaplar için doğal bir aday vardı — ISBN — ama o, bir baskıyı adlandırır; aynı baskının iki nüshası aynı numarayı taşır ve anahtar nüshaları ayırt edemez. Doğal bir alanın anahtar gibi görünüp anahtar olamaması, şema tasarımında sık rastlanan bir durumdur ve genellikle bu dersteki gibi üretilmiş bir anahtarla çözülür.
Özet
- Sütun tanımı ad, tip ve kısıtlardan oluşur; kısıt şemada durursa motor onu her yazma yolunda uygular, uygulama kodunda durursa yalnızca o koddan geçen yolda uygulanır.
NOT NULLboşluğu yasaklar,DEFAULTyazılmayan sütunu doldurur,CHECKdeğer aralığını daraltır; boş değer alabilen sütunlarda kısıt koşulu boşluğu ayrıca karşılamalıdır.UNIQUEtekilliği,PRIMARY KEYtekillik ile zorunluluğu birlikte ister; anahtar birden çok sütundan oluşabilir ve tekillik o sütunların birlikte ürettiği değer üzerindedir.REFERENCESbaşvuru bütünlüğünü şemaya yazar, boş değerli sütunda denetim yapmaz ve denetimin açık olup olmadığı motora göre değişir.- Kısıt ihlalinde deyim tümüyle başarısız olur; hata iletisi kısıt türünü, tabloyu ve kısıt adını verdiği için tanı doğrudan okunabilir.
Sonraki Adım
Şema ilk yazıldığı hâlde kalmaz: yeni bir sütun gerekir, bir sütunun adı yanlış
seçilmiştir, bir kısıt sonradan eklenmek istenir. Sonraki ders şema değiştirmeyi ele alır
ve asıl güçlüğü gösterir: ALTER TABLE ile tek deyimde yapılabilen değişiklikler
sınırlıdır ve bu sınır motorlar arasında farklıdır. Sınırın ötesine geçildiğinde tablo
yeniden kurulur; bu işlemin taşınabilir ve veriyi kaybetmeyen biçimi de o derste
kurulacak.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.