Mert Özen Her Satırda Daha İleri
Veritabanı Data Modeling ve Normalizasyon — #06

1NF, 2NF ve 3NF: Sipariş Tablosunu Adım Adım Normalize Etmek

Mert Özen 14 Kas 2026 13 dk 2 görüntülenme
1NF, 2NF ve 3NF: Sipariş Tablosunu Adım Adım Normalize Etmek

Normalizasyon tabloyu dependency'lere göre böler: 1NF listeleri satırlara açar, 2NF partial, 3NF transitive dependency'leri ayırır ve determinant yeni tablonun key'i olduğu için birleştirme kayıpsız olur. Sipariş Excel'ini SQL Server'da STRING_SPLIT ile 1NF'e, ardından 2NF ve 3NF'e taşıyor, her adımda EXCEPT ile kayıp ve sahte satır olmadığını kanıtlıyor, JSON kolonunun 1NF ile ilişkisini ve sonucu Bookstore şemasına eşlemeyi gösteriyoruz.

Sürüm notu: Bu yazıdaki SQL (Structured Query Language) örnekleri SQL Server 2025 (17.0, Cumulative Update 10) üzerinde çalıştırılmıştır.

Önce Tahmin Et: Bölünen Tablolar Aynı Veriyi Geri Verir mi?

Bir önceki yazıda sipariş Excel'inin dependency haritasını çıkardık. Bu yazıda tabloyu o haritaya göre adım adım böleceğiz ve sonunda beş satırlık düz tablo altı tabloya dönüşecek: customers, cities, orders, order_items, books ve book_authors. Serinin 3. yazısında bir ternary ilişkiyi iki tabloya bölüp geri birleştirdiğimizde sahte satırlar çıkmıştı. Tahmin et: Bu tabloları JOIN ile geri birleştirdiğimizde başlangıçtaki satırların aynısını mı, fazlasını mı, eksiğini mi alırız?

Cevap: aynısını. Her adımın sonunda bunu EXCEPT ile iki yönlü kontrol edeceğiz ve hem "kaybolan" hem "sahte" satır sayısı sıfır çıkacak. Bu bir şans değil; bölmeyi rastgele değil, dependency'lere göre yaptığımız için böyle. Yazının sonunda bunun nedenini tek bir kurala bağlayacağız.

06-normalization-steps-tr.png

SQL denemelerinde her adımın tablosunu adımın sonekiyle adlandırdım (order_lines_1nf, orders_2nf, customers_3nf), böylece aynı veritabanında yan yana durabiliyorlar. Başlangıç noktası, bir önceki yazıdaki dbo.order_sheet tablosunun yeniden yüklenmiş, temiz hâli.

1NF: Atomik Değer ve Tekrarlayan Grup

1NF'in (First Normal Form) özü şu: bir satırın her kolonu tek bir değer taşır ve bir satır aynı türden değerleri tekrarlayan bir grup içermez. Excel'in isbns, books, prices ve quantities hücreleri tam olarak tekrarlayan grup: bir siparişteki her kitap için dört bilginin listesi, dört ayrı hücreye pozisyonla dağıtılmış. 1NF'e geçmek, her kitabı kendi satırına açmak demek. SQL Server 2022'den beri STRING_SPLIT üçüncü parametreyle her parçanın sırasını da döndürüyor; dört listeyi bu sırayla hizalayabiliriz:

SELECT s.order_no, s.order_date, s.customer, s.email, s.phone, s.city, s.region, s.shipping_address,
       i.ordinal                          AS line_no,
       TRIM(i.value)                      AS isbn,
       TRIM(b.value)                      AS title,
       CAST(TRIM(p.value) AS DECIMAL(10,2)) AS unit_price,
       CAST(TRIM(q.value) AS INT)           AS quantity
INTO dbo.order_lines_1nf
FROM dbo.order_sheet AS s
CROSS APPLY STRING_SPLIT(s.isbns, ',', 1) AS i
CROSS APPLY STRING_SPLIT(s.books, ',', 1) AS b
CROSS APPLY STRING_SPLIT(s.prices, ',', 1) AS p
CROSS APPLY STRING_SPLIT(s.quantities, ',', 1) AS q
WHERE b.ordinal = i.ordinal AND p.ordinal = i.ordinal AND q.ordinal = i.ordinal;

ALTER TABLE dbo.order_lines_1nf ALTER COLUMN isbn CHAR(13) NOT NULL;
ALTER TABLE dbo.order_lines_1nf
    ADD CONSTRAINT PK_order_lines_1nf PRIMARY KEY (order_no, isbn);

SELECT order_no, line_no, isbn, title, unit_price, quantity, customer
FROM dbo.order_lines_1nf
ORDER BY order_no, line_no;
(7 rows affected)
order_no  line_no  isbn           title                      unit_price  quantity  customer
--------  -------  -------------  -------------------------  ----------  --------  ------------
1001      1        9798900000268  Dune                       390.50      1         Ada Lovelace
1001      2        9798900000428  Good Omens                 340.90      1         Ada Lovelace
1002      1        9798900000572  The Pragmatic Programmer   1150.00     1         Alan Turing
1003      1        9798900000305  The Left Hand of Darkness  660.00      1         Ada Lovelace
1003      2        9798900000268  Dune                       351.45      2         Ada Lovelace
1004      1        9798900000411  A Wizard of Earthsea       200.50      3         Grace Hopper
1005      1        9798900000428  Good Omens                 340.90      1         Alan Turing

(7 rows affected)

Beş satır yedi satıra açıldı ve artık her satır "bir siparişteki bir kitap". Key (order_no, isbn), bir önceki yazıda closure ile bulduğumuz candidate key. Ama sorgunun ne kadar kırılgan olduğuna dikkat et: dört ayrı listenin aynı sırada yazıldığına güveniyor. Başlığında virgül olan bir kitap (Tinker, Tailor, Soldier, Spy gibi) books listesini bir eleman uzatır ve o siparişin bütün hizalaması kayar: fiyatlar yanlış kitaplara, adetler yanlış satırlara gider. Hücreye liste koymanın asıl maliyeti budur; veriyi okuyabilmek için verinin içinde olmayan bir kurala (sıraya ve ayırıcıya) güvenmek zorunda kalırsın.

Yazarlar iki seviyeli bir liste: kitaplar noktalı virgülle, aynı kitabın yazarları virgülle ayrılmış. Onları ayrı bir tabloya açıyoruz, çünkü yazar sipariş kalemiyle değil kitapla ilgili bir tekrarlayan grup:

SELECT DISTINCT
       TRIM(i.value)  AS isbn,
       TRIM(a.value)  AS author_name,
       a.ordinal      AS author_order
INTO dbo.book_author_lines_1nf
FROM dbo.order_sheet AS s
CROSS APPLY STRING_SPLIT(s.isbns, ',', 1) AS i
CROSS APPLY STRING_SPLIT(s.authors, ';', 1) AS g
CROSS APPLY STRING_SPLIT(g.value, ',', 1) AS a
WHERE g.ordinal = i.ordinal;

SELECT isbn, author_order, author_name
FROM dbo.book_author_lines_1nf
ORDER BY isbn, author_order;
(7 rows affected)
isbn           author_order  author_name
-------------  ------------  -----------------
9798900000268  1             Frank Herbert
9798900000305  1             Ursula K. Le Guin
9798900000411  1             Ursula Le Guin
9798900000428  1             Terry Pratchett
9798900000428  2             Neil Gaiman
9798900000572  1             Andrew Hunt
9798900000572  2             David Thomas

(7 rows affected)

DISTINCT, Dune ve Good Omens'ın iki farklı siparişte geçmesinden doğan tekrarları attı. Ama çıktıda bir şey daha var: "Ursula K. Le Guin" ve "Ursula Le Guin" iki ayrı yazar olarak duruyor. Normalizasyon aynı değerin kopyalarını birleştirir, aynı kişinin farklı yazımlarını birleştiremez. Bu bir veri kalitesi işi; yazarı kendi kimliğiyle (author_id) bir authors tablosuna taşıdığında ve import sırasında eşleştirme yaptığında çözülür. Normalize edilmiş model bu hatanın tekrar oluşmasını engeller; zaten oluşmuş olanı ise senin bulman gerekir.

1NF'in Modern Hâli: JSON ve ARRAY Kolonları

"Atomik değer" tanımı ilk bakışta net görünür ama sınırı tartışmalıdır. Bir tarih atomik mi, yoksa gün, ay ve yıldan oluşan bir yapı mı? Bir adres? Modern veritabanlarında bir kolon JSON (JavaScript Object Notation) document'i ya da PostgreSQL'de bir array (text[] gibi) tutabiliyor ve SQL Server 2025 native json tipini getirdi (SQL Temelleri serisinin "SQL Server 2025 ile T-SQL'e Gelenler" yazısı). Bu kolonlar 1NF'i bozar mı? Pratik cevap şu: bir değer, veritabanının içine bakması gereken bir sorgunun konusu olduğu anda o değer senin için atomik değildir. Sipariş kalemlerini JSON kolonunda tutan bir tablo deneyelim:

CREATE TABLE dbo.orders_json (
    order_no INT  NOT NULL PRIMARY KEY,
    items    JSON NOT NULL
);
INSERT INTO dbo.orders_json (order_no, items) VALUES
    (1001, '[{"isbn":"9798900000268","qty":1,"price":390.50},{"isbn":"9798900000428","qty":1,"price":340.90}]'),
    (1003, '[{"isbn":"9798900000305","qty":1,"price":660.00},{"isbn":"9798900000268","qty":2,"price":351.45}]'),
    (1006, '[{"isbn":"9790000000000","qty":1,"price":10.00}]');

SELECT o.order_no, i.qty, i.price
FROM dbo.orders_json AS o
CROSS APPLY OPENJSON(o.items)
     WITH (isbn CHAR(13) '$.isbn', qty INT '$.qty', price DECIMAL(10,2) '$.price') AS i
WHERE i.isbn = '9798900000268';
(3 rows affected)
order_no  qty  price
--------  ---  ------
1001      1    390.50
1003      2    351.45

(2 rows affected)

Sorgu çalışıyor; "Dune'u kim aldı?" sorusunun cevabı doğru. Ama her sorgu her siparişin JSON'unu açmak zorunda ve daha önemlisi, 1006 numaralı sipariş var olmayan bir ISBN'le (International Standard Book Number) sorunsuzca kaydedildi. JSON'un içindeki bir değere foreign key tanımlayamazsın, UNIQUE koyamazsın, CHECK ile adedin pozitif olduğunu garanti etmek de ayrı bir iş. Kalemler sorgulanan, kısıtlanan ve başka tablolara bağlanan bir şeyse ayrı bir tablodur.

Tersine, kitabın formatına özgü ve kimsenin üzerinde join ya da filtre yapmadığı bir metadata (e-kitabın dosya boyutu, sesli kitabın anlatıcı notları) bir JSON kolonunda gayet iyi durur; çünkü veritabanı o değeri bir bütün olarak saklıyor ve uygulamaya bir bütün olarak veriyor. Aynı veri bir gün rapor sorgusunun WHERE koşuluna girerse, o gün kolonun yerini yeniden düşünürsün. JSON kolonunun doğru yeri serinin 11. yazısında ayrıntısıyla ele alınacak.

2NF: Partial Dependency'yi Ayırmak

2NF (Second Normal Form), 1NF'te olan bir tabloda key olmayan hiçbir attribute'un composite key'in yalnızca bir parçasına bağlı olmamasını ister. order_lines_1nf'in key'i (order_no, isbn); ama sipariş tarihi, müşteri ve adres yalnızca order_no'ya, başlık yalnızca isbn'e bağlı. Her partial dependency kendi determinant'ıyla birlikte ayrı bir tabloya çıkıyor, tam key'e bağlı olanlar kalem tablosunda kalıyor:

SELECT DISTINCT order_no, order_date, email, customer, phone, city, region, shipping_address
INTO dbo.orders_2nf
FROM dbo.order_lines_1nf;

SELECT DISTINCT isbn, title
INTO dbo.books_2nf
FROM dbo.order_lines_1nf;

SELECT order_no, isbn, line_no, unit_price, quantity
INTO dbo.order_items_2nf
FROM dbo.order_lines_1nf;
(5 rows affected)

(5 rows affected)

(7 rows affected)

Yedi kalem satırındaki sipariş bilgisi beş sipariş satırına, kitap bilgisi beş kitap satırına indi. Şimdi üç tabloyu birleştirip başlangıçtaki 1NF tablosuyla iki yönlü karşılaştıralım. missing 1NF'te olup birleştirmede olmayan, spurious birleştirmede olup 1NF'te olmayan satırları sayıyor:

WITH rejoined AS (
    SELECT o.order_no, o.order_date, o.customer, o.email, o.phone, o.city, o.region, o.shipping_address,
           oi.line_no, oi.isbn, b.title, oi.unit_price, oi.quantity
    FROM dbo.order_items_2nf AS oi
    JOIN dbo.orders_2nf AS o ON o.order_no = oi.order_no
    JOIN dbo.books_2nf AS b ON b.isbn = oi.isbn
)
SELECT 'missing' AS direction, COUNT(*) AS row_count
FROM (SELECT * FROM dbo.order_lines_1nf EXCEPT SELECT * FROM rejoined) AS m
UNION ALL
SELECT 'spurious', COUNT(*)
FROM (SELECT * FROM rejoined EXCEPT SELECT * FROM dbo.order_lines_1nf) AS s;
direction  row_count
---------  ---------
missing    0
spurious   0

(2 rows affected)

Kayıp yok, sahte satır yok. book_author_lines_1nf ise bu adımda değişmedi: key'i (isbn, author_order) ve tek key olmayan attribute'u olan author_name key'in tamamına bağlı, yani tablo zaten 2NF'te. Bir de şuna bakalım: orders_2nf artık tekrar içermiyor mu?

SELECT order_no, email, customer, phone, city, region
FROM dbo.orders_2nf
ORDER BY order_no;
order_no  email              customer      phone             city      region
--------  -----------------  ------------  ----------------  --------  ----------------
1001      ada@example.com    Ada Lovelace  +90 532 000 0001  Istanbul  Marmara
1002      alan@example.com   Alan Turing   +90 533 000 0002  Ankara    Central Anatolia
1003      ada@example.com    Ada Lovelace  +90 532 000 0001  Istanbul  Marmara
1004      grace@example.com  Grace Hopper  +90 535 000 0003  Izmir     Aegean
1005      alan@example.com   Alan Turing   +90 533 000 0002  Ankara    Central Anatolia

(5 rows affected)

İçeriyor. Ada'nın telefonu, şehri ve bölgesi hâlâ iki satırda, Alan'ınki de. Bir önceki yazının başındaki telefon anomalisi 2NF'te de aynen yaşanabilir, çünkü kaynağı partial değil transitive dependency. Bütün candidate key'leri tek kolondan oluşan bir tablo partial dependency içeremez, çünkü bölünecek bir "parça" yok; dolayısıyla böyle bir tablo 1NF'teyse otomatik olarak 2NF'tedir. Ama bu, tekrar içermediği anlamına gelmez.

3NF: Transitive Dependency'yi Ayırmak

3NF (Third Normal Form), 2NF'te olan bir tabloda key olmayan hiçbir attribute'un key'e başka bir key olmayan attribute üzerinden bağlı olmamasını ister. orders_2nf'te iki zincir var: order_no → email → customer, phone, city ve email → city → region. Her zincirin ortasındaki determinant kendi tablosunun key'i oluyor:

SELECT DISTINCT email, customer, phone, city
INTO dbo.customers_3nf
FROM dbo.orders_2nf;

SELECT DISTINCT city, region
INTO dbo.cities_3nf
FROM dbo.orders_2nf;

SELECT order_no, order_date, email, shipping_address
INTO dbo.orders_3nf
FROM dbo.orders_2nf;
(3 rows affected)

(3 rows affected)

(5 rows affected)

Şimdi beş tabloyu birden birleştirip yine 1NF tablosuyla karşılaştıralım. Aynı script, Excel'deki total kolonunu kalemlerden yeniden hesaplayıp karşılaştırıyor:

WITH rejoined AS (
    SELECT o.order_no, o.order_date, c.customer, c.email, c.phone, c.city, ci.region, o.shipping_address,
           oi.line_no, oi.isbn, b.title, oi.unit_price, oi.quantity
    FROM dbo.order_items_2nf AS oi
    JOIN dbo.orders_3nf AS o ON o.order_no = oi.order_no
    JOIN dbo.customers_3nf AS c ON c.email = o.email
    JOIN dbo.cities_3nf AS ci ON ci.city = c.city
    JOIN dbo.books_2nf AS b ON b.isbn = oi.isbn
)
SELECT 'missing' AS direction, COUNT(*) AS row_count
FROM (SELECT * FROM dbo.order_lines_1nf EXCEPT SELECT * FROM rejoined) AS m
UNION ALL
SELECT 'spurious', COUNT(*)
FROM (SELECT * FROM rejoined EXCEPT SELECT * FROM dbo.order_lines_1nf) AS s;

SELECT o.order_no, SUM(oi.quantity * oi.unit_price) AS computed_total, s.total AS sheet_total
FROM dbo.orders_3nf AS o
JOIN dbo.order_items_2nf AS oi ON oi.order_no = o.order_no
JOIN dbo.order_sheet AS s ON s.order_no = o.order_no
GROUP BY o.order_no, s.total
ORDER BY o.order_no;
direction  row_count
---------  ---------
missing    0
spurious   0

(2 rows affected)
order_no  computed_total  sheet_total
--------  --------------  -----------
1001      731.40          731.40
1002      1150.00         1150.00
1003      1362.90         1362.90
1004      601.50          601.50
1005      340.90          340.90

(5 rows affected)

Beş tablo, yedi satırı eksiksiz ve fazlasız geri verdi. Toplamlar da kalemlerden birebir hesaplanabiliyor; bu yüzden total hiçbir 3NF tablosuna taşınmadı. Saklanması gerekiyorsa bu bir denormalization kararıdır ve serinin 8. yazısının konusu. Artık Ada'nın telefonu tek bir satırda duruyor; bir önceki yazıdaki UPDATE yalnızca o satırı değiştirebilir ve iki farklı telefon oluşamaz. Grace'in siparişini silmek Grace'i silmez, çünkü Grace customers_3nf'te. Foundation, hiç sipariş edilmeden books_2nf'e eklenebilir. Üç anomaly'nin üçü de ortadan kalktı.

Bölme Neden Kayıpsız Oldu?

Her adımda bir tabloyu ikiye ayırdık ve iki parçanın ortak kolonu, parçalardan birinin key'iydi: orders_2nf ile order_items_2nf'in ortak kolonu order_no, orders_2nf'in key'i; customers_3nf ile orders_3nf'in ortak kolonu email, customers_3nf'in key'i. Bu koşul sağlandığında, bir satır birleştirmede karşısında en fazla bir eşleşme bulabilir; sahte satır üretecek çoklu eşleşme oluşamaz. 3. yazıdaki yayın hakkı örneğinde ise ortak kolon book_id'ydi ve iki parçanın hiçbirinde key değildi: Dune iki yayınevine ve iki ülkeye bağlıydı, birleştirme her yayınevini her ülkeyle eşleştirdi. Dependency'lere göre bölmenin pratik güvencesi bu: determinant'ı yeni tablonun key'i yaptığında birleştirme kayıpsız olur.

Sonuç Şema: Bookstore'a Eşleme

3NF tabloları, SQL Temelleri serisindeki Bookstore şemasının iskeletiyle örtüşüyor. Farklar, serinin önceki yazılarında verdiğimiz kararlardan geliyor:

3NF tablosuBookstore karşılığıFark ve nedeni
customers_3nfcustomersKey email yerine surrogate customer_id, e-posta UNIQUE (4. yazı); ad first_name ve last_name olarak bölünmüş
cities_3nfyokBookstore bölge raporu istemediği için region tutulmuyor; şehir müşteride metin
orders_3nfordersSurrogate order_id, müşteri customer_id ile; status eklendi (silme yerine iptal)
order_items_2nforder_itemsbook_id ile kitaba bağlı; UNIQUE (order_id, book_id) eski key'i koruyor
books_2nfbooksSurrogate book_id, ISBN UNIQUE; list_price eklendi
book_author_lines_1nfauthors ve book_authorsYazar kendi kimliğiyle ayrı tabloda; ilişki (book_id, author_id) ve author_order

cities satırı küçük ama öğretici bir karar. 3NF, city → region dependency'si varsa bölgenin ayrı bir tabloda durmasını söyler. Ama dependency'nin var olup olmadığına iş kuralı karar verir: kitapçı bölge raporu istemiyorsa region kolonuna da, cities tablosuna da gerek yok. Normalizasyon "bu veriyi tutuyorsan nerede tutmalısın?" sorusunu cevaplar; "bu veriyi tutmalı mısın?" sorusunu değil.

order_items.unit_price ile books.list_price'ın yan yana durması bir tekrar değil. İlk yazıdaki requirement'a göre sipariş anındaki fiyat değişmez; bu yüzden fiyat {order_no, isbn}'e bağlı ayrı bir olgu, katalog fiyatı ise yalnızca kitaba bağlı başka bir olgu. Normalizasyon ikisini de doğru yere koydu: biri kalem tablosunda, diğeri kitap tablosunda.

Üretimde Dikkat

Normalizasyonu bir kez yapılan bir dönüşüm olarak değil, bir doğrulama aracı olarak kullan. Yeni bir kolon eklerken "bu kolon neye bağlı?" sorusunu sormak, kolonu doğru tabloya koymanın en kısa yolu. Cevap tablonun key'i değilse, kolon yanlış yerde.

