Bagaimana Anda memodelkan pewarisan secara efektif dalam database?

131

Apa praktik terbaik untuk memodelkan pewarisan dalam basis data?

Apa trade-off (mis queriability)?

(Saya paling tertarik dengan SQL Server dan .NET, tetapi saya juga ingin memahami bagaimana platform lain mengatasi masalah ini.)

Bahkan Mien
sumber
14
Jika Anda tertarik pada "praktik terbaik", sebagian besar jawabannya tidak benar. Praktik terbaik menentukan bahwa RDb dan aplikasi bersifat independen; mereka memiliki kriteria desain yang sangat berbeda. Oleh karena itu "pemodelan pewarisan" dalam database (atau pemodelan RDb agar sesuai dengan satu aplikasi atau bahasa aplikasi) adalah praktik yang sangat buruk, tidak mendapat informasi, dan melanggar aturan desain RDb dasar, dan melumpuhkannya.
PerformanceDBA
kemungkinan duplikat Sesuatu seperti warisan dalam desain basis data
Steve Chambers
6
@ PerformaDBA Jadi apa saran Anda untuk menghindari warisan dalam model DB? Katakanlah kita memiliki 50 jenis guru yang berbeda, dan kita ingin menghubungkan guru tersebut dengan kelas. Bagaimana Anda akan mencapai itu tanpa memiliki warisan?
svlada
1
@svlada. Itu mudah untuk diterapkan dalam RDb, jadi "warisan" diperlukan. Ajukan pertanyaan, sertakan tabel defn dan contoh, dan saya akan menjawabnya secara terperinci. Jika Anda melakukannya dalam istilah OO, itu akan menjadi kekacauan kerajaan.
PerformanceDBA
1
Kemungkinan duplikat dari Bagaimana Anda bisa mewakili warisan dalam database?
philipxy

Jawaban:

162

Ada beberapa cara untuk memodelkan pewarisan dalam database. Yang Anda pilih tergantung pada kebutuhan Anda. Berikut ini beberapa opsi:

Table-Per-Type (TPT)

Setiap kelas memiliki meja sendiri. Kelas dasar memiliki semua elemen kelas dasar di dalamnya, dan setiap kelas yang berasal darinya memiliki tabel sendiri, dengan kunci utama yang juga merupakan kunci asing ke tabel kelas dasar; kelas tabel turunan hanya berisi elemen yang berbeda.

Jadi misalnya:

class Person {
    public int ID;
    public string FirstName;
    public string LastName;
}

class Employee : Person {
    public DateTime StartDate;
}

Akan menghasilkan tabel seperti:

table Person
------------
int id (PK)
string firstname
string lastname

table Employee
--------------
int id (PK, FK)
datetime startdate

Table-Per-Hierarchy (TPH)

Ada tabel tunggal yang mewakili semua hierarki warisan, yang berarti beberapa kolom mungkin akan jarang. Kolom diskriminator ditambahkan yang memberi tahu sistem apa jenis baris ini.

Dengan kelas-kelas di atas, Anda berakhir dengan tabel ini:

table Person
------------
int id (PK)
int rowtype (0 = "Person", 1 = "Employee")
string firstname
string lastname
datetime startdate

Untuk setiap baris yang bertipe 0 (Orang), tanggal mulai akan selalu nol.

Table-Per-Beton (TPC)

Setiap kelas memiliki tabel yang sepenuhnya terbentuk sendiri tanpa referensi ke tabel lain.

Dengan kelas-kelas di atas, Anda berakhir dengan tabel ini:

table Person
------------
int id (PK)
string firstname
string lastname

table Employee
--------------
int id (PK)
string firstname
string lastname
datetime startdate
Brad Wilson
sumber
23
'Yang Anda pilih tergantung pada kebutuhan Anda' - tolong jelaskan, karena saya pikir alasan pilihan membentuk inti dari pertanyaan.
Alex
12
Lihat komentar saya pada pertanyaan. Menggunakan nama baru yang lucu untuk istilah teknis Rdb yang telah ada menyebabkan kebingungan. "TPT" adalah subtipe-supertipe. "TPH" adalah Tidak Normal, kesalahan kotor. "TPH" bahkan lebih dinormalisasi, kesalahan kotor lain.
PerformanceDBA
45
Hanya DBA yang akan menganggap bahwa denasionalisasi selalu merupakan kesalahan. :)
Brad Wilson
7
Walaupun saya akan mengakui bahwa denormalisasi menghasilkan peningkatan kinerja dalam beberapa kasus, ini sepenuhnya disebabkan oleh pemisahan yang tidak lengkap (atau tidak ada) antara struktur logis dan fisik data dalam DBMS. Sayangnya sebagian besar DBMS komersial menderita dari masalah ini. @ PerformaDBA sudah benar. Normalisasi adalah kesalahan penilaian, mengorbankan konsistensi data untuk kecepatan. Sayangnya, ini adalah pilihan yang DBA atau dev tidak perlu buat jika DBMS dirancang dengan benar. Sebagai catatan saya bukan DBA.
Kenneth Cochran
6
@ Edward Wilson. Hanya pengembang yang akan menormalisasi, "untuk kinerja", atau sebaliknya. Seringkali, ini bukan de-normalisasi, kenyataannya itu tidak normal. Bahwa de-Normalisasi atau tidak normal adalah kesalahan, adalah fakta, didukung oleh teori, dan dialami oleh jutaan orang, itu bukan "anggapan".
PerformanceDBA
133

Desain database yang tepat tidak seperti desain objek yang tepat.

Jika Anda berencana untuk menggunakan database untuk apa pun selain hanya membuat serial objek Anda (seperti laporan, kueri, penggunaan multi-aplikasi, intelijen bisnis, dll.) Maka saya tidak merekomendasikan segala jenis pemetaan sederhana dari objek ke tabel.

Banyak orang menganggap sebuah baris dalam tabel database sebagai entitas (saya menghabiskan waktu bertahun-tahun memikirkan istilah-istilah itu), tetapi sebuah baris bukanlah entitas. Itu adalah proposisi. Relasi basis data (yaitu, tabel) mewakili beberapa pernyataan fakta tentang dunia. Kehadiran baris mengindikasikan fakta itu benar (dan sebaliknya, ketidakhadirannya menunjukkan fakta itu salah).

Dengan pemahaman ini, Anda dapat melihat bahwa satu tipe dalam program berorientasi objek dapat disimpan di selusin hubungan yang berbeda. Dan berbagai jenis (disatukan oleh warisan, asosiasi, agregasi, atau sama sekali tidak terafiliasi) dapat sebagian disimpan dalam satu hubungan.

Yang terbaik adalah bertanya pada diri sendiri, fakta apa yang ingin Anda simpan, pertanyaan apa yang ingin Anda jawab, laporan apa yang ingin Anda hasilkan.

Setelah desain DB yang tepat dibuat, maka itu adalah masalah sederhana untuk membuat kueri / tampilan yang memungkinkan Anda untuk membuat serial objek Anda ke relasi tersebut.

Contoh:

Dalam sistem pemesanan hotel, Anda mungkin perlu menyimpan fakta bahwa Jane Doe memiliki pemesanan kamar di Seaview Inn untuk 10-12 April. Apakah itu atribut entitas pelanggan? Apakah itu atribut entitas hotel? Apakah ini entitas reservasi dengan properti yang mencakup pelanggan dan hotel? Bisa jadi salah satu atau semua hal itu dalam sistem berorientasi objek. Dalam database, itu bukan hal-hal itu. Ini hanyalah fakta yang tidak jelas.

Untuk melihat perbedaannya, pertimbangkan dua pertanyaan berikut. (1) Berapa banyak pemesanan hotel yang dimiliki Jane Doe untuk tahun depan? (2) Berapa banyak kamar yang dipesan untuk 10 April di Seaview Inn?

Dalam sistem berorientasi objek, kueri (1) adalah atribut entitas pelanggan, dan kueri (2) adalah atribut entitas hotel. Itu adalah objek yang akan mengekspos properti itu di API mereka. (Padahal, jelas mekanisme internal yang digunakan nilai-nilai tersebut dapat melibatkan referensi ke objek lain.)

Dalam sistem basis data relasional, kedua kueri akan memeriksa relasi reservasi untuk mendapatkan nomor mereka, dan secara konseptual tidak perlu repot dengan "entitas" lainnya.

Jadi, dengan mencoba menyimpan fakta tentang dunia — alih-alih mencoba menyimpan entitas dengan atribut — maka basis data relasional yang tepat dibangun. Dan begitu itu dirancang dengan baik, maka pertanyaan berguna yang tidak terbayangkan selama fase desain dapat dengan mudah dibangun, karena semua fakta yang diperlukan untuk memenuhi pertanyaan tersebut ada di tempat yang tepat.

Jeffrey L Whitledge
sumber
12
+1 Akhirnya, sebuah pulau pengetahuan asli di lautan ketidaktahuan (dan penolakan untuk belajar apa pun di luar ambisi mereka). Setuju, ini bukan sihir: jika RDb dirancang menggunakan prinsip RDb, tidak mudah untuk "memetakan" atau "memproyeksikan" setiap "kelas". Memaksa RDb ke dalam persyaratan berbasis kelas sama sekali tidak benar.
PerformanceDBA
2
Jawaban yang menarik. Bagaimana Anda menyarankan pemodelan contoh Person-Karyawan dalam jawaban yang diterima?
sevenforce
2
@ sevenforce-Desain DB benar-benar tergantung pada persyaratan sistem, yang tidak diberikan. Hampir tidak ada informasi yang cukup untuk memutuskan. Dalam banyak kasus, sesuatu yang mirip dengan desain "table-per-type" mungkin sesuai, jika tidak diikuti dengan kasar. Misalnya, tanggal mulai mungkin merupakan properti yang baik untuk dimiliki oleh objek Karyawan, tetapi dalam database itu harus benar-benar bidang dalam tabel Ketenagakerjaan, karena seseorang dapat disewa berulang kali dengan beberapa tanggal mulai. Ini tidak masalah untuk objek (yang akan menggunakan yang terbaru), tetapi penting dalam database.
Jeffrey L Whitledge
2
Tentu, pertanyaan saya terutama tentang cara memodelkan warisan. Maaf karena belum cukup jelas. Terima kasih. Seperti yang Anda sebutkan, kemungkinan besar harus ada Employmentmeja, yang mengumpulkan semua pekerjaan dengan tanggal mulai mereka. Jadi, jika mengetahui tanggal mulai kerja seorang saat Employerini adalah penting, itu bisa menjadi kasus penggunaan yang tepat untuk a View, yang mencakup properti itu dengan menanyakan? (catatan: tampaknya karena '-' tepat setelah nama panggilan saya, saya tidak mendapat pemberitahuan tentang komentar Anda)
sevenforce
5
Ini adalah permata nyata dari sebuah jawaban. Ini akan membutuhkan beberapa waktu untuk benar-benar meresap dan memerlukan beberapa latihan untuk mendapatkan yang benar, tetapi itu telah mempengaruhi proses pemikiran saya pada desain basis data relasional.
MarioDS
9

Jawaban singkat: Anda tidak.

Jika Anda perlu membuat serial objek Anda, gunakan ORM, atau bahkan sesuatu yang lebih baik seperti activerecord atau prevaylence.

Jika Anda perlu menyimpan data, simpanlah dengan cara yang relasional (berhati-hatilah dengan apa yang Anda simpan, dan perhatikan apa yang dikatakan Jeffrey L Whitledge), tidak ada yang terpengaruh oleh desain objek Anda.

Marcin
sumber
3
+1 Mencoba memodelkan pewarisan dalam basis data adalah pemborosan sumber daya yang baik dan relasional.
Daniel Spiewak
7

Pola TPT, TPH, dan TPC adalah cara yang Anda gunakan, seperti yang disebutkan oleh Brad Wilson. Tetapi beberapa catatan:

  • kelas anak yang diwarisi dari kelas dasar dapat dilihat sebagai entitas yang lemah terhadap definisi kelas dasar dalam database, yang berarti mereka bergantung pada kelas dasar mereka dan tidak dapat ada tanpa kelas itu. Saya telah melihat beberapa kali, bahwa ID unik disimpan untuk masing-masing dan setiap tabel anak sambil juga menjaga FK ke tabel induk. Satu FK cukup dan bahkan lebih baik untuk memiliki kaskade on-delete memungkinkan untuk hubungan-FK antara anak dan tabel dasar.

  • Di TPT, dengan hanya melihat catatan tabel dasar, Anda tidak dapat menemukan kelas anak mana yang diwakili catatan tersebut. Ini kadang-kadang diperlukan, ketika Anda ingin memuat daftar semua catatan (tanpa melakukan select pada setiap tabel anak). Salah satu cara untuk menangani ini, adalah dengan memiliki satu kolom yang mewakili jenis kelas anak (mirip dengan bidang rowType di TPH), jadi bagaimanapun juga, mencampur TPT dan TPH.

Katakanlah kita ingin mendesain database yang berisi diagram bentuk kelas berikut:

public class Shape {
int id;
Color color;
Thickness thickness;
//other fields
}

public class Rectangle : Shape {
Point topLeft;
Point bottomRight;
}

public class Circle : Shape {
Point center;
int radius;
}

Desain database untuk kelas-kelas di atas bisa seperti ini:

table Shape
-----------
int id; (PK)
int color;
int thichkness;
int rowType; (0 = Rectangle, 1 = Circle, 2 = ...)

table Rectangle
----------
int ShapeID; (FK on delete cascade)
int topLeftX;
int topLeftY;
int bottomRightX;
int bottomRightY;

table Circle
----------
int ShapeID; (FK on delete cascade)  
int centerX;
int center;
int radius;
imang
sumber
4

Ada dua jenis warisan yang bisa Anda siapkan dalam DB, tabel per entitas dan tabel per Hierarki.

Tabel per entitas adalah tempat Anda memiliki tabel entitas dasar yang memiliki properti bersama dari semua kelas anak. Anda kemudian memiliki per kelas anak tabel lain masing-masing dengan hanya properti yang berlaku untuk kelas itu. Mereka dihubungkan 1: 1 oleh PK mereka

teks alternatif

Tabel per hierarki adalah tempat semua kelas berbagi tabel, dan properti opsional dapat dibatalkan. Mereka juga merupakan bidang diskriminator yang merupakan angka yang menunjukkan jenis yang dimiliki catatan saat ini

teks alternatif SessionTypeID adalah diskriminator

Target per hierarki lebih cepat dicari karena Anda tidak perlu bergabung (hanya nilai diskriminator), sedangkan target per entitas yang perlu Anda lakukan bergabung kompleks untuk mendeteksi apa jenis sesuatu serta retreiuve semua datanya ..

Sunting: Gambar yang saya perlihatkan di sini adalah cuplikan layar dari proyek yang sedang saya kerjakan. Gambar Aset tidak lengkap, karena itu kekosongan itu, tetapi itu terutama untuk menunjukkan bagaimana pengaturannya, bukan apa yang harus dimasukkan ke dalam tabel Anda. Itu terserah anda ;). Tabel sesi berisi informasi sesi kolaborasi virtual, dan dapat terdiri dari beberapa jenis sesi tergantung pada jenis kolaborasi yang terlibat.

mattlant
sumber
Saya juga akan mempertimbangkan Target per kelas Beton untuk tidak benar-benar memodelkan warisan dengan baik dan jadi saya tidak menunjukkan.
mattlant
Bisakah Anda menambahkan referensi dari mana ilustrasi itu berasal?
chryss
Di mana gambar yang Anda bicarakan di akhir jawaban Anda?
Musa Haidari
1

