İçeriğe geç
academia.sh

Ders 11 / 14

Şema Tasarım Kalıpları

İlişki kardinalitelerinin şemaya çevrilmesi, hiyerarşik verinin saklanması, alt tür ve zaman boyutu kalıpları ile anahtar-değer tablosu başta olmak üzere yaygın karşı kalıplar.

İçindekiler

Önceki dersler tek tek kuralları verdi: bağımlılıkları bul, bağıntıyı böl, tekrarı ölçerek geri koy. Uygulamada aynı modelleme durumları tekrar tekrar karşımıza çıkar ve her birinin yerleşik bir karşılığı vardır. Bu dersin sorusu şudur: hangi durum hangi şema yapısına çevrilir, ve hangi yaygın yapılar aslında sorunun kendisidir?

İlişki Kardinaliteleri

İki bağıntı arasındaki ilişkinin şemaya çevrilmesi kardinaliteye bağlıdır.

Bire çok ilişki, yabancı anahtarın “çok” tarafına konmasıyla kurulur. Bir üyenin birden çok ödünç kaydı vardır; uye_no sütunu ödünç bağıntısında durur. Ters yön — üye bağıntısında ödünç numarası tutmak — tek hücreye liste sıkıştırmak demektir ve birinci normal formu bozar.

Bire bir ilişki nadirdir ve genellikle iki gerekçeden birine dayanır: satırın seyrek doldurulan bölümünü ayırmak, ya da erişimi farklı yetkiye bağlı sütunları ayrı tutmak. Yabancı anahtar iki taraftan birine konur ve tekillik kısıtıyla desteklenir.

Çoka çok ilişki doğrudan ifade edilemez; araya bir bağlantı bağıntısı (junction table) konur. Anahtarı, iki tarafın anahtarlarından oluşan bileşik anahtardır:

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 yazar (yazar_no INTEGER PRIMARY KEY, ad TEXT NOT NULL);
CREATE TABLE kitap_yazar (
  isbn     TEXT    NOT NULL REFERENCES kitap (isbn),
  yazar_no INTEGER NOT NULL REFERENCES yazar (yazar_no),
  sira     INTEGER NOT NULL,
  PRIMARY KEY (isbn, yazar_no)
);
INSERT INTO kitap VALUES ('975-01', 'Kayip Zaman'), ('975-02', 'Deniz Fenerleri');
INSERT INTO yazar VALUES (7, 'Elif Toprak'), (8, 'Kemal Aras');
INSERT INTO kitap_yazar VALUES ('975-01', 7, 1), ('975-01', 8, 2), ('975-02', 8, 1);
SELECT k.baslik, COUNT(*) AS yazar_sayisi FROM kitap k
JOIN kitap_yazar ky ON ky.isbn = k.isbn GROUP BY k.isbn ORDER BY k.isbn;
SQL
┌─────────────────┬──────────────┐
│     baslik      │ yazar_sayisi │
├─────────────────┼──────────────┤
│ Kayip Zaman     │ 2            │
│ Deniz Fenerleri │ 1            │
└─────────────────┴──────────────┘

Bağlantı bağıntısı çoğu zaman yalın bir eşleştirmeden fazlasını taşır. Buradaki sira sütunu, yazarların kapaktaki sırasını tutar — ilişkinin kendisine ait bir özniteliktir ve başka hiçbir bağıntıya sığmaz. Ödünç işlemi de aslında böyledir: üye ile nüsha arasındaki ilişkiye ait alış ve iade tarihlerini taşır.

Hiyerarşik Veri

Kitap konuları ağaç oluşturur: edebiyat altında roman, roman altında tarihsel roman. En yalın karşılık, bağıntının kendi anahtarına başvurmasıdır — buna komşuluk listesi (adjacency list) denir. Ağaç üzerinde dolaşmak özyinelemeli sorgu gerektirir:

sqlite3 :memory: <<'SQL'
.headers on
.mode box
PRAGMA foreign_keys = ON;
CREATE TABLE konu (
  konu_no  INTEGER PRIMARY KEY,
  ad       TEXT    NOT NULL,
  ust_konu INTEGER REFERENCES konu (konu_no)
);
INSERT INTO konu VALUES (1, 'Edebiyat', NULL), (2, 'Roman', 1), (3, 'Siir', 1),
                        (4, 'Tarihsel Roman', 2), (5, 'Bilim', NULL);

WITH RECURSIVE dal(konu_no, ad, derinlik, yol) AS (
  SELECT konu_no, ad, 0, ad FROM konu WHERE ust_konu IS NULL
  UNION ALL
  SELECT k.konu_no, k.ad, d.derinlik + 1, d.yol || ' / ' || k.ad
  FROM konu k JOIN dal d ON k.ust_konu = d.konu_no
)
SELECT derinlik, yol FROM dal ORDER BY yol;
SQL
┌──────────┬───────────────────────────────────┐
│ derinlik │                yol                │
├──────────┼───────────────────────────────────┤
│ 0        │ Bilim                             │
│ 0        │ Edebiyat                          │
│ 1        │ Edebiyat / Roman                  │
│ 2        │ Edebiyat / Roman / Tarihsel Roman │
│ 1        │ Edebiyat / Siir                   │
└──────────┴───────────────────────────────────┘

Sorgu, kökten başlayıp her adımda bir alt seviyeyi ekleyerek ağacı açar. Veri Yapıları kursundaki enine arama ile aynı fikirdir; fark, kuyruğun sorgu motorunda tutulmasıdır.

Komşuluk listesinin yazması ucuz, okuması pahalıdır: bir düğümün bütün alt ağacını almak derinlik kadar adım gerektirir. Üç yaygın karşılık vardır. Yol numaralandırma (path enumeration) her satırda kökten gelen yolu bir dizgide tutar; alt ağaç sorgusu önek eşlemesine döner, ama taşıma işlemleri bütün alt ağacı günceller. İç içe küme (nested set) her düğüme sol ve sağ sayı verir; okuma çok hızlıdır, ekleme neredeyse bütün tabloyu kaydırır. Kapanış tablosu (closure table) her ata–torun çiftini ayrı satırda tutar; okuma ve yazma dengelidir, karşılığında satır sayısı artar. Seçim, okuma ile yazma oranına göre yapılır.

Alt Tür ve Zaman

Alt tür (subtype) durumu, ortak bir üst kavramın farklı öznitelikler taşıyan türlerinden doğar: kütüphanedeki materyaller kitap, süreli yayın ve ses kaydı olabilir; ortak alanları vardır, ayrık alanları da. Üç karşılık kullanılır. Tek bağıntıda tutmak — bütün türlerin sütunlarını yan yana koymak — çok sayıda boş değer üretir. Tür başına ayrı bağıntı, ortak sorguları zorlaştırır. Ortak öznitelikleri bir üst bağıntıda, ayrık öznitelikleri tür başına bir alt bağıntıda tutmak dengeli karşılıktır; alt bağıntının birincil anahtarı, aynı zamanda üst bağıntıya yabancı anahtardır.

Zaman boyutu iki ayrı gereksinimi gizler. Birincisi, bir olgunun geçerli olduğu aralıktır: üyelik ücretinin hangi tarihler arasında hangi tutarda olduğu. Karşılığı, satıra başlangıç ve bitiş tarihi eklemek ve aralıkların örtüşmemesini kısıtla denetlemektir. İkincisi, bir satırın geçmiş hâllerini saklamaktır: her değişiklikte yeni bir sürüm satırı yazılır, güncel satır bir bayrakla ya da boş bitiş tarihiyle işaretlenir. İkisini karıştırmak, ne geçmişi doğru veren ne de güncel durumu ucuza veren bir şema üretir.

Karşı Kalıplar

Bazı yapılar sorunu çözer görünürken kısıtları ortadan kaldırır. En yaygını, sütunları satır olarak tutan anahtar-değer tablosudur:

sqlite3 :memory: <<'SQL'
.headers on
.mode box
.nullvalue (bos)
CREATE TABLE kitap_ozellik (
  isbn    TEXT NOT NULL,
  ozellik TEXT NOT NULL,
  deger   TEXT NOT NULL,
  PRIMARY KEY (isbn, ozellik)
);
INSERT INTO kitap_ozellik VALUES
  ('975-01', 'baslik', 'Kayip Zaman'), ('975-01', 'yayin_yili', '2019'),
  ('975-02', 'baslik', 'Deniz Fenerleri'), ('975-02', 'yayin_yili', 'iki bin yirmi'),
  ('975-03', 'baslik', 'Sessiz Bahce');

SELECT b.deger AS baslik, y.deger AS yayin_yili
FROM kitap_ozellik b
LEFT JOIN kitap_ozellik y ON y.isbn = b.isbn AND y.ozellik = 'yayin_yili'
WHERE b.ozellik = 'baslik'
ORDER BY b.isbn;
SQL
┌─────────────────┬───────────────┐
│     baslik      │  yayin_yili   │
├─────────────────┼───────────────┤
│ Kayip Zaman     │ 2019          │
│ Deniz Fenerleri │ iki bin yirmi │
│ Sessiz Bahce    │ (bos)         │
└─────────────────┴───────────────┘

Üç kayıp aynı çıktıda görünür. Yayın yılına tip ve alan kısıtı yazılamaz, çünkü deger sütunu bütün öznitelikler için ortaktır — iki bin yirmi girmiştir. Zorunluluk uygulanamaz; üçüncü kitabın yılı hiç yoktur ve bunu engelleyecek bir NOT NULL yeri yoktur. Ve iki öznitelik okumak için tabloyu kendisiyle birleştirmek gerekir; öznitelik sayısı arttıkça birleştirme sayısı da artar.

Bu yapının meşru kullanımı dardır: gerçekten önceden bilinmeyen, kullanıcı tarafından tanımlanan ve üzerinde kısıt aranmayan öznitelikler. Şema değişikliğinden kaçınmak için seçildiğinde, veritabanının verdiği bütün güvenceler geri verilmiş olur.

Üç karşı kalıp daha sık görülür. Çok amaçlı yabancı anahtar, tek bir sütunla birden çok bağıntıya başvurmayı dener — hedef bağıntının adı ayrı bir sütunda tutulur ve başvuru bütünlüğü uygulanamaz hâle gelir. Sütun adında numaratelefon_1, telefon_2 — birinci normal formu yatay yazmaktır. Sihirli değer, boş değer yerine -1 ya da '1900-01-01' gibi bir yer tutucu koymaktır; her sorgunun o değeri elemesini gerektirir ve eleme unutulduğunda toplamlara karışır.

Özet

  • Bire çok ilişki yabancı anahtarla, çoka çok ilişki bileşik anahtarlı bir bağlantı bağıntısıyla kurulur; bağlantı bağıntısı ilişkinin kendi özniteliklerini taşır.
  • Hiyerarşi için komşuluk listesi yazmada ucuz okumada pahalıdır; yol numaralandırma, iç içe küme ve kapanış tablosu bu dengeyi farklı yönlere kaydırır.
  • Alt türler ortak öznitelikleri üst bağıntıda, ayrık öznitelikleri tür başına alt bağıntıda tutularak modellenir.
  • Geçerlilik aralığı ile sürüm geçmişi ayrı gereksinimlerdir; tek yapıyla ikisini birden karşılamaya çalışmak her ikisini de bozar.
  • Anahtar-değer tablosu tip, alan ve zorunluluk kısıtlarını birden yitirir ve öznitelik başına bir birleştirme ekler; meşru kullanımı önceden bilinmeyen özniteliklerle sınırlıdır.

Sonraki Adım

Normalizasyon konusu burada tamamlanıyor: şemanın nasıl kurulacağı, nasıl bölüneceği ve hangi durumlarda bilinçli olarak birleştirileceği belirlendi. Kalan soru, bu şemanın hangi tür iş yükü altında çalışacağıdır. Tek tek ödünç işlemlerini kaydeden bir sistemle yıllık ödünç istatistiği çıkaran bir sistem aynı veriyi ister ama aynı erişim biçimini istemez. Sıradaki konu bu ayrımla başlar.

İ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