Ders 08 / 14
Normal Formlar
Güncelleme anomalilerinin üç türü; birinci, ikinci ve üçüncü normal formun tanımı; her basamağın hangi anomaliyi kaldırdığının sorgu çıktısıyla gösterimi.
İçindekiler
Önceki ders tekrarın kaynağını adlandırdı: sol tarafı anahtar olmayan işlevsel bağımlılıklar. Bu dersin sorusu şudur: bu bağımlılıklar hangi sırayla giderilir, ve her adım hangi somut sorunu ortadan kaldırır?
Normalizasyonun ölçütü estetik değildir. Ölçüt, güncelleme anomalileridir (update anomalies): bir bağıntının yapısı yüzünden ortaya çıkan, veriyi tutarsız bırakan durumlar. Üç türü vardır.
- Ekleme anomalisi (insertion anomaly): bir olguyu kaydetmek için ilgisiz başka bir olgunun da bilinmesi gerekir. Henüz hiç ödünç verilmemiş bir şube, ödünç kayıtlarının tutulduğu tabloya yazılamaz.
- Silme anomalisi (deletion anomaly): bir olgu silinirken ikinci bir olgu da kaybolur. Bir kitabın tek ödünç kaydı silindiğinde kitabın kendisi de kayıtlardan çıkar.
- Güncelleme anomalisi (modification anomaly): aynı olgu birden çok satırda yazılı olduğu için değişiklik hepsine uygulanmalıdır; biri atlanırsa veri kendi içinde çelişir.
Normal formlar, bu üç anomaliyi doğuran yapıları basamak basamak eleyen tanımlardır.
Birinci Normal Form
Birinci normal form (first normal form), her satır–sütun kesişiminde tek ve bölünmez bir değer bulunmasını ister. Liste, iç içe yapı ya da tekrarlanan sütun grubu bulunmaz.
Kuralın bedeli somuttur. Telefon numaraları tek bir metin sütununda virgülle ayrılmış tutulduğunda, bir numarayı aramak dizgi eşlemesine döner ve dizgi eşlemesi yanlış eşleşir:
sqlite3 :memory: <<'SQL' .headers on .mode box CREATE TABLE uye_duz (uye_no INTEGER PRIMARY KEY, ad TEXT, telefonlar TEXT); INSERT INTO uye_duz VALUES (41, 'Ayse Demir', '0312-555-0101,0312-555-0102'), (52, 'Mert Kaya', '0312-555-0110'), (63, 'Selin Ozturk', '0312-555-011'); CREATE TABLE uye_telefon (uye_no INTEGER, telefon TEXT, PRIMARY KEY (uye_no, telefon)); INSERT INTO uye_telefon VALUES (41, '0312-555-0101'), (41, '0312-555-0102'), (52, '0312-555-0110'), (63, '0312-555-011'); SELECT 'tek sutunda liste' AS yazim, COUNT(*) AS bulunan FROM uye_duz WHERE telefonlar LIKE '%0312-555-011%' UNION ALL SELECT 'ayri bagintida', COUNT(*) FROM uye_telefon WHERE telefon = '0312-555-011'; SQL
┌───────────────────┬─────────┐ │ yazim │ bulunan │ ├───────────────────┼─────────┤ │ tek sutunda liste │ 2 │ │ ayri bagintida │ 1 │ └───────────────────┴─────────┘
Aranan numara 0312-555-011 idi ve tek sahibi vardı. Dizgi araması, o numarayı önek
olarak içeren 0312-555-0110 kaydını da getirdi. Numaralar ayrı bir bağıntıda tutulduğunda
karşılaştırma eşitlik olur ve yanlış eşleşme kalkar; ayrıca numaraya kısıt yazılabilir,
tekilliği denetlenebilir ve tek bir numara silinebilir.
Aynı kural “tekrarlanan sütun grubu” biçimi için de geçerlidir. telefon_1, telefon_2,
telefon_3 sütunları listeyi yatay yazmaktan başka bir şey değildir; dördüncü numara
geldiğinde şema değişikliği gerektirir.
İkinci Normal Form
İkinci normal form (second normal form) birinci normal formu sağlayan ve anahtar dışı hiçbir özniteliğin aday anahtarın öz alt kümesine bağımlı olmadığı bağıntıyı tanımlar. Kısacası kısmi bağımlılık bulunmaz. Tanım yalnızca bileşik anahtarlı bağıntılarda anlam taşır; tek sütunluk anahtarda öz alt küme yoktur.
Her şubedeki kitap adedini tutan bağıntının anahtarı (isbn, sube_kodu) çiftidir. Kitabın
başlığı yalnız ISBN’e, şubenin adı yalnız şube koduna bağlıdır — ikisi de kısmi
bağımlılıktır:
sqlite3 :memory: <<'SQL' .headers on .mode box CREATE TABLE stok_duz ( isbn TEXT NOT NULL, sube_kodu TEXT NOT NULL, adet INTEGER NOT NULL, kitap_baslik TEXT NOT NULL, sube_ad TEXT NOT NULL, PRIMARY KEY (isbn, sube_kodu) ); INSERT INTO stok_duz VALUES ('975-01', 'MRK', 3, 'Kayip Zaman', 'Merkez'), ('975-01', 'BHC', 1, 'Kayip Zaman', 'Bahcelievler'), ('975-02', 'MRK', 2, 'Deniz Fenerleri', 'Merkez'), ('975-02', 'BHC', 4, 'Deniz Fenerleri', 'Bahcelievler'), ('975-03', 'BHC', 1, 'Sessiz Bahce', 'Bahcelievler'); UPDATE stok_duz SET sube_ad = 'Bahcelievler Subesi' WHERE sube_kodu = 'BHC' AND isbn = '975-01'; SELECT sube_kodu, COUNT(*) AS satir, COUNT(DISTINCT sube_ad) AS farkli_ad FROM stok_duz GROUP BY sube_kodu; SQL
┌───────────┬───────┬───────────┐ │ sube_kodu │ satir │ farkli_ad │ ├───────────┼───────┼───────────┤ │ BHC │ 3 │ 2 │ │ MRK │ 2 │ 1 │ └───────────┴───────┴───────────┘
Şube adını değiştiren güncelleme bir satıra uygulandı ve şube artık iki farklı adla kayıtlı. Motor bunu engelleyemez, çünkü “aynı şube kodunun aynı adı taşıması” kuralı şemada yazılı değildir — yazılamaz da, bağımlılığın sol tarafı anahtar değildir.
Ayrıştırma, kısmi bağımlılığın sol tarafını kendi bağıntısının anahtarı yapar: kitap
bilgisi kitap bağıntısına, şube bilgisi sube bağıntısına taşınır, stok yalnız
anahtar ile ona tam bağımlı adet sütununu tutar.
sqlite3 :memory: <<'SQL' .headers on .mode box PRAGMA foreign_keys = ON; CREATE TABLE kitap (isbn TEXT PRIMARY KEY NOT NULL, baslik TEXT NOT NULL); CREATE TABLE sube (sube_kodu TEXT PRIMARY KEY NOT NULL, ad TEXT NOT NULL); CREATE TABLE stok ( isbn TEXT NOT NULL REFERENCES kitap (isbn), sube_kodu TEXT NOT NULL REFERENCES sube (sube_kodu), adet INTEGER NOT NULL, PRIMARY KEY (isbn, sube_kodu) ); INSERT INTO kitap VALUES ('975-01', 'Kayip Zaman'), ('975-02', 'Deniz Fenerleri'), ('975-03', 'Sessiz Bahce'); INSERT INTO sube VALUES ('MRK', 'Merkez'), ('BHC', 'Bahcelievler'), ('KDK', 'Kadikoy'); INSERT INTO stok VALUES ('975-01', 'MRK', 3), ('975-01', 'BHC', 1), ('975-02', 'MRK', 2), ('975-02', 'BHC', 4), ('975-03', 'BHC', 1); UPDATE sube SET ad = 'Bahcelievler Subesi' WHERE sube_kodu = 'BHC'; SELECT changes() AS guncellenen_satir, (SELECT COUNT(*) FROM sube WHERE sube_kodu = 'KDK') AS stoksuz_sube; SQL
┌───────────────────┬──────────────┐ │ guncellenen_satir │ stoksuz_sube │ ├───────────────────┼──────────────┤ │ 1 │ 1 │ └───────────────────┴──────────────┘
Şube adı tek bir satırda tutulduğu için tek güncelleme yeter ve tutarsızlık kurulamaz. Aynı ayrıştırma ekleme anomalisini de kaldırır: hiç kitabı bulunmayan Kadıköy şubesi kayıtlıdır — düz tabloda bu satır yazılamazdı, çünkü anahtarın ISBN parçası boş kalırdı. Değişen satır sayısını veren işlevin adı motora göre değişir.
Üçüncü Normal Form
Üçüncü normal form (third normal form) ikinci normal formu sağlayan ve anahtar dışı hiçbir özniteliğin başka bir anahtar dışı özniteliğe bağımlı olmadığı bağıntıyı tanımlar. Kısacası geçişli bağımlılık bulunmaz.
Ödünç kaydında anahtar tek sütundur, dolayısıyla kısmi bağımlılık yoktur; buna karşılık
odunc_no → uye_no → uye_ad, uye_eposta zinciri vardır. Üye bilgisi ödünç işlemine
doğrudan değil, üye numarası üzerinden bağlıdır:
sqlite3 :memory: <<'SQL' .headers on .mode box CREATE TABLE odunc_duz ( odunc_no INTEGER PRIMARY KEY, uye_no INTEGER NOT NULL, uye_ad TEXT NOT NULL, uye_eposta TEXT NOT NULL, isbn TEXT NOT NULL, kitap_baslik TEXT NOT NULL, alis_tarihi TEXT NOT NULL ); INSERT INTO odunc_duz VALUES (1001, 41, 'Ayse Demir', '[email protected]', '975-01', 'Kayip Zaman', '2025-03-02'), (1002, 52, 'Mert Kaya', '[email protected]', '975-01', 'Kayip Zaman', '2025-03-04'), (1003, 41, 'Ayse Demir', '[email protected]', '975-02', 'Deniz Fenerleri', '2025-03-05'), (1004, 41, 'Ayse Demir', '[email protected]', '975-01', 'Kayip Zaman', '2025-03-20'), (1005, 52, 'Mert Kaya', '[email protected]', '975-03', 'Sessiz Bahce', '2025-03-22'); UPDATE odunc_duz SET uye_eposta = '[email protected]' WHERE odunc_no = 1001; DELETE FROM odunc_duz WHERE odunc_no = 1005; SELECT (SELECT COUNT(DISTINCT uye_eposta) FROM odunc_duz WHERE uye_no = 41) AS uye41_eposta, (SELECT COUNT(*) FROM odunc_duz WHERE isbn = '975-03') AS kitap975_03; SQL
┌──────────────┬─────────────┐ │ uye41_eposta │ kitap975_03 │ ├──────────────┼─────────────┤ │ 2 │ 0 │ └──────────────┴─────────────┘
İki anomali aynı anda görünür. Bir satırda düzeltilen e-posta adresi, aynı üyenin iki farklı adrese sahip görünmesine yol açtı. Tek ödünç kaydı silinen kitap ise veritabanından tamamen kayboldu — başlığı yalnız o satırda yazılıydı.
Ayrıştırma, geçişli zincirin ortasını kendi bağıntısının anahtarı yapar:
sqlite3 :memory: <<'SQL' .headers on .mode box PRAGMA foreign_keys = ON; CREATE TABLE uye (uye_no INTEGER PRIMARY KEY, ad TEXT NOT NULL, eposta TEXT NOT NULL); CREATE TABLE kitap (isbn TEXT PRIMARY KEY NOT NULL, baslik TEXT NOT NULL); CREATE TABLE odunc ( odunc_no INTEGER PRIMARY KEY, uye_no INTEGER NOT NULL REFERENCES uye (uye_no), isbn TEXT NOT NULL REFERENCES kitap (isbn), alis_tarihi TEXT NOT NULL ); INSERT INTO uye VALUES (41, 'Ayse Demir', '[email protected]'), (52, 'Mert Kaya', '[email protected]'); INSERT INTO kitap VALUES ('975-01', 'Kayip Zaman'), ('975-02', 'Deniz Fenerleri'), ('975-03', 'Sessiz Bahce'); INSERT INTO odunc VALUES (1001, 41, '975-01', '2025-03-02'), (1002, 52, '975-01', '2025-03-04'), (1003, 41, '975-02', '2025-03-05'), (1004, 41, '975-01', '2025-03-20'), (1005, 52, '975-03', '2025-03-22'); UPDATE uye SET eposta = '[email protected]' WHERE uye_no = 41; DELETE FROM odunc WHERE odunc_no = 1005; SELECT (SELECT COUNT(DISTINCT eposta) FROM uye WHERE uye_no = 41) AS uye41_eposta, (SELECT COUNT(*) FROM kitap WHERE isbn = '975-03') AS kitap975_03; SQL
┌──────────────┬─────────────┐ │ uye41_eposta │ kitap975_03 │ ├──────────────┼─────────────┤ │ 1 │ 1 │ └──────────────┴─────────────┘
Aynı iki deyim çalıştırıldı; sonuç değişti. E-posta tek yerde tutulduğu için tek adres kaldı, kitap kaydı ödünç kaydından bağımsız olduğu için silmeden etkilenmedi. İkisi de sorgu yazımıyla değil, şema yapısıyla sağlandı.
Ayrıştırmanın İki Koşulu
Bağıntı bölmek her zaman güvenli değildir. İki koşul aranır.
Kayıpsız birleştirme (lossless join): ayrılan bağıntılar yeniden birleştirildiğinde başlangıçtaki satırlar aynen elde edilmeli, fazladan satır doğmamalıdır. Koşul şudur: ortak öznitelik kümesi, parçalardan en az birinin süperanahtarı olmalıdır. Yukarıdaki ayrıştırmaların hepsi bunu sağlar, çünkü bölme her zaman bir bağımlılığın sol tarafından yapıldı.
Bağımlılık koruma (dependency preservation): başlangıçtaki bağımlılıklar, parçalar üzerinde ayrı ayrı denetlenebilmelidir. Korunmayan bir bağımlılık, ancak parçaları birleştirerek sınanabilir — yani kısıt olarak yazılamaz.
Üçüncü normal forma kadar olan ayrıştırmalarda ikisi de birlikte sağlanabilir. Sıradaki dersin konusu, bu iki koşulun ayrıştığı durumdur.
Özet
- Normalizasyonun ölçütü ekleme, silme ve güncelleme anomalilerinin ortadan kalkmasıdır.
- Birinci normal form her hücrede tek bölünmez değer ister; liste tutan sütun, aramayı dizgi eşlemesine indirger ve yanlış eşleşme üretir.
- İkinci normal form kısmi bağımlılığı, üçüncü normal form geçişli bağımlılığı kaldırır; ikisi de bölmeyi bağımlılığın sol tarafından yapar.
- Normalleştirilmiş şemada bir olgu tek yerde yazılıdır; güncelleme tek satıra uygulanır ve tutarsızlık kurulamaz.
- Ayrıştırma kayıpsız birleştirme ve bağımlılık koruma koşullarını sağlamalıdır; ortak öznitelik kümesi parçalardan birinin süperanahtarı olduğunda birleştirme kayıpsızdır.
Sonraki Adım
Üçüncü normal form, anahtar dışı özniteliklere odaklanır. Peki bağımlılığın sağ tarafı anahtarın bir parçasıysa? Tanım gereği o öznitelik anahtar dışı değildir ve üçüncü normal form ihlal edilmiş sayılmaz — ama tekrar sürer. Sıradaki ders bu boşluğu kapatan Boyce–Codd normal formunu tanımlar, kayıpsız birleştirme ile bağımlılık korumanın birlikte sağlanamadığı durumu gösterir ve çok değerli bağımlılıkla dördüncü normal forma geçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.