Bagaimana Anda bisa mewakili warisan dalam database?

236

Saya sedang berpikir tentang bagaimana untuk mewakili struktur kompleks dalam database SQL Server.

Pertimbangkan aplikasi yang perlu menyimpan detail kumpulan objek, yang memiliki beberapa atribut, tetapi memiliki banyak lainnya yang tidak umum. Misalnya, paket asuransi komersial dapat mencakup pertanggungjawaban, motor, properti, dan perlindungan ganti rugi dalam catatan kebijakan yang sama.

Ini sepele untuk menerapkan ini dalam C #, dll, karena Anda dapat membuat Kebijakan dengan koleksi Bagian, di mana Bagian diwarisi sebagaimana diperlukan untuk berbagai jenis sampul. Namun, database relasional sepertinya tidak memungkinkan ini dengan mudah.

Saya dapat melihat bahwa ada dua pilihan utama:

  1. Buat tabel Kebijakan, lalu tabel Bagian, dengan semua bidang yang diperlukan, untuk semua kemungkinan variasi, yang sebagian besar akan menjadi nol.

  2. Buat tabel Kebijakan dan banyak tabel Bagian, satu untuk setiap jenis sampul.

Kedua alternatif ini tampaknya tidak memuaskan, terutama karena itu perlu untuk menulis pertanyaan di semua Bagian, yang akan melibatkan banyak gabungan, atau banyak cek kosong.

Apa praktik terbaik untuk skenario ini?

Steve Jones
sumber

Jawaban:

430

@Bill Karwin menjelaskan tiga model pewarisan dalam bukunya SQL Antipatterns , ketika mengusulkan solusi untuk antipattern SQL Entity-Attribute-Value . Ini adalah gambaran singkat:

Single Table Inheritance (alias Table Per Hierarchy Inheritance):

Menggunakan tabel tunggal seperti pada opsi pertama Anda mungkin desain yang paling sederhana. Seperti yang Anda sebutkan, banyak atribut yang subtipe-spesifik harus diberi NULLnilai pada baris di mana atribut ini tidak berlaku. Dengan model ini, Anda akan memiliki satu tabel kebijakan, yang akan terlihat seperti ini:

+------+---------------------+----------+----------------+------------------+
| 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 -----/

Menjaga desain tetap sederhana adalah nilai tambah, tetapi masalah utama dengan pendekatan ini adalah sebagai berikut:

  • Ketika menambahkan subtipe baru, Anda harus mengubah tabel untuk mengakomodasi atribut yang menggambarkan objek baru ini. Ini dapat dengan cepat menjadi masalah ketika Anda memiliki banyak subtipe, atau jika Anda berencana untuk menambahkan subtipe secara teratur.

  • Basis data tidak akan dapat menegakkan atribut mana yang berlaku dan mana yang tidak, karena tidak ada metadata untuk menentukan atribut mana yang termasuk subtipe mana.

  • Anda juga tidak dapat menerapkan NOT NULLatribut subtipe yang wajib. Anda harus menangani ini di aplikasi Anda, yang secara umum tidak ideal.

Warisan Meja Beton:

Pendekatan lain untuk mengatasi pewarisan adalah membuat tabel baru untuk setiap subtipe, mengulangi semua atribut umum di setiap tabel. Sebagai contoh:

--// 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    |   
+------+---------------------+------------------+

Desain ini pada dasarnya akan memecahkan masalah yang diidentifikasi untuk metode tabel tunggal:

  • Atribut wajib sekarang dapat ditegakkan dengan NOT NULL.

  • Menambahkan subtipe baru membutuhkan menambahkan tabel baru alih-alih menambahkan kolom ke yang sudah ada.

  • Juga tidak ada risiko bahwa atribut yang tidak pantas ditetapkan untuk subtipe tertentu, seperti vehicle_reg_nobidang untuk kebijakan properti.

  • Tidak perlu typeatribut seperti dalam metode tabel tunggal. Jenisnya sekarang ditentukan oleh metadata: nama tabel.

Namun model ini juga dilengkapi dengan beberapa kelemahan:

  • Atribut umum dicampur dengan atribut subtipe spesifik, dan tidak ada cara mudah untuk mengidentifikasi mereka. Basis data juga tidak akan tahu.

  • Saat mendefinisikan tabel, Anda harus mengulang atribut umum untuk setiap tabel subtipe. Itu jelas bukan KERING .

  • Mencari semua kebijakan terlepas dari subtipe menjadi sulit, dan akan membutuhkan banyak UNION.

Ini adalah bagaimana Anda harus menanyakan semua kebijakan terlepas dari jenisnya:

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;

Perhatikan bagaimana menambahkan subtipe baru akan membutuhkan kueri di atas untuk dimodifikasi dengan tambahan UNION ALLuntuk setiap subtipe. Ini dengan mudah dapat menyebabkan bug di aplikasi Anda jika operasi ini dilupakan.

Class Table Inheritance (alias Table Per Type Inheritance):

