İçeriğe geç
academia.sh

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 NULL boşluğu yasaklar, DEFAULT yazılmayan sütunu doldurur, CHECK değ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.
  • UNIQUE tekilliği, PRIMARY KEY tekillik ile zorunluluğu birlikte ister; anahtar birden çok sütundan oluşabilir ve tekillik o sütunların birlikte ürettiği değer üzerindedir.
  • REFERENCES baş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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat