ER Modeling: Entity, Attribute, Relationship ve Crow's Foot Notation
ER modeling, veriyi entity, attribute ve relationship olarak çizer; bir ilişkiyi doğru anlatmak için her iki ucun hem maksimumu (cardinality) hem minimumu (optionality) gerekir. Simple, composite, multivalued ve derived attribute'ları, unary, binary ve ternary ilişkileri, Chen ile Crow's Foot notation'larını, Bookstore'un Mermaid erDiagram taslağını ve SQL Server catalog view'larından diyagram üretmenin neyi kaybettiğini gösteriyoruz.
Sürüm notu: Bu yazıdaki SQL (Structured Query Language) sorguları SQL Server 2025 (17.0, Cumulative Update 10) üzerinde çalıştırılmış, Mermaid diyagramları Mermaid 12.3.0 ile çizdirilerek doğrulanmıştır.
Önce Tahmin Et: Bu İki Çizgi Aynı Şeyi mi Söylüyor?
Bir önceki yazıda kitapçının requirement'larından "müşteri sipariş verir" ilişkisini çıkarmıştık. Ekipten iki kişi bu ilişkiyi Mermaid ile iki farklı şekilde çiziyor:
erDiagram
CUSTOMER ||..o{ ORDER : places
CUSTOMER |o..|{ ORDER : places
İki satır da geçerli bir diyagram üretiyor, ikisi de müşteri ile sipariş arasında bir çizgi çekiyor. Tahmin et: Hangisinde müşterisi olmayan bir sipariş olabilir? Hangisinde hiç siparişi olmayan bir müşteri kayıt olamaz? Hangisini seçtiğin, foreign key kolonunun NULL alıp alamayacağını değiştirir mi?
Cevap şu: İlk satırda her siparişin tam bir müşterisi var (||), müşterinin ise sıfır ya da çok siparişi olabilir (o{). İkinci satırda sipariş müşterisiz olabilir (|o), müşteri ise en az bir sipariş vermeden var olamaz (|{). Kitapçının gerçeği ilki: üyelik siparişten önce gelir ve misafir siparişi yok. İkinci satır kâğıt üstünde zararsız görünür ama veritabanına orders.customer_id NULL olarak iner ve "sahipsiz sipariş" diye bir kavramı modele sokar. Bu yazıda böyle farkları çizimden okuyabilmeyi öğreniyoruz: entity, attribute, relationship, cardinality ve optionality; Chen ve Crow's Foot notation'ları; ve ER (Entity-Relationship) diyagramını kodun yanında tutmanın yolu.
Entity ve Attribute Türleri
Entity, hakkında bilgi tutmak istediğimiz ve diğerlerinden ayırt edilebilen bir şeydir: müşteri, kitap, sipariş. Entity instance ise onun tek bir örneğidir: Ada Lovelace adlı müşteri, Dune adlı kitap. ER modelinde kutuya yazdığımız şey entity'dir, tabloya satır olarak giren şey instance. Peter Chen 1976'da ACM'nin (Association for Computing Machinery) Transactions on Database Systems dergisinde yayımladığı makalede entity ve relationship ayrımını bu şekilde ortaya koydu.
Attribute, entity'nin bir özelliğidir ve dört türü modelleme kararlarını doğrudan etkiler.
Simple attribute bölünemeyen tek bir değerdir: kitabın başlığı, müşterinin e-postası. Composite attribute anlamlı parçalardan oluşur: adres; sokak, ilçe, şehir ve posta kodundan. Adresi tek bir metin kolonu olarak mı yoksa parçalarına bölünmüş olarak mı tutacağın, parçalar üzerinde sorgu yapıp yapmayacağına bağlıdır. "İzmir'deki müşteriler" sorusu geliyorsa şehir ayrı bir attribute olmak zorunda.
Multivalued attribute, tek bir instance için birden çok değer taşır: müşterinin telefonları, kitabın etiketleri. ER diyagramında çift çizgili elipsle gösterilir ve logical modele geçerken genellikle ayrı bir tabloya dönüşür. Multivalued attribute'u tek kolona virgülle sıkıştırmak, serinin çalışma malzemesi olan sipariş Excel'inin books ve authors kolonlarında gördüğümüz hatanın ta kendisi.
Derived attribute başka attribute'lardan hesaplanabilir: yazarın yaşı doğum yılından, siparişin toplamı kalemlerin adet ve fiyatından. ER diyagramında kesikli elipsle gösterilir. Derived attribute'u saklamak da saklamamak da meşru bir karardır; ama saklıyorsan, kaynağı değişince kimin güncelleyeceğini de modelin parçası olarak yazman gerekir. Bu karar serinin 8. yazısında denormalization olarak geri dönecek.
Bir de key attribute var: bir entity instance'ını tek başına ayırt eden attribute ya da attribute kümesi. Chen notation'da altı çizili yazılır. Key seçimi o kadar önemli ki serinin 4. yazısını tamamen ona ayırdık.
Relationship ve Degree
Relationship, entity'ler arasındaki bir ilişkidir ve kendi adı vardır: müşteri sipariş verir, sipariş kitap içerir. Bir relationship'e katılan entity sayısına degree denir.
Unary (ya da recursive) relationship bir entity'nin kendisiyle ilişkisidir. Bookstore'daki örneği kategoriler: Bilim Kurgu bir kategori, Kurgu da bir kategori ve biri diğerinin üst kategorisi. Binary relationship iki entity arasındadır ve modellerin büyük çoğunluğu bunlardan oluşur. Ternary relationship ise üç entity'yi aynı anda bağlar: "Bu yayınevi bu kitabı bu ülkede basma hakkına sahip" cümlesi kitap, yayınevi ve ülke arasında tek bir olgudur. Ternary relationship'i iki binary relationship'e bölmeye çalışınca ne kaybedildiğini bir sonraki yazıda SQL ile göreceğiz.
Relationship'in de attribute'u olabilir. Sipariş ile kitap arasındaki "içerir" ilişkisinin adedi, kitap ile yazar arasındaki "yazar" ilişkisinin yazar sırası böyledir. Bu attribute'lar ne iki entity'den birine aittir ne de bir yerde "tekrar" sayılır; ilişkinin kendisinin bilgisidir. Logical modelde bu tür ilişkiler associative entity'ye dönüşür.
Cardinality ve Optionality
Bir relationship'i "1-N" diye anlatmak yarım bilgidir. Her iki yönün de bir minimumu ve bir maksimumu var:
- Bir müşterinin en az 0, en fazla çok siparişi olur.
- Bir siparişin en az 1, en fazla 1 müşterisi olur.
- Bir siparişin en az 1, en fazla çok kalemi olur.
- Bir kitabın en az 0, en fazla 1 kategorisi olur (henüz sınıflandırılmamış kitaplar varsa).
Maksimum tarafına cardinality, minimum tarafına optionality (ya da participation) denir. Maksimum, ilişkinin hangi tarafta foreign key'le, hangi tarafta ayrı bir tabloyla kurulacağını belirler. Minimum ise doğrudan bir constraint'e dönüşür: siparişin müşterisi en az 1 ise orders.customer_id NOT NULL olur; kitabın kategorisi en az 0 ise books.category_id nullable olur. "Bir siparişin en az bir kalemi olur" kuralı ise ilginçtir: bunu foreign key ile ifade edemezsin, çünkü kural parent tarafında. Ya uygulama kodunda, ya trigger'da, ya da siparişi ve kalemlerini aynı transaction'da yazan tek bir yolla korursun. Diyagramın bu kuralı göstermesi, şemanın onu tek başına garanti ettiği anlamına gelmez.
Chen ve Crow's Foot: Aynı Modelin İki Çizimi

Chen notation, Chen'in 1976 makalesindeki çizim dilidir: entity dikdörtgen, relationship eşkenar dörtgen, attribute elips. Cardinality çizgilerin üstüne yazılan 1, N, M gibi etiketlerle, zorunlu katılım ise çift çizgiyle gösterilir. Gücü kavramsal netliktir: relationship ayrı bir şekil olduğu için ilişkinin attribute'u (yukarıdaki quantity) doğrudan ilişkiye bağlanır, ternary relationship tek bir eşkenar dörtgenle çizilir. Zayıf yanı yer kaplamasıdır; yirmi tablolu bir şemada elipsler okunmaz hale gelir.
Crow's Foot notation, çizgi uçlarındaki sembollerle konuşur. Literatürde kökeni genellikle Gordon Everest'in 1976 tarihli bir konferans makalesine bağlanır; Everest sembole o zaman "inverted arrow" (ters ok) demiş, "karga ayağı" adı sonradan yerleşmiş. Information Engineering yöntemiyle yaygınlaştı ve bugün çoğu modelleme aracının varsayılan notation'ı. Her çizgi ucunda iki sembol vardır: entity'ye yakın olan maksimumu (tek çizgi = bir, çatal = çok), uzak olan minimumu (çizgi = bir, daire = sıfır) gösterir. Attribute'lar kutunun içinde listelenir, bu da diyagramı tablo yapısına yaklaştırır.
Figürün alt yarısında bir fark daha görünüyor: Chen'de "içerir" bir relationship ve quantity onun attribute'u. Crow's Foot'ta ise M-N ilişki çoğu zaman doğrudan ORDER_ITEM kutusuyla çizilir, çünkü bu notation logical modele daha yakın. İkisi aynı modeli anlatıyor; Chen iş birimiyle kavramı konuşurken, Crow's Foot ise geliştiriciyle tabloyu konuşurken daha rahat okunur.
Bookstore'un İlk ER Taslağı: Mermaid ile Kodda
ER diyagramını bir çizim aracında tutmanın en büyük sorunu, diyagramın şemadan kopmasıdır. Mermaid, diyagramı düz metin olarak yazmanı sağlar; dosya repository'de durur, pull request'te diff'lenir, GitHub ve GitLab markdown içindeki Mermaid bloklarını doğrudan çizer. Bookstore'un ilk taslağı:
erDiagram
CUSTOMER ||..o{ ORDER : places
ORDER ||--|{ ORDER_ITEM : contains
BOOK ||..o{ ORDER_ITEM : "sold as"
AUTHOR }|..|{ BOOK : writes
CATEGORY ||..o{ BOOK : classifies
CATEGORY |o..o{ CATEGORY : "parent of"
CUSTOMER ||..o{ REVIEW : writes
BOOK ||..o{ REVIEW : receives
CUSTOMER {
int customer_id PK
string email UK
string first_name
string last_name
string phone
string city
}
ORDER {
int order_id PK
int customer_id FK
datetime ordered_at
string status
}
ORDER_ITEM {
int order_id PK, FK
int line_no PK
int book_id FK
int quantity
decimal unit_price
}
BOOK {
int book_id PK
string isbn UK
string title
decimal list_price
int category_id FK
}
AUTHOR {
int author_id PK
string first_name
string last_name
int birth_year
}
CATEGORY {
int category_id PK
string name UK
int parent_category_id FK
}
REVIEW {
int review_id PK
int book_id FK
int customer_id FK
int rating
}
Sözdizimi Crow's Foot'un metin hâli. Her ilişki satırında iki uç var ve her uç iki karakterden oluşuyor: dıştaki maksimum, içteki minimum. || tam bir, |o sıfır ya da bir, o{ sıfır ya da çok, |{ bir ya da çok. Aradaki çizgi de anlam taşıyor: -- identifying relationship (düz çizgi), .. non-identifying relationship (kesikli çizgi). Identifying, child entity'nin kimliğinin parent'ı içerdiği anlamına gelir: ORDER_ITEM'ın key'i (order_id, line_no), yani bir kalem siparişi olmadan tanımlanamıyor. REVIEW ise kendi review_id'sine sahip; kitapla ilişkisi var ama kimliği ona bağlı değil. İlişki etiketi zorunlu; Mermaid etiketsiz bir ilişki satırını parse etmiyor.
Bu taslakta bilinçli olarak bıraktığım iki karar var. AUTHOR }|..|{ BOOK bir M-N ilişki ve henüz associative entity'ye çevrilmedi; conceptual seviyede bu doğru, logical seviyede book_authors tablosuna dönüşecek. CATEGORY ||..o{ BOOK ise bir önceki yazıdaki R7'yi (her kitap bir kategoriye bağlıdır) olduğu gibi çiziyor. Az sonra gerçek veritabanının bu konuda başka bir şey söylediğini göreceğiz. Diyagramın çizildiğini ve parse edildiğini Mermaid 12.3.0 ile doğruladım; söz dizimi hatası olan bir satırda Mermaid Parse error on line 2 ile hangi token'ı beklediğini söylüyor.
Veritabanından Diyagram Çıkarmak ve Sınırları
Elinde zaten çalışan bir veritabanı varsa ER diyagramını ondan da üretebilirsin. Çoğu araç bunu yapar, ama ne gördüğünü ve neyi göremediğini anlamak için aynı işi bir sorguyla yapalım. SQL Temelleri serisinin Bookstore şemasını dm_02 veritabanına yükledim ve SQL Server'ın catalog view'larından Mermaid ilişki satırları üretiyorum: foreign key kolonu nullable ise parent tarafı |o, değilse ||; foreign key kolonları child'ın primary key'inin parçasıysa çizgi --, değilse ..; foreign key kolonlarının tam üstünde bir unique index varsa child tarafı o|, yoksa o{.
SET NOCOUNT ON;
WITH fk AS (
SELECT fk.object_id,
fk.name AS fk_name,
OBJECT_NAME(fk.referenced_object_id) AS parent_table,
OBJECT_NAME(fk.parent_object_id) AS child_table,
fk.parent_object_id,
MAX(CAST(c.is_nullable AS INT)) AS any_nullable
FROM sys.foreign_keys AS fk
JOIN sys.foreign_key_columns AS fkc ON fkc.constraint_object_id = fk.object_id
JOIN sys.columns AS c ON c.object_id = fkc.parent_object_id AND c.column_id = fkc.parent_column_id
GROUP BY fk.object_id, fk.name, fk.referenced_object_id, fk.parent_object_id
),
index_flags AS (
SELECT fk.object_id,
CASE WHEN i.is_unique = 1 AND NOT EXISTS (
SELECT ic.column_id FROM sys.index_columns AS ic
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id AND ic.key_ordinal > 0
EXCEPT
SELECT fkc.parent_column_id FROM sys.foreign_key_columns AS fkc
WHERE fkc.constraint_object_id = fk.object_id)
THEN 1 ELSE 0 END AS fk_is_unique,
CASE WHEN i.is_primary_key = 1 AND EXISTS (
SELECT 1 FROM sys.index_columns AS ic
JOIN sys.foreign_key_columns AS fkc
ON fkc.constraint_object_id = fk.object_id AND fkc.parent_column_id = ic.column_id
WHERE ic.object_id = i.object_id AND ic.index_id = i.index_id)
THEN 1 ELSE 0 END AS fk_in_pk
FROM fk
JOIN sys.indexes AS i ON i.object_id = fk.parent_object_id
)
SELECT CONCAT(' ', fk.parent_table, ' ',
IIF(fk.any_nullable = 1, '|o', '||'),
IIF(MAX(f.fk_in_pk) = 1, '--', '..'),
IIF(MAX(f.fk_is_unique) = 1, 'o|', 'o{'),
' ', fk.child_table, ' : "', fk.fk_name, '"') AS mermaid_line
FROM fk
JOIN index_flags AS f ON f.object_id = fk.object_id
GROUP BY fk.parent_table, fk.child_table, fk.fk_name, fk.any_nullable
ORDER BY fk.parent_table, fk.child_table;
mermaid_line
-----------------------------------------------------------
authors ||--o{ book_authors : "FK_book_authors_authors"
books ||--o{ book_authors : "FK_book_authors_books"
books ||..o{ order_items : "FK_order_items_books"
books ||..o{ reviews : "FK_reviews_books"
books ||--o| stock_items : "FK_stock_items_books"
categories |o..o{ books : "FK_books_categories"
categories |o..o{ categories : "FK_categories_parent"
customers ||..o{ orders : "FK_orders_customers"
customers ||..o{ reviews : "FK_reviews_customers"
orders ||..o{ order_items : "FK_order_items_orders"
orders ||..o{ payments : "FK_payments_orders"
Çıktı geçerli bir Mermaid ve çizildiğinde Bookstore'un iskeletini gösteriyor. stock_items ile books arasındaki 1-1 ilişkiyi bile buldu, çünkü stock_items'ın primary key'i book_id. Ama neyi kaybettiğine dikkat et.
Child tarafının minimumu her zaman o. Veritabanında "her siparişin en az bir kalemi olur" kuralını tutan bir constraint yok, dolayısıyla sorgunun bunu bilmesinin yolu da yok. Taslaktaki ORDER ||--|{ ORDER_ITEM burada orders ||..o{ order_items oldu.
Weak entity kayboldu. Bookstore şemasında order_items'ın kendi surrogate key'i (order_item_id) var, bu yüzden ilişki non-identifying görünüyor. Kavramsal olarak kalem hâlâ siparişsiz var olamaz; bunu söyleyen şey artık key değil, ON DELETE CASCADE ve iş kuralı.
M-N ilişkiler iki ayrı 1-N'ye dönüştü: authors ||--o{ book_authors ve books ||--o{ book_authors. Diyagramı okuyan biri book_authors'ın bir kavram mı yoksa bir bağlantı tablosu mu olduğunu ayırt edemez.
categories |o..o{ books satırı, taslaktaki R7 ile çelişiyor. Gerçek şemada books.category_id nullable ve iki kitabın kategorisi yok. Bu ya requirement'ın eksik yazıldığını ya da şemanın kuralı uygulamadığını gösterir; diyagram bunu söyleyemez, sadece farkı görünür kılar. Nullable foreign key'in "bilinmiyor" mu "yok" mu anlamına geldiği bir sonraki yazının konularından biri.
İlişki adları da gitti. "places", "writes", "classifies" yerine constraint adları var. Constraint adlarını isimli ve tutarlı vermek (SQL Temelleri serisinin "Constraint'ler ve Şema Değişikliği" yazısı) burada bir kez daha işe yarıyor, ama bir fiil cümlesinin yerini tutmuyor.
Yani reverse-engineering, "şema şu an ne diyor?" sorusunun cevabıdır; "model ne olmalı?" sorusunun değil. İkisini yan yana koyup farkları konuşmak, şema review'ının en verimli on dakikası olabilir.
Üretimde Dikkat
Diyagramın kaynağını tekleştir. Ya elle yazılmış model asıl kaynaktır ve şema ondan türetilir, ya da şema asıl kaynaktır ve diyagram yukarıdaki gibi her build'de ondan üretilir. İkisini ayrı ayrı elle güncellemek, birinin mutlaka geride kalması demek. Pratik bir orta yol: conceptual taslağı elle Mermaid'de tut, physical diyagramı CI (Continuous Integration) adımında veritabanından üret ve ikisini aynı klasörde sakla.
Diyagramı bölerek çiz. Elli tabloluk bir şemanın tek diyagramı kimseye bir şey anlatmaz. Sipariş, katalog, müşteri gibi alanlara göre ayrı diyagramlar çiz; alanlar arasındaki ilişkileri ayrı bir genel diyagramda göster. Bu bölünme genellikle DDD (Domain-Driven Design) serisindeki bounded context sınırlarıyla da örtüşür.
Optionality'yi diyagramda gördüğün an constraint'e çevir. Diyagramda || olan bir uç veritabanında nullable bir kolona iniyorsa, model ile şema arasında bir kopukluk vardır ve bu kopukluk bir gün veride görünür.
Notation'da ekip içinde anlaş. Aynı repository'de bir kişinin Chen, diğerinin Crow's Foot, üçüncüsünün UML (Unified Modeling Language) class diyagramıyla model çizmesi, review'da "bu çizgi ne demek?" tartışmasına dönüşür.
Sık Düşülen Tuzaklar
Multivalued attribute'u tek kolona sıkıştırmak. phones = '0532..., 0216...' ya da tags = 'novel,classic' ilk gün pratik görünür; ilk "bu telefonla kayıtlı müşteri var mı?" sorusunda LIKE '%...%' ile tablo taramaya, ilk yazım farkında da tutarsız veriye dönüşür. Bu anti-pattern'in ayrıntısı serinin 9. yazısında.
Derived attribute'u saklayıp güncellemeyi unutmak. orders.total_amount kolonunu eklemek bir performans kararı olabilir; ama kalemler değişince toplamı kimin, hangi transaction'da güncellediği yazılı değilse, o kolon bir gün yalan söyler.
Cardinality'yi yazıp optionality'yi atlamak. "1-N" diye çizilmiş bir ilişki, foreign key'in nullable olup olmayacağı konusunda hiçbir şey söylemez. Her iki ucun minimumunu da çiz.
Relationship attribute'unu yanlış entity'ye koymak. Yazar sırasını authors tablosuna koymak, aynı yazarın her kitapta aynı sırada olduğunu varsaymak demektir. "Bu bilgi kimin?" sorusunun cevabı iki entity'yi birden içeriyorsa, cevap ilişkidir.
Reverse-engineered diyagramı model sanmak. Yukarıda gördüğümüz gibi araç yalnızca şemanın söylediğini çizebilir; şemada olmayan kural diyagramda da yoktur.
Küçük Bir Deneme
★☆☆ Bookstore taslağına PAYMENT entity'sini ekle: bir siparişin birden çok ödemesi olabilir (hediye çeki ve kart), her ödeme tam bir siparişe aittir. Mermaid'de ilişki satırını yaz ve hangi uçta o, hangi uçta | olacağına karar ver.
★★☆ Reverse-engineering sorgusunu, ilişki satırının sonuna foreign key'in ON DELETE davranışını da (sys.foreign_keys.delete_referential_action_desc) yazacak şekilde değiştir. Hangi ilişkiler CASCADE, hangileri NO_ACTION? Bu bilgi weak entity'leri bulmana yardım ediyor mu?
★★★ Ternary bir ilişkiyi Mermaid ile çizmeye çalış: yayınevi, kitap ve ülke arasındaki "basım hakkı". Mermaid'in erDiagram sözdiziminde ternary relationship için bir şekil var mı? Yoksa bunu nasıl modellersin ve bu çizim Chen notation'daki tek eşkenar dörtgenle aynı şeyi söylüyor mu?
ER diyagramında ilişkileri çizmeyi öğrendik. Bir sonraki yazıda bu ilişkileri tablolara indiriyoruz: 1-N'yi foreign key ile, M-N'yi associative entity ile kurmak, 1-1'in gerçek nedenleri, self-reference, ternary ilişkinin neden iki binary'ye bölünemediği ve weak entity'den ON DELETE davranışını türetmek.