Ini adalah solusi yang @David sebutkan di jawaban lain . Anda membuat tabel tunggal untuk kelas dasar Anda, yang mencakup semua atribut umum. Kemudian Anda akan membuat tabel spesifik untuk setiap subtipe, yang kunci utamanya juga berfungsi sebagai kunci asing ke tabel dasar. Contoh:

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)
);

Solusi ini memecahkan masalah yang diidentifikasi dalam dua desain lainnya:

  • Atribut wajib dapat ditegakkan dengan NOT NULL.

  • Menambahkan subtipe baru membutuhkan menambahkan tabel baru alih-alih menambahkan kolom ke yang sudah ada.

  • Tidak ada risiko bahwa atribut yang tidak pantas ditetapkan untuk subtipe tertentu.

  • Tidak perlu typeatribut.

  • Sekarang atribut umum tidak dicampur dengan atribut spesifik subtipe lagi.

  • Kita bisa tetap KERING, akhirnya. Tidak perlu mengulangi atribut umum untuk setiap tabel subtipe saat membuat tabel.

  • Mengelola peningkatan otomatis iduntuk kebijakan menjadi lebih mudah, karena ini dapat ditangani oleh tabel dasar, daripada setiap tabel subtipe yang menghasilkannya secara independen.

  • Mencari semua kebijakan terlepas dari subtipe sekarang menjadi sangat mudah: Tidak UNIONperlu - hanya a SELECT * FROM policies.

Saya menganggap pendekatan tabel kelas sebagai yang paling cocok dalam kebanyakan situasi.


Nama ketiga model ini berasal dari buku Martin Fowler, Patterns of Enterprise Application Architecture .

Daniel Vassallo
sumber
97
Saya menggunakan desain ini juga, tetapi Anda tidak menyebutkan kekurangannya. Secara khusus: 1) Anda mengatakan Anda tidak perlu mengetik; true tetapi Anda tidak dapat mengidentifikasi tipe aktual baris kecuali jika Anda melihat semua tabel subtipe untuk menemukan kecocokan. 2) Sulit untuk menjaga tabel master dan tabel subtipe dalam sinkronisasi (misalnya, dapat menghapus baris dalam tabel subtipe dan bukan di tabel master). 3) Anda dapat memiliki lebih dari satu subtipe untuk setiap baris master. Saya menggunakan pemicu untuk mengatasi 1, tetapi 2 dan 3 adalah masalah yang sangat sulit. Sebenarnya 3 bukan masalah jika Anda memodelkan komposisi, tetapi untuk warisan yang ketat.
19
+1 untuk komentar @ Tibo, itu masalah serius. Warisan Tabel Kelas sebenarnya menghasilkan skema yang tidak dinormalkan. Sedangkan pewarisan Meja Beton tidak, dan saya tidak setuju dengan argumen bahwa Warisan Tabel Beton menghalangi KERING. SQL menghalangi KERING, karena tidak memiliki fasilitas pemrograman. Solusinya adalah dengan menggunakan Database Toolkit (atau menulis sendiri) untuk melakukan angkat berat, daripada menulis SQL secara langsung (ingat, itu sebenarnya hanya bahasa antarmuka DB). Lagi pula, Anda juga tidak menulis aplikasi perusahaan Anda dalam pertemuan.
Jo So
18
@Tibo, tentang poin 3, Anda dapat menggunakan pendekatan yang dijelaskan di sini: sqlteam.com/article/… , Periksa bagian Modeling One-to-Either Constraints .
Andrew
4
@DanielVassallo Pertama terima kasih atas jawaban yang memukau, saya ragu apakah seseorang memiliki kebijakan. Bagaimana cara mengetahui apakah policy_motor atau policy_property? Salah satu caranya adalah dengan mencari policyId di semua sub Tabel tapi saya rasa ini adalah cara yang buruk, bukan, Apa pendekatan yang benar?
ThomasBecker
11
Saya sangat suka opsi ketiga Anda. Namun, saya bingung bagaimana SELECT akan bekerja. Jika Anda PILIH * DARI kebijakan, Anda akan mendapatkan kembali id ​​kebijakan tetapi Anda masih tidak tahu tabel subtipe mana yang dimiliki kebijakan. Tidakkah Anda masih harus BERGABUNG dengan semua subtipe untuk mendapatkan semua detail kebijakan?
Adam
14

Opsi ke-3 adalah membuat tabel "Kebijakan", lalu tabel "SectionsMain" yang menyimpan semua bidang yang sama di semua jenis bagian. Kemudian buat tabel lain untuk setiap jenis bagian yang hanya berisi bidang yang tidak sama.

Memutuskan yang terbaik tergantung pada berapa banyak bidang yang Anda miliki dan bagaimana Anda ingin menulis SQL Anda. Mereka semua akan bekerja. Jika Anda hanya memiliki beberapa bidang maka saya mungkin akan pergi dengan # 1. Dengan "banyak" bidang saya akan condong ke # 2 atau # 3.

David
sumber
+1: opsi ke-3 adalah yang paling dekat dengan model pewarisan, dan IMO paling dinormalisasi
RedFilter
Opsi Anda # 3 benar-benar seperti yang saya maksud dengan opsi # 2. Ada banyak bidang dan beberapa Bagian akan memiliki entitas anak juga.
Steve Jones
9

