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

İlişkileri Modellemek: One-to-One, One-to-Many, Many-to-Many, Self-Reference ve Weak Entity

Mert Özen 14 Kas 2026 14 dk 2 görüntülenme
İlişkileri Modellemek: One-to-One, One-to-Many, Many-to-Many, Self-Reference ve Weak Entity

İlişkiler tablolara modelden türetilen kurallarla iner: 1-N'de foreign key çok tarafındadır, M-N kendi kimliği olan bir associative entity'dir, 1-1'de foreign key opsiyonel taraftadır ve primary key'dir. SQL Server'da rol eklenince primary key'i değişen bağlantı tablosunu, self-reference'taki döngü tuzağını, iki binary'ye bölününce sahte satır üreten ternary ilişkiyi, weak entity'den ON DELETE CASCADE türetmeyi ve nullable foreign key'in iki anlamını 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: Bağlantı Tablosuna Bir Kolon Eklemek

Bookstore'da kitap ile yazar arasındaki M-N ilişki, çoğu projede olduğu gibi iki foreign key'den oluşan bir tabloyla kurulmuş. Primary key iki kolonun birleşimi, yazar sırası da tabloda duruyor:

CREATE TABLE dbo.book_authors (
    book_id      INT     NOT NULL,
    author_id    INT     NOT NULL,
    author_order TINYINT NOT NULL,
    CONSTRAINT PK_book_authors PRIMARY KEY (book_id, author_id),
    CONSTRAINT FK_book_authors_books FOREIGN KEY (book_id) REFERENCES dbo.books (book_id),
    CONSTRAINT FK_book_authors_authors FOREIGN KEY (author_id) REFERENCES dbo.authors (author_id),
    CONSTRAINT UQ_book_authors_order UNIQUE (book_id, author_order)
);
INSERT INTO dbo.book_authors (book_id, author_id, author_order)
VALUES (26, 18, 1), (42, 29, 1), (42, 30, 2), (41, 20, 1);

Yeni requirement geliyor: kitabın künyesinde yazarların yanında çevirmen, editör ve önsöz yazarı da görünecek ve aynı kişi bir kitapta birden fazla rolde olabilir. Diyelim ki Good Omens'ın yeni bir baskısında önsözü de Neil Gaiman yazdı. Ekip tabloya bir rol kolonu ekleyip yeni satırı yazıyor. Tahmin et: Bu iki komuttan hangisi hata verir?

ALTER TABLE dbo.book_authors
    ADD contribution VARCHAR(20) NOT NULL
        CONSTRAINT DF_book_authors_contribution DEFAULT ('author') WITH VALUES;
GO

INSERT INTO dbo.book_authors (book_id, author_id, author_order, contribution)
VALUES (42, 30, 3, 'foreword');
Msg 2627, Level 14, State 1, Server b8be943222fa, Line 2
Violation of PRIMARY KEY constraint 'PK_book_authors'. Cannot insert duplicate key in object 'dbo.book_authors'. The duplicate key value is (42, 30).
The statement has been terminated.

Kolon sorunsuz eklendi, mevcut satırlar 'author' değerini aldı. Ama yeni satır girmedi, çünkü primary key hâlâ "bir kitapta bir kişi bir kez görünür" diyor. Rol eklemek aslında ilişkinin kimliğini değiştirdi: artık bir satırı ayırt eden şey kitap ve kişi ikilisi değil, kitap, kişi ve rol üçlüsü. Bağlantı tablosunu "iki foreign key'den ibaret bir teknik detay" olarak gördüğünde bu kararı hiç vermemiş oluyorsun. Bu yazıda ilişkileri tablolara indirmeyi konuşuyoruz: 1-N, M-N, 1-1, self-reference, ternary ilişki, weak entity ve bunların her birinin şemada nereye ve nasıl düştüğü. CREATE TABLE ve constraint sözdiziminin ayrıntısı SQL Temelleri serisinin 4. yazısında; burada odak, kararın modelden nasıl çıktığı.

1-N: Foreign Key Neden Hep "Çok" Tarafında?

Bir müşterinin çok siparişi, bir siparişin tek müşterisi var. Bu ilişkiyi kurmanın tek doğru yolu, "çok" tarafındaki tabloya, yani orders'a bir customer_id kolonu koymaktır. Tersini yapmak, yani customers tablosuna sipariş numaralarını yazmak, ya tek bir kolona liste sıkıştırmak ya da order_1, order_2, order_3 gibi tekrarlayan kolonlar açmak demektir. Serinin çalışma malzemesi olan sipariş Excel'inde kitapların virgülle tek hücreye yazılması tam olarak bu hatanın örneği.

Bunun bir kuralı var: ilişkisel modelde her hücre tek bir değer taşır. "Bir"in tarafı "çok"u listeleyemez, ama "çok"un her satırı tek bir "bir"i gösterebilir. Ok her zaman çoktan bire doğru çizilir. Optionality ise foreign key kolonunun NULL olup olamayacağını belirler: siparişin müşterisi zorunluysa orders.customer_id NOT NULL. Entity Framework Core (EF Core) tarafında bu ilişkinin nasıl map edildiği EF Core serisinin "EF Core'da İlişkiler 1: One-to-Many, Foreign Key ve Cascade Delete" yazısında.

M-N: Associative Entity Yalnızca Bir Bağlantı Tablosu Değildir

M-N ilişkiyi iki tablodan birine foreign key koyarak kuramazsın; iki taraf da "çok". Çözüm üçüncü bir tablo: her satırı "bu kitap ile bu yazar arasındaki tek bir bağ"ı temsil eden associative entity. Önemli olan, bu tablonun kendine ait bir anlamı olduğunu kabul etmek. book_authors bir kavram: bir kişinin bir kitaba katkısı. Katkının sırası var, türü var, ileride başka attribute'ları da olabilir (telif oranı gibi). Yukarıdaki hatayı düzeltmek için primary key'i yeni kimliğe göre yeniden kurmamız gerekiyor:

ALTER TABLE dbo.book_authors DROP CONSTRAINT PK_book_authors;
ALTER TABLE dbo.book_authors
    ADD CONSTRAINT PK_book_authors PRIMARY KEY (book_id, author_id, contribution);
GO

INSERT INTO dbo.book_authors (book_id, author_id, author_order, contribution)
VALUES (42, 30, 3, 'foreword');

SELECT b.title, a.full_name, ba.contribution, ba.author_order
FROM dbo.book_authors AS ba
JOIN dbo.books AS b ON b.book_id = ba.book_id
JOIN dbo.authors AS a ON a.author_id = ba.author_id
WHERE ba.book_id = 42
ORDER BY ba.author_order;
(1 rows affected)
title       full_name        contribution  author_order
----------  ---------------  ------------  ------------
Good Omens  Terry Pratchett  author        1
Good Omens  Neil Gaiman      author        2
Good Omens  Neil Gaiman      foreword      3

(3 rows affected)

Canlı bir sistemde bu iki satırlık değişiklik o kadar masum değil. Primary key genellikle clustered index'tir; düşürüp yeniden kurmak tablonun yeniden yazılması, o sırada tabloya erişenlerin beklemesi ve bu tabloya bağlanan başka foreign key'ler varsa onların da düşürülüp yeniden kurulması demektir. Associative entity'yi baştan "bir kavram" olarak modellemek, ilk gün şu soruyu sormayı sağlar: Bu ilişkinin kimliği ne? Aynı kişi aynı kitapta iki kez görünebilir mi? Cevap "evet, farklı rollerle" ise rol baştan key'in parçası olur.

Tablodaki UQ_book_authors_order constraint'i de bir modelleme kararı: aynı kitapta iki katkı aynı sırada olamaz. Bunu uygulama koduna bırakmak, iki ekranın aynı anda kaydettiği bir günde kapakta iki "ilk yazar" görmek demektir. EF Core'da associative entity'yi kendi class'ı olarak map etmenin yolu EF Core serisinin "EF Core'da İlişkiler 2: One-to-One, Many-to-Many ve UsingEntity" yazısında.

1-1'in Üç Gerçek Nedeni

Gerçek bir 1-1 ilişki, iki tablonun aslında tek bir entity olduğunu düşündürür ve çoğu zaman öyledir. Ayrı tablo için meşru nedenler sınırlı: Birincisi opsiyonel ve büyük veri. Kitabın uzun tanıtım metni ve içindekiler listesi her kitapta yok, olduğunda da yalnızca detay sayfasında okunuyor. Kategori listesini çeken sorgunun bu metinleri taşıyan satırlarla uğraşmasına gerek yok. İkincisi güvenlik ayrımı. Müşterinin parola hash'i ya da kimlik doğrulama bilgileri, müşteri profilini okuyan herkesin yetkisi olmaması gereken bir tabloda durabilir; yetkiyi tablo seviyesinde vermek kolon seviyesinde vermekten daha az hata kaldırır. Üçüncüsü subtype. Bir kitap e-kitap da olabilir, basılı da; e-kitaba özgü dosya boyutu ve format, basılıya özgü ağırlık ve sayfa ölçüsü ayrı tablolarda durabilir. EF Core'un bu yapıyı TPT (Table per Type) olarak nasıl map ettiği EF Core serisinin "EF Core'da Inheritance Mapping" yazısında.

Foreign key opsiyonel olan tarafa konur ve aynı zamanda primary key olur:

CREATE TABLE dbo.book_details (
    book_id           INT            NOT NULL,
    long_description  NVARCHAR(MAX)  NOT NULL,
    table_of_contents NVARCHAR(MAX)  NULL,
    CONSTRAINT PK_book_details PRIMARY KEY (book_id),
    CONSTRAINT FK_book_details_books FOREIGN KEY (book_id) REFERENCES dbo.books (book_id)
);
INSERT INTO dbo.book_details (book_id, long_description)
VALUES (26, N'Set on the desert planet Arrakis...');
INSERT INTO dbo.book_details (book_id, long_description)
VALUES (26, N'A second description for the same book');
(1 rows affected)
Msg 2627, Level 14, State 1, Server b8be943222fa, Line 10
Violation of PRIMARY KEY constraint 'PK_book_details'. Cannot insert duplicate key in object 'dbo.book_details'. The duplicate key value is (26).
The statement has been terminated.

book_details.book_id hem primary key hem foreign key olduğu için bir kitabın en fazla bir detay satırı olabiliyor; 1-1'i ilişkinin maksimumu olarak şema garanti ediyor. Foreign key'i ters tarafa, yani books.book_details_id olarak koysaydın, detayı olmayan her kitap için nullable bir kolon taşıyacak ve iki kitabın aynı detay satırını göstermesini engellemek için ayrıca bir unique constraint yazacaktın. Kural basit: foreign key, var olmak için diğerine ihtiyaç duyan tarafta durur.

Self-Reference: Kendine Bağlanan Tablo

Kategoriler iç içe: Science Fiction'ın üst kategorisi Fiction. Bu unary ilişki, tablonun kendi primary key'ine bakan bir foreign key ile kurulur: categories.parent_category_id. En üstteki kategorilerin parent'ı yok, bu yüzden kolon nullable. Foreign key var olmayan bir parent'ı engeller, ama hiyerarşinin anlamını korumaz:

ALTER TABLE dbo.categories
    ADD CONSTRAINT CK_categories_not_own_parent CHECK (parent_category_id <> category_id);

UPDATE dbo.categories SET parent_category_id = 2 WHERE category_id = 2;

UPDATE dbo.categories SET parent_category_id = 3 WHERE category_id = 1;
SELECT category_id, name, parent_category_id FROM dbo.categories;
Msg 547, Level 16, State 1, Server b8be943222fa, Line 4
The UPDATE statement conflicted with the CHECK constraint "CK_categories_not_own_parent". The conflict occurred in database "dm_03", table "dbo.categories".
The statement has been terminated.

(1 rows affected)
category_id  name             parent_category_id
-----------  ---------------  ------------------
1            Fiction          3
2            Science Fiction  1
3            Fantasy          1

(3 rows affected)

CHECK constraint bir kategorinin kendisinin parent'ı olmasını engelledi. Ama ikinci UPDATE ile Fiction'ın parent'ını Fantasy yaptım ve Fantasy'nin parent'ı zaten Fiction. Artık bir döngü var ve kökü olmayan bu ağacı gezen her recursive CTE (Common Table Expression) ya MAXRECURSION sınırına çarpacak ya da yanlış sonuç verecek. CHECK constraint tek bir satıra bakar; döngü ise birden çok satırın birlikte oluşturduğu bir durum. Döngüyü engellemek bir trigger, yazma işlemini tek bir yerden yapan uygulama kodu ya da farklı bir hiyerarşi modeli gerektirir. Adjacency list, closure table ve hierarchyid karşılaştırması Çözüm Desenleri serisinin "SQL'de Hiyerarşik Veri" yazısında; recursive sorgunun kendisi SQL Temelleri serisinin "CTE, View ve Recursive Sorgular" yazısında.

Ternary İlişki Neden İki Binary'ye Bölünemez?

Kitapçı yabancı yayınevleriyle çalışmaya başlıyor ve bir kural var: bir kitabın bir ülkedeki basım hakkı tek bir yayınevindedir. "Bu yayınevi, bu kitabı, bu ülkede basabilir" cümlesi üç entity'yi aynı anda bağlayan tek bir olgu. Primary key (book_id, country_code), çünkü kurala göre bir kitap ile bir ülke ikilisi tek bir yayınevini belirler. Yayınevi adları örnek için uydurulmuş adlar:

CREATE TABLE dbo.publishers (
    publisher_id INT           NOT NULL,
    name         NVARCHAR(100) NOT NULL,
    CONSTRAINT PK_publishers PRIMARY KEY (publisher_id)
);
CREATE TABLE dbo.publishing_rights (
    book_id      INT     NOT NULL,
    publisher_id INT     NOT NULL,
    country_code CHAR(2) NOT NULL,
    CONSTRAINT PK_publishing_rights PRIMARY KEY (book_id, country_code),
    CONSTRAINT FK_publishing_rights_books FOREIGN KEY (book_id) REFERENCES dbo.books (book_id),
    CONSTRAINT FK_publishing_rights_publishers FOREIGN KEY (publisher_id) REFERENCES dbo.publishers (publisher_id)
);
INSERT INTO dbo.publishers VALUES (1, N'Northwind Books'), (2, N'Harbor Press');
INSERT INTO dbo.publishing_rights VALUES (26, 1, 'TR'), (26, 2, 'DE'), (42, 1, 'TR');

Dune'un Türkiye'deki hakkı Northwind Books'ta, Almanya'daki hakkı Harbor Press'te. Bu tablo "gereksiz karmaşık" görünebilir ve birisi onu iki binary ilişkiye bölmeyi önerebilir: kitap–yayınevi ve kitap–ülke. Bölüp tekrar birleştirelim ve orijinal tabloda olmayan satırları arayalım:

SELECT DISTINCT book_id, publisher_id INTO dbo.book_publishers FROM dbo.publishing_rights;
SELECT DISTINCT book_id, country_code INTO dbo.book_countries FROM dbo.publishing_rights;

SELECT bp.book_id, bp.publisher_id, bc.country_code
FROM dbo.book_publishers AS bp
JOIN dbo.book_countries AS bc ON bc.book_id = bp.book_id
EXCEPT
SELECT book_id, publisher_id, country_code
FROM dbo.publishing_rights;
(3 rows affected)

(3 rows affected)
book_id  publisher_id  country_code
-------  ------------  ------------
26       1             DE
26       2             TR

(2 rows affected)

İki sahte satır çıktı. Birleştirilmiş veri "Northwind Books Dune'u Almanya'da basabilir" ve "Harbor Press Dune'u Türkiye'de basabilir" diyor; ikisi de yanlış. Bölünmüş iki tablo "Dune ile hangi yayınevleri çalışıyor?" ve "Dune hangi ülkelerde satılıyor?" sorularını doğru cevaplıyor, ama "hangi yayınevi hangi ülkede?" bilgisi kayboldu, çünkü o bilgi ne kitap–yayınevi ne de kitap–ülke çiftine ait. Üçünün birlikteliğine ait. Bir tabloyu bölüp geri birleştirince sahte satır üretiliyorsa o bölme kayıplıdır (lossy). Ternary ilişkilerin hangi koşulda gerçekten üç parçaya bölünebildiği ise join dependency ve 5NF konusu; serinin 7. yazısında ayrıntısıyla ele alınacak.

Weak Entity ve ON DELETE'i Modelden Türetmek

Sipariş kalemi kendi başına var olamaz: "1003 numaralı siparişin 1. kalemi" demeden bir kalemden söz edemezsin. ER (Entity-Relationship) modelinde buna weak entity, parent'ıyla arasındaki ilişkiye identifying relationship denir. Weak entity'nin key'i parent'ın key'ini içerir:

CREATE TABLE dbo.orders (
    order_id   INT          NOT NULL,
    ordered_at DATETIME2(0) NOT NULL,
    CONSTRAINT PK_orders PRIMARY KEY (order_id)
);
CREATE TABLE dbo.order_items (
    order_id   INT           NOT NULL,
    line_no    SMALLINT      NOT NULL,
    book_id    INT           NOT NULL,
    quantity   INT           NOT NULL,
    unit_price DECIMAL(10,2) NOT NULL,
    CONSTRAINT PK_order_items PRIMARY KEY (order_id, line_no),
    CONSTRAINT FK_order_items_orders FOREIGN KEY (order_id)
        REFERENCES dbo.orders (order_id) ON DELETE CASCADE,
    CONSTRAINT FK_order_items_books FOREIGN KEY (book_id)
        REFERENCES dbo.books (book_id)
);
INSERT INTO dbo.orders VALUES (1001, '2026-03-02T09:30:00'), (1003, '2026-03-09T10:15:00');
INSERT INTO dbo.order_items VALUES
    (1001, 1, 26, 1, 390.50), (1001, 2, 42, 1, 340.90),
    (1003, 1, 26, 2, 351.45);

İki foreign key'in silme davranışı farklı ve ikisi de modelden geliyor. Kalem siparişsiz anlamsız olduğu için sipariş silinirse kalemlerinin de silinmesi modelle tutarlı: ON DELETE CASCADE. Kitap ise kalemin parent'ı değil, kalemin referans verdiği bağımsız bir entity. Satılmış bir kitabın silinmesi, geçmiş siparişlerin anlamını bozar; bu yüzden varsayılan NO ACTION doğru seçim. Deneyelim:

DELETE FROM dbo.orders WHERE order_id = 1001;
SELECT order_id, line_no, book_id FROM dbo.order_items;

DELETE FROM dbo.books WHERE book_id = 26;
(1 rows affected)
order_id  line_no  book_id
--------  -------  -------
1003      1        26

(1 rows affected)
Msg 547, Level 16, State 1, Server b8be943222fa, Line 4
The DELETE statement conflicted with the REFERENCE constraint "FK_book_authors_books". The conflict occurred in database "dm_03", table "dbo.book_authors", column 'book_id'.
The statement has been terminated.

1001 silinince iki kalemi de gitti, 1003'ün kalemi duruyor. Dune'u silme denemesi ise reddedildi. Hata mesajı book_authors'tan geldi, çünkü bu demo veritabanında Dune'a bağlı üç tablo var (book_authors, book_details, order_items) ve SQL Server çakışan ilk constraint'i raporluyor. Hangisi olursa olsun sonuç aynı: kitap, ona bağlı satırlar varken silinemez.

Bir incelik daha var. Serinin ilk yazısındaki requirement listesine göre siparişler zaten silinmiyor, iptal ediliyor. O zaman CASCADE neden var? Çünkü foreign key'in silme davranışı, "bu satırı kim silerse silsin ne olmalı?" sorusunun cevabı. Test verisi temizliği, kişisel verinin silinmesi talebi ya da yanlış oluşturulmuş bir taslak sipariş gibi istisnai durumlarda kalemlerin sahipsiz kalmaması gerekiyor. Bazı ekipler tam tersini tercih eder: siparişte de NO ACTION bırakıp silmeyi bilinçli olarak iki adımlı yapmak. İki yol da savunulabilir; savunulamayan, bu kararın varsayılana bırakılması. SQL Server Derinlemesine serisinin "Function'lar ve Trigger'lar" yazısı ise cascade zincirleri ile trigger'ların bir arada nasıl davrandığını anlatıyor.

Nullable Foreign Key: Bilinmiyor mu, Yok mu?

Bookstore şemasında books.category_id nullable ve iki kitabın kategorisi yok. Bir de ileride gelecek orders.coupon_id kolonunu düşün. İki kolon da NULL taşıyor ama aynı şeyi söylemiyor. Kuponsuz sipariş için NULL "kupon yok" demek ve bu bilinen, kesin bir bilgi. Kategorisiz kitap için NULL ise büyük ihtimalle "henüz sınıflandırılmadı" demek, yani bilinmiyor. Rapor yazan biri ikisini aynı şekilde yorumlarsa "kategorisiz kitapların cirosu" diye bir satır üretir ve okuyan bunun bir kategori olduğunu sanır.

Modelde bu farkı açıkça yazmalısın. Üç yol var. Birincisi kolonun anlamını belgelemek: SQL Server'da extended property, PostgreSQL'de COMMENT ON COLUMN; serinin son yazısında şemayı bu açıklamalarla birlikte kuracağız. İkincisi "bilinmiyor" durumunu ayrı bir değerle ifade etmek: "Uncategorized" adında gerçek bir kategori satırı. Bu bazen doğru, bazen bir sentinel value tuzağıdır; tartışması Çözüm Desenleri serisinin "Sentinel Value'lar" yazısında. Üçüncüsü, optionality'yi yeniden sorgulamak: requirement "her kitap bir kategoriye bağlıdır" diyorsa kolon NOT NULL olmalı ve kategorisiz kitap kaydedilememeli.

