Bagaimana cara memetakan hubungan IS-A ke dalam basis data?

26

Pertimbangkan yang berikut ini:

entity User
{
    autoincrement uid;
    string(20) name;
    int privilegeLevel;
}

entity DirectLoginUser
{
    inherits User;
    string(20) username;
    string(16) passwordHash;
}

entity OpenIdUser
{
    inherits User;
    //Whatever attributes OpenID needs... I don't know; this is hypothetical
}

Berbagai jenis pengguna (pengguna Login Langsung, dan pengguna OpenID) menampilkan hubungan IS-A; yaitu, bahwa kedua jenis pengguna adalah pengguna. Sekarang, ada beberapa cara ini dapat direpresentasikan dalam RDBMS:

Cara Satu

CREATE TABLE Users
(
    uid INTEGER AUTO_INCREMENT NOT NULL,
    name VARCHAR(20) NOT NULL,
    privlegeLevel INTEGER NOT NULL,
    type ENUM("DirectLogin", "OpenID") NOT NULL,
    username VARCHAR(20) NULL,
    passwordHash VARCHAR(20) NULL,
    //OpenID Attributes
    PRIMARY_KEY(uid)
)

Jalan Dua

CREATE TABLE Users
(
    uid INTEGER AUTO_INCREMENT NOT NULL,
    name VARCHAR(20) NOT NULL,
    privilegeLevel INTEGER NOT NULL,
    type ENUM("DirectLogin", "OpenID") NOT NULL,
    PRIMARY_KEY(uid)
)

CREATE TABLE DirectLogins
(
    uid INTEGER NOT_NULL,
    username VARCHAR(20) NOT NULL,
    passwordHash VARCHAR(20) NOT NULL,
    PRIMARY_KEY(uid),
    FORIGEN_KEY (uid) REFERENCES Users.uid
)

CREATE TABLE OpenIDLogins
(
    uid INTEGER NOT_NULL,
    // ...
    PRIMARY_KEY(uid),
    FORIGEN_KEY (uid) REFERENCES Users.uid
)

Jalan Tiga

CREATE TABLE DirectLoginUsers
(
    uid INTEGER AUTO_INCREMENT NOT NULL,
    name VARCHAR(20) NOT NULL,
    privlegeLevel INTEGER NOT NULL,
    username VARCHAR(20) NOT NULL,
    passwordHash VARCHAR(20) NOT NULL,
    PRIMARY_KEY(uid)
)

CREATE TABLE OpenIDUsers
(
    uid INTEGER AUTO_INCREMENT NOT NULL,
    name VARCHAR(20) NOT NULL,
    privlegeLevel INTEGER NOT NULL,
    //OpenID Attributes
    PRIMARY_KEY(uid)
)

Saya hampir yakin cara ketiga adalah cara yang salah, karena tidak mungkin untuk melakukan join sederhana terhadap pengguna di tempat lain dalam database.

Contoh dunia nyata saya bukan pengguna dengan contoh login berbeda; Saya tertarik bagaimana memodelkan hubungan ini dalam kasus umum.

Billy ONeal
sumber
Saya telah mengedit jawaban saya untuk memasukkan pendekatan yang disarankan dalam komentar oleh Joel Brown. Itu harus bekerja untuk Anda. Jika Anda mencari lebih banyak saran, saya akan menandai ulang pertanyaan Anda untuk menunjukkan bahwa Anda sedang mencari jawaban khusus MySQL.
Nick Chammas
Catatan itu benar-benar tergantung pada bagaimana relasi digunakan di seluruh database. Hubungan yang melibatkan entitas anak memerlukan kunci asing hanya untuk tabel itu, dan melarang pemetaan ke tabel pemersatu tunggal tanpa sedikitpun kekhasan.
beldaz
Dalam database objek-relasional seperti PostgreSQL, sebenarnya ada cara keempat: Anda bisa mendeklarasikan tabel INHERIT dari tabel lain. postgresql.org/docs/current/static/tutorial-inheritance.html
MarkusSchaber

Jawaban:

16

Cara kedua adalah cara yang benar.

Kelas dasar Anda mendapatkan tabel, dan kemudian kelas anak mendapatkan tabel mereka sendiri hanya dengan bidang tambahan yang mereka perkenalkan, ditambah referensi kunci asing ke tabel dasar.

Seperti yang disarankan Joel dalam komentarnya pada jawaban ini, Anda dapat menjamin bahwa pengguna akan memiliki login langsung atau login OpenID, tetapi tidak keduanya (dan juga mungkin juga tidak) dengan menambahkan kolom tipe ke setiap tabel sub-tipe yang mengunci kembali ke tabel root. Kolom tipe di setiap sub-tipe tabel dibatasi untuk memiliki nilai tunggal yang mewakili tipe tabel itu. Karena kolom ini adalah kunci asing ke tabel akar, hanya satu baris sub-jenis yang dapat menautkan ke baris akar yang sama pada suatu waktu.

Sebagai contoh, MySQL DDL akan terlihat seperti:

CREATE TABLE Users
(
      uid               INTEGER AUTO_INCREMENT NOT NULL
    , type              ENUM("DirectLogin", "OpenID") NOT NULL
    // ...

    , PRIMARY_KEY(uid)
);

CREATE TABLE DirectLogins
(
      uid               INTEGER NOT_NULL
    , type              ENUM("DirectLogin") NOT NULL
    // ...

    , PRIMARY_KEY(uid)
    , FORIGEN_KEY (uid, type) REFERENCES Users (uid, type)
);

CREATE TABLE OpenIDLogins
(
      uid               INTEGER NOT_NULL
    , type              ENUM("OpenID") NOT NULL
    // ...

    PRIMARY_KEY(uid),
    FORIGEN_KEY (uid, type) REFERENCES Users (uid, type)
);

(Pada platform lain, Anda akan menggunakan CHECKbatasan alih-alih ENUM.) MySQL mendukung kunci asing gabungan sehingga ini harus bekerja untuk Anda.

Cara satu valid, meskipun Anda membuang-buang ruang di NULLkolom -ableable karena penggunaannya tergantung pada jenis pengguna. Keuntungannya adalah jika Anda memilih untuk memperluas jenis pengguna apa yang akan disimpan dan jenis-jenis itu tidak memerlukan kolom tambahan, Anda bisa memperluas domain Anda ENUMdan menggunakan tabel yang sama.

Cara ketiga memaksa kueri yang merujuk pengguna untuk memeriksa kedua tabel. Ini juga mencegah Anda merujuk tabel pengguna tunggal melalui kunci asing.

Nick Chammas
sumber
1
Bagaimana cara saya berurusan dengan fakta bahwa menggunakan cara 2, tidak ada cara untuk menegakkan bahwa ada tepat satu baris yang sesuai di dua tabel lainnya?
Billy ONeal
2
@Illy - Keberatan yang bagus. Jika pengguna Anda hanya dapat memiliki satu atau yang lain, Anda dapat memberlakukan ini melalui layer proc atau pemicu Anda. Saya ingin tahu apakah ada cara tingkat-DDL untuk menegakkan batasan ini. (Sayangnya, tampilan yang diindeks tidak memungkinkan UNION , atau saya akan menyarankan tampilan yang diindeks dengan indeks yang unik terhadap UNION ALLdari uiddari dua tabel.)
Nick Chammas
Tentu saja itu mengasumsikan bahwa RDBMS Anda mendukung tampilan indeks di tempat pertama.
Billy ONeal
1
Cara praktis untuk mengimplemetasi batasan cross-table semacam ini adalah dengan memasukkan atribut partisi di tabel tipe-super. Kemudian masing-masing sub-tipe dapat memeriksa untuk memastikan bahwa itu hanya berhubungan dengan super-type yang memiliki nilai atribut partisi yang sesuai. Ini menghemat harus melakukan uji tabrakan dengan melihat satu atau lebih tabel sub-tipe lainnya.
Joel Brown
1
@ Joel - Jadi, misalnya, kita menambahkan typekolom ke setiap tabel sub-tipe yang dibatasi melalui CHECKkendala untuk memiliki tepat satu nilai (tipe tabel itu). Kemudian, kami membuat kunci asing sub-tabel ke super-tabel menjadi yang komposit pada keduanya uiddan type. Itu cerdik.
Nick Chammas
5

Mereka akan diberi nama

  1. Warisan Tabel Tunggal
  2. Warisan Tabel Kelas
  3. Warisan Tabel Beton .

dan semua memiliki kegunaan yang sah dan didukung oleh beberapa perpustakaan. Anda harus mencari tahu mana yang paling cocok.

Memiliki beberapa tabel akan membuat manajemen data lebih memperhatikan kode aplikasi Anda tetapi akan mengurangi jumlah ruang yang tidak digunakan.

flob
sumber
2
Ada teknik tambahan yang disebut "Shared Primary Key". Dalam teknik ini, tabel subclass tidak memiliki id kunci primer yang ditugaskan secara independen. Sebagai gantinya, PK dari tabel subclass adalah FK yang mereferensikan tabel superclass. Ini memberikan beberapa manfaat, terutama karena menegakkan sifat satu-ke-satu dari hubungan IS-A. Teknik ini merupakan tambahan pada Class Table Inheritance.
Walter Mitty