Anda akan menormalkan database Anda dan itu sebenarnya akan mencerminkan warisan Anda. Itu mungkin memiliki penurunan kinerja, tapi begitulah dengan normalisasi. Anda mungkin harus menggunakan akal sehat untuk menemukan keseimbangan.

Per Hornshøj-Schierbeck
sumber
2
mengapa orang percaya bahwa normalisasi basis data menurunkan kinerja? apakah orang juga berpikir bahwa prinsip KERING menurunkan kinerja kode? dari mana salah persepsi ini berasal?
Steven A. Lowe
1
Mungkin karena denormalising dapat meningkatkan kinerja, maka normalisasi menurunkannya, secara relatif. Tidak bisa mengatakan saya setuju dengan itu, tapi mungkin itulah yang terjadi.
Matthew Scharley
2
Pada awalnya, normalisasi mungkin memiliki efek kecil pada kinerja, tetapi seiring waktu, seiring dengan meningkatnya jumlah baris, GABUNGAN efisien akan mulai mengungguli tabel bulkier. Tentu saja, normalisasi memiliki manfaat lain yang lebih besar - konsistensi dan kurangnya redundansi, dll.
Rob
1

ulangi jawaban utas yang serupa

dalam pemetaan ATAU, pewarisan memetakan ke tabel induk di mana tabel induk dan anak menggunakan pengidentifikasi yang sama

sebagai contoh

create table Object (
    Id int NOT NULL --primary key, auto-increment
    Name varchar(32)
)
create table SubObject (
    Id int NOT NULL  --primary key and also foreign key to Object
    Description varchar(32)
)

SubObject memiliki hubungan foreign-key dengan Object. saat Anda membuat baris SubObject, Anda harus terlebih dahulu membuat baris Objek dan menggunakan Id di kedua baris

EDIT: jika Anda mencari untuk memodelkan perilaku juga, Anda akan membutuhkan tabel tipe yang mencantumkan hubungan warisan antara tabel, dan menentukan perakitan dan nama kelas yang menerapkan perilaku setiap tabel

sepertinya berlebihan, tetapi itu semua tergantung pada apa Anda ingin menggunakannya!

Steven A. Lowe
sumber
Diskusi itu berakhir dengan menambahkan beberapa kolom ke setiap tabel, bukan tentang pemodelan pewarisan. Saya pikir judul diskusi itu harus diubah untuk lebih mencerminkan sifat pertanyaan dan diskusi.
Even Mien
1

Menggunakan SQL Alkimia (Python ORM), Anda dapat melakukan dua jenis warisan.

Pengalaman yang pernah saya alami adalah menggunakan meja makan, dan memiliki kolom diskriminan. Sebagai contoh, database Sheep (jangan bercanda!) Menyimpan semua Sheep dalam satu tabel, dan Rams dan Ewes ditangani menggunakan kolom gender dalam tabel itu.

Dengan demikian, Anda dapat meminta semua Domba, dan mendapatkan semua Domba. Atau Anda dapat meminta hanya dengan Ram, dan itu hanya akan mendapatkan Rams. Anda juga dapat melakukan hal-hal seperti memiliki hubungan yang hanya dapat menjadi Ram (yaitu, Sire of a Sheep), dan sebagainya.

Matthew Schinckel
sumber
1

Perhatikan bahwa beberapa mesin basis data sudah menyediakan mekanisme pewarisan asli seperti Postgres . Lihatlah dokumentasinya .

Sebagai contoh, Anda akan meminta sistem Person / Karyawan yang dijelaskan dalam respons di atas seperti ini:

  / * Ini menunjukkan nama depan semua orang atau karyawan * /
  SELECT firstname FROM Person; 

  / * Ini menunjukkan tanggal mulai semua karyawan saja * /
  SELECT mulai tanggal DARI Karyawan;

Dalam hal itu adalah pilihan basis data Anda, Anda tidak perlu menjadi sangat pintar!

Pierre
sumber