Bir foreign key'in birden çok tablodan birine bağlanması gerektiğinde (yorumun kitaba da yazara da yapılabilmesi gibi) ortaya çıkan polymorphic association ise ayrı bir problem; seçenekleri Çözüm Desenleri serisinin "Polymorphic Association" yazısında.

Üretimde Dikkat

Her foreign key kolonuna index'i düşün. SQL Server foreign key tanımlayınca child tarafta otomatik index oluşturmaz. Parent'tan bir satır silinirken ya da key'i güncellenirken motor child tabloda eşleşen satırları arar; index yoksa bu arama tablonun tamamını tarar ve lock süresini uzatır. Index tasarımı SQL Server Derinlemesine serisinin "Nonclustered Index Tasarımı" yazısında.

Cascade zincirlerini haritala. ON DELETE CASCADE birden çok seviye boyunca yayılabilir ve tek bir DELETE beklemediğin kadar çok satırı silebilir. SQL Server, aynı tabloya birden çok cascade yolunun ulaştığı tanımları zaten reddeder; ama izin verilen bir zincirin ne kadar uzadığını görmek senin işin.

Associative entity'nin primary key'ini değiştirmek bir migration projesidir. Clustered index'in yeniden kurulması, bağlı foreign key'ler ve uygulamadaki sorgular birlikte değişir. Bu değişikliğin canlıda nasıl kesintisiz yapılacağı Deployment Stratejileri serisinin "Migration'ların Deploy'daki Yeri" yazısında.

Sık Düşülen Tuzaklar

Nullable foreign key'in anlamını belirlememek. Yukarıda gördüğümüz gibi NULL "yok" ile "bilinmiyor" arasında gidip gelir; hangisi olduğu yazılı değilse her rapor kendi yorumunu yapar.

M-N tablosunu yalnızca iki foreign key olarak kurup sonradan attribute eklemek. İlişkinin kimliği değişiyorsa primary key de değişir ve bu canlı bir tabloda pahalı bir iştir.

1-1'i gereksiz yere bölmek. Ortada büyük ve opsiyonel veri, güvenlik ayrımı ya da subtype yoksa, iki tablo yalnızca her okumada bir join daha ve her yazmada bir tutarlılık sorunu daha demektir.

Ternary ilişkiyi iki binary'ye bölmek. Bölünen tablolar tek tek doğru görünür; hata ancak birleştirildiklerinde, sahte satırlar olarak ortaya çıkar.

Silme davranışını varsayılana bırakmak. NO ACTION da CASCADE da bir karardır; foreign key'i yazan kişi hangisini neden seçtiğini söyleyebilmeli.

Self-reference'ta yalnızca foreign key'e güvenmek. Foreign key var olmayan parent'ı engeller ama döngüyü engellemez.

Küçük Bir Deneme

★☆☆ book_details tablosuna bir satır ekle, sonra FK_book_details_books foreign key'ini ON DELETE CASCADE ile yeniden tanımla. Bu ilişki için cascade modelle tutarlı mı? book_details bir weak entity mi?

★★☆ categories tablosunda döngü oluşmasını engelleyen bir AFTER UPDATE trigger yaz: güncellenen satırdan yukarı doğru recursive CTE ile çık, aynı category_id'ye tekrar ulaşırsan transaction'ı geri al. Yukarıdaki Fiction–Fantasy döngüsünü tekrar dene.

★★★ publishing_rights tablosunda kuralı değiştir: artık bir kitabın bir ülkede birden çok yayınevi olabilir (farklı formatlar için). Primary key ne olmalı? Aynı bölme ve birleştirme deneyini yeni veriyle tekrarla; sahte satır hâlâ çıkıyor mu?

İlişkileri tablolara indirdik; her adımda bir key kararı verdik ama key'in kendisini hiç sorgulamadık. Bir sonraki yazıda superkey'den candidate key'e, natural key'den surrogate key'e ve composite key'e uzanan zinciri, ISBN (International Standard Book Number), e-posta ve kimlik numarası gibi "doğal" key adaylarının tuzaklarını ve fiziksel tip kararını konuşuyoruz.