Dengan informasi yang diberikan, saya akan memodelkan database untuk memiliki yang berikut:

KEBIJAKAN

  • POLICY_ID (kunci utama)

KEWAJIBAN

  • LIABILITY_ID (kunci utama)
  • POLICY_ID (kunci asing)

SIFAT

  • PROPERTY_ID (kunci utama)
  • POLICY_ID (kunci asing)

... dan seterusnya, karena saya berharap akan ada atribut berbeda yang terkait dengan setiap bagian dari kebijakan. Kalau tidak, mungkin ada satu SECTIONSmeja dan selain itu policy_id, akan ada section_type_code...

Bagaimanapun, ini akan memungkinkan Anda untuk mendukung bagian opsional per kebijakan ...

Saya tidak mengerti apa yang Anda anggap tidak memuaskan tentang pendekatan ini - ini adalah cara Anda menyimpan data sambil mempertahankan integritas referensial dan tidak menggandakan data. Istilah ini "dinormalisasi" ...

Karena SQL berbasis SET, ini agak asing dengan konsep pemrograman prosedural / OO & membutuhkan kode untuk transisi dari satu ranah ke ranah lain. ORM sering dipertimbangkan, tetapi mereka tidak bekerja dengan baik dalam sistem volume tinggi dan kompleks.

OMG Ponies
sumber
Ya, saya mendapatkan hal normalisasi ;-) Untuk struktur yang kompleks, dengan beberapa bagian yang sederhana dan beberapa memiliki sub-struktur kompleks mereka sendiri, sepertinya ORM tidak akan bekerja, walaupun itu akan menyenangkan.
Steve Jones
6

Selain itu pada solusi Daniel Vassallo, jika Anda menggunakan SQL Server 2016+, ada solusi lain yang saya gunakan dalam beberapa kasus tanpa kehilangan kinerja.

Anda bisa membuat tabel hanya dengan bidang umum dan menambahkan satu kolom dengan string JSON yang berisi semua bidang khusus subtipe.

Saya telah menguji desain ini untuk mengelola warisan dan saya sangat senang atas fleksibilitas yang dapat saya gunakan dalam aplikasi relatif.

pemenang
sumber
1
Itu ide yang menarik. Saya belum pernah menggunakan JSON di SQL Server, tetapi banyak menggunakannya di tempat lain. Terimakasih atas peringatannya.
Steve Jones
5

Cara lain untuk melakukannya, adalah menggunakan INHERITSkomponen. Sebagai contoh:

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);

Dengan demikian dimungkinkan untuk mendefinisikan warisan antar tabel.

Marco Paulo Ollivier
sumber
Apakah DB lain mendukung INHERITSselain PostgreSQL ? MySQL misalnya?
giannis christofakis
1
@giannischristofakis: MySQL hanya database relasional, sedangkan Postgres adalah database objek-relasional. Jadi, tidak ada MySQL yang tidak mendukung ini. Sebenarnya, saya berpikir bahwa Postgres adalah satu-satunya DBMS saat ini yang mendukung tipe pewarisan ini.
a_horse_with_no_name
2
@ marco-paulo-ollivier, pertanyaan OP adalah tentang SQL Server, jadi saya tidak mengerti mengapa Anda memberikan solusi yang hanya berfungsi dengan Postgres. Jelas, tidak mengatasi masalah tersebut.
map hingga
@mapto pertanyaan ini telah menjadi sesuatu dari sasaran dupe "bagaimana cara melakukan gaya pewarisan OO dalam database"; bahwa itu awalnya tentang sql server kemungkinan sekarang tidak relevan
Caius Jard
0

Saya condong ke metode # 1 (tabel Bagian terpadu), demi mengambil seluruh kebijakan secara efisien dengan semua bagian mereka (yang saya anggap sistem Anda akan melakukan banyak hal).

Lebih lanjut, saya tidak tahu versi SQL Server yang Anda gunakan, tetapi pada 2008+ Kolom Jarang membantu mengoptimalkan kinerja dalam situasi di mana banyak nilai dalam kolom adalah NULL.

Pada akhirnya, Anda harus memutuskan seberapa "mirip" bagian kebijakan tersebut. Kecuali mereka berbeda secara substansial, saya pikir solusi yang lebih normal mungkin lebih banyak masalah daripada nilainya ... tetapi hanya Anda yang bisa melakukan panggilan itu. :)

Dan J
sumber
Akan ada terlalu banyak informasi untuk menyajikan seluruh Kebijakan dalam sekali jalan, sehingga tidak akan perlu untuk mengambil seluruh catatan. Saya pikir ini tahun 2005, walaupun saya jarang menggunakan 2008 di proyek lain.
Steve Jones
Di mana istilah "tabel bagian terpadu" berasal? Google hampir tidak menunjukkan hasil untuk itu dan sudah ada cukup istilah yang membingungkan di sini.
Stephan-v
-1

Sebagai alternatif, pertimbangkan untuk menggunakan basis data dokumen (seperti MongoDB) yang secara asli mendukung struktur data yang kaya dan bersarang.

Grigori Melnik
sumber