Yeni bir özellik "müşteri de olsun, tedarikçi de aynı tabloda" cümlesiyle başlar. Gerekçe hazırdır: hepsinin adı, e-postası, telefonu var; aynı tablo bize tasarruf sağlar. Aylar sonra tabloda beş farklı varlık yaşar, kolonların yarısı bazı türlerde anlamsızdır ve hiçbir yeni geliştirici hangi kolonun hangi türde zorunlu olduğunu söyleyemez. Bu, en yaygın veri modelleme antiplanıdır ve ciddi olan tarafı henüz hata vermemiş olmasıdır.

Antiplanlar genellikle kötü kararların ürünü değildir; iyi niyetli tasarrufun zamanla bozulmuş hâlidir. Bu yüzden tanımak, yasaklamaktan daha etkilidir: kalıbı gören ekip, yeni özellikte aynı yola sapmaz.

Beş anlam, tek tablo

Klasik görünüm şöyledir:

CREATE TABLE kayitlar (
  id         bigserial PRIMARY KEY,
  tur        text,        -- musteri, tedarikci, sirket, sube...
  adi        text,
  eposta     text,
  vergi_no   text,        -- musteri boş, tedarikçi dolu, şirkette kimlik no
  ebeveyn_id bigint,      -- tedarikçide süpervizör, şubede merkez, müşteride anlamsız
  silindi_mi boolean
);

Her kolonun anlamı tur kolonuna bağlıdır: musteri turunda ebeveyn_id hep boştur, sirket turunda vergi_no aslında kimlik numarasıdır. Kısıt koymak neredeyse imkânsızdır; CHECK yazmaya kalktığınızda her tür için ayrı dal içeren bir koşul ormanı çıkar. NOT NULL bile getiremezsiniz, çünkü aynı kolonun zorunluluğu tür bazında değişir.

Bedeli sorgularda ve akılda ödenir

Raporlar ilk çatlakları gösterir: ay bazında müşteri sayısı isterken bile tur filtresini unutmamak gerekir; unutursanız sayı "yanlış" değil, sessizce yanlış olur. Yeni bir varlık eklemek kolon ekletir; altıncı tür geldiğinde tablo on sekiz kolonluk bir Null labirentine döner. Uygulama kodunda da aynı zehri içersiniz: her if bloğu tur alanına bakar, her yenisi bir diğerine eklenir. Modelleme hatası burada bitmez, kodun her katmanına sızar; ve teknik borç olarak birikir.

Sağlıklı modele doğru

Çözüm çoğu zaman cesaret değil dürüstlük ister: farklı anlam taşıyan veriler, farklı tablolara aitdir.

CREATE TABLE musteriler (
  id       bigserial PRIMARY KEY,
  adi      text NOT NULL,
  eposta   text NOT NULL UNIQUE,
  vergi_no text
);

CREATE TABLE subeler (
  id         bigserial PRIMARY KEY,
  sirket_id  bigint NOT NULL REFERENCES sirketler (id),
  adi        text NOT NULL,
  sehir      text NOT NULL
);

Ortak davranış gerçekten varsa, ortak arayüzü uygulama katmanında kurun; veritabanı katmanında değil. Ortak rapor ihtiyacı için her tabloyu ayrı okuyup birleştirmek, tek tabloda tur filtrelemekten daha ucuzdur; çünkü ilkinin maliyeti ölçülür, ikisininki sessizce büyür.

Özetleyelim: aynı tabloda birden fazla anlam, anlaşılmaz bir şema, yazılamayan kısıtlar ve büyüyen if ormanı demektir. Şemanızı yazarken sorun: bu kolonun zorunluluğu türle değişiyor mu? Cevap evetse tabloyu bölün.

Bölme işlemini korkutmayın: mevcut tabloyu tek hamlede yıkmak gerekmez. Önce yeni tabloları yazın, sonra eski tabloyu çift yazar hâle getirin, sonra veriyi parça parça taşıyın, en son eski tabloyu arşivleyip kaldırın. Her adım küçük olduğundan geri dönüş de küçüktür; aylara yayılan bir temizlik, tek geceye sıkıştırılmış bir felaketten iyidir.

Veritabanı, birbirine benzemeyen şeyleri benzetmek için değil, benzemeyeni ayırt etmek için tasarlanmıştır. Kolon sayısına değil, anlam sayısına bakın: bir tabloda kaç farklı varlık yaşıyorsa, model o kadar borçludur.