Bir veritabanında kalıtım nasıl temsil edilir?


236

Bir SQL Server veritabanında karmaşık bir yapı temsil nasıl düşünüyorum.

Bazı öznitelikleri paylaşan, ancak birçoğu ortak olmayan bir nesne ailesinin ayrıntılarını saklaması gereken bir uygulamayı düşünün. Örneğin, bir ticari sigorta paketi aynı politika kaydında sorumluluk, motor, mal ve tazminat teminatını içerebilir.

Bölümün çeşitli kapak türleri için gerektiği gibi devralındığı bir Bölüm koleksiyonu ile bir Politika oluşturabileceğiniz için bunu C #, vb. De uygulamak önemsizdir. Ancak, ilişkisel veritabanları buna kolayca izin vermiyor gibi görünmektedir.

İki ana seçenek olduğunu görebiliyorum:

  1. Bir ilke tablosu, ardından birçoğu boş olan tüm olası varyasyonlar için gerekli tüm alanları içeren bir Bölümler tablosu oluşturun.

  2. Her kapak türü için bir tane olmak üzere bir Politika tablosu ve çok sayıda Bölüm tablosu oluşturun.

Bu alternatiflerin her ikisi de, özellikle çok sayıda birleştirme veya çok sayıda boş denetim içeren tüm Bölümlerde sorgular yazmak gerektiğinden, tatmin edici görünmemektedir.

Bu senaryo için en iyi uygulama hangisidir?


Yanıtlar:


430

@Bill Karwin , SQL Entity-Attribute-Value antipatternine çözümler sunarken SQL Antipatterns kitabında üç miras modeli açıklar . Bu kısa bir genel bakış:

Tek Tablo Mirası (Hiyerarşi Başına Miras Olarak Tablo):

İlk seçeneğinizde olduğu gibi tek bir tablo kullanmak muhtemelen en basit tasarımdır. Bahsettiğiniz gibi, alt türe özgü birçok özelliğe, NULLbu özelliklerin geçerli olmadığı satırlarda bir değer verilmelidir . Bu modelle, aşağıdaki gibi görünen bir politika tablonuz olur:

+------+---------------------+----------+----------------+------------------+
| id   | date_issued         | type     | vehicle_reg_no | property_address |
+------+---------------------+----------+----------------+------------------+
|    1 | 2010-08-20 12:00:00 | MOTOR    | 01-A-04004     | NULL             |
|    2 | 2010-08-20 13:00:00 | MOTOR    | 02-B-01010     | NULL             |
|    3 | 2010-08-20 14:00:00 | PROPERTY | NULL           | Oxford Street    |
|    4 | 2010-08-20 15:00:00 | MOTOR    | 03-C-02020     | NULL             |
+------+---------------------+----------+----------------+------------------+

\------ COMMON FIELDS -------/          \----- SUBTYPE SPECIFIC FIELDS -----/

Tasarımı basit tutmak bir artıdır, ancak bu yaklaşımla ilgili ana problemler şunlardır:

  • Yeni alt türler ekleme söz konusu olduğunda, bu yeni nesneleri tanımlayan niteliklere uyum sağlamak için tabloyu değiştirmeniz gerekir. Bu, birçok alt türünüz olduğunda veya düzenli olarak alt türler eklemeyi planladığınızda hızlı bir şekilde sorunlu hale gelebilir.

  • Hangi özniteliklerin hangi alt türlere ait olduğunu tanımlayacak hiçbir meta veri olmadığından, veritabanı hangi özniteliklerin uygulanıp hangilerinin uygulanmadığını uygulayamaz.

  • Ayrıca, NOT NULLzorunlu olması gereken bir alt türün niteliklerini uygulayamazsınız . Genel olarak ideal olmayan başvurunuzda bunu ele almanız gerekir.

Beton Masa Mirası:

Kalıtımla başa çıkmanın bir başka yaklaşımı, her bir alt tür için yeni bir tablo oluşturmak ve her tablodaki tüm ortak özellikleri tekrarlamaktır. Örneğin:

--// Table: policies_motor
+------+---------------------+----------------+
| id   | date_issued         | vehicle_reg_no |
+------+---------------------+----------------+
|    1 | 2010-08-20 12:00:00 | 01-A-04004     |
|    2 | 2010-08-20 13:00:00 | 02-B-01010     |
|    3 | 2010-08-20 15:00:00 | 03-C-02020     |
+------+---------------------+----------------+
                          
--// Table: policies_property    
+------+---------------------+------------------+
| id   | date_issued         | property_address |
+------+---------------------+------------------+
|    1 | 2010-08-20 14:00:00 | Oxford Street    |   
+------+---------------------+------------------+

Bu tasarım temel olarak tek tablo yöntemi için belirlenen sorunları çözecektir:

  • Zorunlu özellikler artık uygulanabilir NOT NULL.

  • Yeni bir alt tür eklemek için varolan bir alt kısma sütun eklemek yerine yeni bir tablo eklemek gerekir.

  • Ayrıca, vehicle_reg_nobir mülkiyet ilkesine ilişkin alan gibi belirli bir alt tür için uygun olmayan bir özniteliğin ayarlanması riski yoktur .

  • typeTek tablo yönteminde olduğu gibi özniteliğe gerek yoktur . Tür artık meta veriler tarafından tanımlanmıştır: tablo adı.

Bununla birlikte, bu model birkaç dezavantajla birlikte gelir:

  • Ortak öznitelikler alt türe özgü özniteliklerle karıştırılır ve bunları tanımlamanın kolay bir yolu yoktur. Veritabanı da bilmiyor.

  • Tabloları tanımlarken, her bir alt tür tablosu için ortak özellikleri tekrarlamanız gerekir. Kesinlikle KURU değil .

  • Alt türden bağımsız olarak tüm politikaları aramak zorlaşır ve bir grup UNIONs gerektirir .

Türüne bakılmaksızın tüm politikaları şu şekilde sorgulamanız gerekir:

SELECT     date_issued, other_common_fields, 'MOTOR' AS type
FROM       policies_motor
UNION ALL
SELECT     date_issued, other_common_fields, 'PROPERTY' AS type
FROM       policies_property;

Yeni alt türler eklemenin, yukarıdaki sorgunun UNION ALLher alt tür için bir ek ile nasıl değiştirilmesini gerektirdiğini unutmayın . Bu işlem unutulursa, uygulamanızda hatalara neden olabilir.

Sınıf Tablosu Devralma (diğer adıyla Tür Devralma Tablosu):

@David'in diğer cevapta bahsettiği çözüm budur . Temel sınıfınız için tüm ortak öznitelikleri içeren tek bir tablo oluşturursunuz. Ardından, her bir alt tür için, birincil anahtarı da temel tabloya yabancı anahtar işlevi gören belirli tablolar oluşturacaksınız . Misal:

CREATE TABLE policies (
   policy_id          int,
   date_issued        datetime,

   -- // other common attributes ...
);

CREATE TABLE policy_motor (
    policy_id         int,
    vehicle_reg_no    varchar(20),

   -- // other attributes specific to motor insurance ...

   FOREIGN KEY (policy_id) REFERENCES policies (policy_id)
);

CREATE TABLE policy_property (
    policy_id         int,
    property_address  varchar(20),

   -- // other attributes specific to property insurance ...

   FOREIGN KEY (policy_id) REFERENCES policies (policy_id)
);

Bu çözüm diğer iki tasarımda tanımlanan sorunları çözer:

  • Zorunlu özellikler uygulanabilir NOT NULL.

  • Yeni bir alt tür eklemek için varolan bir alt kısma sütun eklemek yerine yeni bir tablo eklemek gerekir.

  • Belirli bir alt tür için uygun olmayan bir öznitelik ayarlanma riski yoktur.

  • Özelliğe gerek yok type.

  • Artık ortak özellikler artık alt tip spesifik özelliklerle karıştırılmamaktadır.

  • Nihayet KURU kalabiliriz. Tabloları oluştururken her alt tip tablo için ortak nitelikleri tekrarlamaya gerek yoktur.

  • İlkeler için otomatik olarak artırmayı yönetmek iddaha kolay hale gelir, çünkü bu, bağımsız olarak oluşturulan her alt tür tablo yerine temel tablo tarafından işlenebilir.

  • Alt türden bağımsız olarak tüm politikaları aramak artık çok kolay: Gerek yok UNION- sadece a SELECT * FROM policies.

Sınıf tablosu yaklaşımını çoğu durumda en uygun olarak görüyorum.


Bu üç modelin adları Martin Fowler'in Kurumsal Uygulama Mimarisi Desenleri kitabından alınmıştır .


97
Ben de bu tasarımı kullanıyorum ama dezavantajlardan bahsetmiyorsunuz. Özellikle: 1) tipe ihtiyacınız olmadığını söylüyorsunuz; true, ancak eşleşme bulmak için tüm alt tür tablolarına bakmazsanız satırın gerçek türünü tanımlayamazsınız. 2) Ana tabloyu ve alt tür tablolarını senkronize tutmak zordur (örneğin alt tablodaki satırı kaldırabilir, ana tablodan değil). 3) Her ana satır için birden fazla alt tipiniz olabilir. 1 civarında çalışmak için tetikleyicileri kullanıyorum, ancak 2 ve 3 çok zor problemler. Kompozisyonu modelliyorsanız aslında 3 bir problem değildir, ancak katı bir miras içindir.

19
@ Tibo'nun yorumu için +1, bu büyük bir sorun. Sınıf Tablosu devralma aslında normalleştirilmemiş bir şema verir. Beton Masa mirası olarak değil ve Beton Masa Mirası DRY'yi engellediği iddiasına katılmıyorum. SQL, DRY'yi engeller, çünkü meta programlama olanakları yoktur. Çözüm, SQL'i doğrudan yazmak yerine, ağır kaldırmayı yapmak için bir Veritabanı Araç Seti kullanmak (veya kendi yazmanızı) kullanmaktır (unutmayın, aslında sadece bir DB arayüz dili). Sonuçta, kurumsal uygulamanızı montajda da yazmazsınız.
Jo So

18
@Tibo, nokta 3 hakkında, burada açıklanan yaklaşımı kullanabilirsiniz: sqlteam.com/article/… , Modelleme Bire Bir Kısıtlamalar Kısmını Kontrol Edin .
Andrew

4
@DanielVassallo Öncelikle çarpıcı cevap için teşekkürler, bir kişinin bir politikası varsa 1 şüphe onun politika_motor veya politika_property olup olmadığını bilmek nasıl? Bir yolu, tüm alt tablolarda policyId aramaktır ama sanırım bu kötü bir yol değil, doğru yaklaşım ne olmalı?
ThomasBecker

11
Üçüncü seçeneğinizi gerçekten seviyorum. Ancak, SELECT'in nasıl çalışacağı konusunda kafam karıştı. Politikalardan * SEÇEBİLİRSİNİZ, politika kimliklerini geri alırsınız, ancak yine de politikanın hangi alt tür tablosuna ait olduğunu bilemezsiniz. Politika ayrıntılarının tümünü almak için yine de tüm alt türlerle KATILMAYA gerek kalmayacak mı?
Adam

14

Üçüncü seçenek, bir "İlke" tablosu, sonra da bölüm türleri arasında ortak olan tüm alanları depolayan bir "SectionsMain" tablosu oluşturmaktır. Ardından, yalnızca ortak olmayan alanları içeren her bölüm türü için başka tablolar oluşturun.

Hangisinin en iyi olduğuna karar vermek çoğunlukla sahip olduğunuz alanlara ve SQL'inizi nasıl yazmak istediğinize bağlıdır. Hepsi işe yarar. Sadece birkaç alanınız varsa, muhtemelen # 1 ile giderdim. Alanların "sürü" ile # 2 veya # 3 doğru eğilir.


+1: 3. seçenek kalıtım modeline en yakın ve en normalleştirilmiş IMO
RedFilter

# 3 seçeneğiniz gerçekten # 2 seçeneği ile kastettiğim şey. Birçok alan vardır ve bazı Bölümlerde de çocuk varlıkları olacaktır.
Steve Jones

9

Sağlanan bilgilerle, veritabanını aşağıdakilere sahip olacak şekilde modelleyebilirim:

POLİTİKALAR

  • POLICY_ID (birincil anahtar)

YÜKÜMLÜLÜKLER

  • LIABILITY_ID (birincil anahtar)
  • POLICY_ID (yabancı anahtar)

ÖZELLİKLERİ

  • PROPERTY_ID (birincil anahtar)
  • POLICY_ID (yabancı anahtar)

... ve bunun gibi, çünkü politikanın her bir bölümüyle ilişkili farklı özellikler olmasını beklerdim. Aksi takdirde, tek olabilir SECTIONSmasa ve ek olarak policy_id, bir olurdu section_type_code...

Her iki durumda da, politika başına isteğe bağlı bölümleri desteklemenize olanak tanır ...

Bu yaklaşımla ilgili tatmin edici bulmadıklarınızı anlamıyorum - bu, referans bütünlüğünü korurken ve verileri çoğaltmadan verileri nasıl depoladığınızdır. Terim "normalleştirilmiş" ...

SQL SET tabanlı olduğundan, yordamsal / OO programlama kavramlarına yabancıdır ve kodun bir alemden diğerine geçişini gerektirir. ORM'ler genellikle dikkate alınır, ancak yüksek hacimli, karmaşık sistemlerde iyi çalışmazlar.


Evet, normalleştirme olayını alıyorum ;-) Böyle karmaşık bir yapı için, bazı bölümlerin basit ve bazılarının kendi karmaşık alt yapılarına sahip olması, bir ORM'nin iyi olmasına rağmen, işe yarama olasılığı düşük görünüyor.
Steve Jones

6

Ayrıca Daniel Vassallo çözümünde, SQL Server 2016+ kullanıyorsanız, bazı durumlarda performansları önemli ölçüde kaybetmeden kullandığım başka bir çözüm var.

Yalnızca ortak alana sahip bir tablo oluşturabilir ve JSON ile tek bir sütun ekleyebilirsiniz dizesine tüm alt türe özgü alanları içeren .

Bu tasarımı miras yönetimi için test ettim ve göreli uygulamada kullanabileceğim esneklik için çok mutluyum.


1
Bu ilginç bir fikir. Henüz SQL Server'da JSON kullanmadım, ancak başka yerlerde çok kullanıyorum. Söylediğin için teşekkürler.
Steve Jones

5

Bunu yapmanın başka bir yolu da INHERITSbileşeni kullanmaktır . Örneğin:

CREATE TABLE person (
    id int ,
    name varchar(20),
    CONSTRAINT pessoa_pkey PRIMARY KEY (id)
);

CREATE TABLE natural_person (
    social_security_number varchar(11),
    CONSTRAINT pessoaf_pkey PRIMARY KEY (id)
) INHERITS (person);


CREATE TABLE juridical_person (
    tin_number varchar(14),
    CONSTRAINT pessoaj_pkey PRIMARY KEY (id)
) INHERITS (person);

Böylece tablolar arasında kalıtım tanımlamak mümkündür.


PostgreSQLINHERITS dışında diğer DB'ler destekliyor mu? Örneğin MySQL ?
giannis christofakis

1
@giannischristofakis: MySQL sadece ilişkisel bir veritabanı, Postgres ise nesnel-ilişkisel bir veritabanıdır. Yani, hiçbir MySQL bunu desteklemez. Aslında, Postgres'in bu tür kalıtımı destekleyen tek güncel DBMS olduğunu düşünüyorum.
a_horse_with_no_name

2
@ marco-paulo-ollivier, OP'nin sorusu SQL Server hakkında, bu yüzden neden sadece Postgres ile çalışan bir çözüm sağladığınızı anlamıyorum. Açıkçası, sorunu ele almamak.
mapto

@mapto bu soru "biri bir veritabanında OO tarzı miras nasıl yapar" bir şey haline geldi dupe hedef; Başlangıçta sql sunucusu hakkında olduğunu muhtemelen şimdi alakasız
Caius Jard

0

Tüm bölümleriyle birlikte tüm politikaları verimli bir şekilde almak için yöntem # 1'e (birleşik bir Bölüm tablosu) doğru eğildim (ki sisteminizin çok şey yapacağını varsayıyorum).

Ayrıca, hangi SQL Server sürümünü kullandığınızı bilmiyorum, ancak 2008+ Seyrek Sütunlar , bir sütundaki değerlerin çoğunun NULL olacağı durumlarda performansı optimize etmeye yardımcı olur.

Sonuçta, politika bölümlerinin ne kadar "benzer" olduğuna karar vermelisiniz. Büyük ölçüde farklılık göstermedikçe, daha normalleştirilmiş bir çözümün değerinden daha fazla sorun olabileceğini düşünüyorum ... ama sadece bu çağrıyı yapabilirsiniz. :)


Politikanın tamamını bir seferde sunmak için çok fazla bilgi olacaktır, bu nedenle tüm kaydı almak asla gerekli olmayacaktır. 2008'in seyrini diğer projelerde kullanmamıza rağmen 2005 olduğunu düşünüyorum.
Steve Jones

"Birleştirilmiş bölüm tablosu" terimi nereden geliyor? Google bunun için neredeyse hiç sonuç göstermiyor ve burada zaten kafa karıştırıcı terimler var.
Stephan-v

-1

Alternatif olarak, zengin veri yapılarını ve iç içe yerleştirmeyi yerel olarak destekleyen bir belge veritabanları (MongoDB gibi) kullanmayı düşünün.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.