Gerçek bir taşıma işinde yukarıdaki EXCEPT kontrolünü script'in parçası yap. Eski düz tablodan yeni şemaya veri taşırken "kayıp ve sahte satır sıfır" kontrolü, migration'ın en ucuz testidir. Toplam ve sayım karşılaştırmaları da (sipariş başına toplam, müşteri başına sipariş sayısı) aynı script'e girer.

Join sayısının arttığını kabul et ama erken optimize etme. 3NF'teki bir sipariş detayı beş tabloyu birleştirmek demek; doğru index'lerle bu, OLTP (Online Transaction Processing) yükünde genellikle sorun değildir. Ölçmeden denormalize etmek, bu yazıda ortadan kaldırdığımız anomaly'leri geri çağırmaktır. Ne zaman ölçüp ne zaman denormalize edeceğin serinin 8. yazısında.

Veri temizliğini normalizasyondan ayrı planla. "Ursula Le Guin" örneğindeki gibi yazım farkları, tekrar eden müşteriler, farklı formattaki telefonlar şemaya taşınmadan önce eşleştirilmeli. Bu iş için staging tablosu, eşleştirme kuralları ve satır bazlı hata raporu Çözüm Desenleri serisinin "Toplu Import ve Validation Raporu" yazısında.

Sık Düşülen Tuzaklar

3NF'i "her şeyi ayrı tabloya böl" diye anlamak. 3NF yalnızca dependency'lere göre böler: determinant'ı key olmayan bir dependency varsa ayırır, yoksa ayırmaz. Müşterinin adını, soyadını ve e-postasını üç ayrı tabloya koymak normalizasyon değil, gereksiz join'dir; hepsi aynı key'e doğrudan bağlı.

Lookup tablosu çılgınlığı. Her metin kolonu için bir lookup tablosu açmak (her şehir, her renk, her durum için ayrı tablo) 3NF'in gerektirdiği bir şey değil. Bir lookup tablosu ancak değerin kendine ait attribute'ları varsa (şehrin bölgesi gibi) ya da değer kümesini veritabanında kısıtlamak istiyorsan anlamlıdır. Enum mu lookup tablosu mu kararı Çözüm Desenleri serisinin "Enum mı Lookup Tablosu mu?" yazısında.

Snapshot'ı tekrar sanıp ayırmak. unit_price'ı "kitabın fiyatı zaten books'ta var" diye kalem tablosundan silmek, geçmiş siparişlerin tutarını bugünkü katalog fiyatına bağlar. Aynı tuzak teslimat adresi için de geçerli.

Derived kolonu taşımak. total'ı orders tablosuna "nasılsa siparişin bilgisi" diye koymak, kalemlerle çelişebilecek bir kopya yaratır. Ya hiç saklama ya da bilinçli olarak sakla ve senkronizasyonunu tasarla.

Listeleri parse ederek normalize etmeye güvenmek. STRING_SPLIT bir kerelik dönüşüm için iyi bir araç; ama ayırıcıyı içeren tek bir değer bütün hizalamayı bozar. Dönüşümden sonra satır sayılarını ve toplamları mutlaka karşılaştır.

Küçük Bir Deneme

★☆☆ order_sheet'e başlığı virgül içeren bir kitapla yeni bir sipariş ekle (N'Tinker, Tailor, Soldier, Spy' gibi) ve 1NF sorgusunu yeniden çalıştır. Hangi satırlar yanlış çıkıyor? Sorgu bunu fark edebilir mi?

★★☆ book_author_lines_1nf'ten bir authors tablosu (author_id, full_name) ve bir book_authors tablosu (isbn, author_id, author_order) türet. Le Guin'in iki yazımını tek bir author_id'ye nasıl eşlersin? Eşledikten sonra kayıpsız birleştirme kontrolünü tekrarla.

★★★ Kitapçı "teslimat adresi siparişe değil müşteriye aittir, müşteri taşınınca eski siparişler de yeni adresi göstersin" diyor. Dependency'leri değiştir, 2NF ve 3NF adımlarını yeniden yap. Hangi tablo değişti? Bu kural bir muhasebe ya da kargo senaryosunda neden tehlikeli?

Bookstore'un iskeleti 3NF'te. Bir sonraki yazıda 3NF'in yetmediği durumlara bakıyoruz: her determinant'ın candidate key olmasını isteyen BCNF (Boyce-Codd Normal Form), bağımsız çok değerli olguları ayıran 4NF, join dependency ile 5NF ve bunların pratikte ne zaman gerçekten gerektiği.