Apa yang Anda gambarkan disebut Asosiasi Polimorfik. Yaitu, kolom "kunci asing" berisi nilai id yang harus ada di salah satu dari set tabel target. Biasanya tabel target terkait dalam beberapa cara, seperti menjadi contoh beberapa data superkelas umum. Anda juga membutuhkan kolom lain di samping kolom kunci asing, sehingga pada setiap baris, Anda dapat menentukan tabel target mana yang dirujuk.
CREATE TABLE popular_places (
user_id INT NOT NULL,
place_id INT NOT NULL,
place_type VARCHAR(10) -- either 'states' or 'countries'
-- foreign key is not possible
);
Tidak ada cara untuk memodelkan Asosiasi Polimorfik menggunakan batasan SQL. Batasan kunci asing selalu merujuk satu tabel target.
Asosiasi Polimorfik didukung oleh kerangka kerja seperti Rails dan Hibernate. Tetapi mereka secara eksplisit mengatakan bahwa Anda harus menonaktifkan batasan SQL untuk menggunakan fitur ini. Sebaliknya, aplikasi atau kerangka kerja harus melakukan pekerjaan yang setara untuk memastikan bahwa referensi terpenuhi. Artinya, nilai dalam kunci asing hadir di salah satu tabel target yang mungkin.
Asosiasi Polymorphic lemah sehubungan dengan menegakkan konsistensi database. Integritas data tergantung pada semua klien yang mengakses database dengan logika integritas referensial yang sama ditegakkan, dan juga penegakannya harus bebas bug.
Berikut adalah beberapa solusi alternatif yang memanfaatkan integritas referensial yang didukung oleh database:
Buat satu tabel tambahan per target. Misalnya popular_states
dan popular_countries
referensi mana states
dan countries
masing - masing. Setiap tabel "populer" ini juga merujuk profil pengguna.
CREATE TABLE popular_states (
state_id INT NOT NULL,
user_id INT NOT NULL,
PRIMARY KEY(state_id, user_id),
FOREIGN KEY (state_id) REFERENCES states(state_id),
FOREIGN KEY (user_id) REFERENCES users(user_id),
);
CREATE TABLE popular_countries (
country_id INT NOT NULL,
user_id INT NOT NULL,
PRIMARY KEY(country_id, user_id),
FOREIGN KEY (country_id) REFERENCES countries(country_id),
FOREIGN KEY (user_id) REFERENCES users(user_id),
);
Ini berarti bahwa untuk mendapatkan semua tempat favorit populer pengguna, Anda perlu menanyakan kedua tabel ini. Tetapi itu berarti Anda dapat mengandalkan database untuk menegakkan konsistensi.
Buat places
tabel sebagai supertable. Seperti yang Abie sebutkan, alternatif kedua adalah tempat populer Anda mereferensikan tabel seperti places
, yang merupakan induk bagi keduanya states
dan countries
. Artinya, baik negara bagian maupun negara juga memiliki kunci asing untuk places
(Anda bahkan dapat membuat kunci asing ini juga menjadi kunci utama states
dan countries
).
CREATE TABLE popular_areas (
user_id INT NOT NULL,
place_id INT NOT NULL,
PRIMARY KEY (user_id, place_id),
FOREIGN KEY (place_id) REFERENCES places(place_id)
);
CREATE TABLE states (
state_id INT NOT NULL PRIMARY KEY,
FOREIGN KEY (state_id) REFERENCES places(place_id)
);
CREATE TABLE countries (
country_id INT NOT NULL PRIMARY KEY,
FOREIGN KEY (country_id) REFERENCES places(place_id)
);
Gunakan dua kolom. Alih-alih satu kolom yang dapat mereferensikan salah satu dari dua tabel target, gunakan dua kolom. Dua kolom ini mungkin NULL
; pada kenyataannya hanya satu dari mereka yang bukan NULL
.
CREATE TABLE popular_areas (
place_id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
state_id INT,
country_id INT,
CONSTRAINT UNIQUE (user_id, state_id, country_id), -- UNIQUE permits NULLs
CONSTRAINT CHECK (state_id IS NOT NULL OR country_id IS NOT NULL),
FOREIGN KEY (state_id) REFERENCES places(place_id),
FOREIGN KEY (country_id) REFERENCES places(place_id)
);
Dalam hal teori relasional, Asosiasi Polimorfik melanggar Bentuk Normal Pertama , karena popular_place_id
pada dasarnya kolom dengan dua makna: itu adalah negara atau negara. Anda tidak akan menyimpan seseorang age
dan mereka phone_number
dalam satu kolom, dan untuk alasan yang sama Anda tidak harus menyimpan keduanya state_id
dan country_id
dalam satu kolom. Fakta bahwa kedua atribut ini memiliki tipe data yang kompatibel adalah kebetulan; mereka masih menandakan entitas logis yang berbeda.
Asosiasi Polimorfik juga melanggar Bentuk Normal Ketiga , karena makna kolom tergantung pada kolom tambahan yang menamai tabel yang dirujuk oleh kunci asing. Dalam Bentuk Normal Ketiga, atribut dalam tabel harus hanya bergantung pada kunci utama tabel itu.
Re komentar dari @SavasVedova:
Saya tidak yakin saya mengikuti uraian Anda tanpa melihat definisi tabel atau kueri contoh, tetapi sepertinya Anda hanya memiliki beberapa Filters
tabel, masing-masing berisi kunci asing yang mereferensikan Products
tabel pusat .
CREATE TABLE Products (
product_id INT PRIMARY KEY
);
CREATE TABLE FiltersType1 (
filter_id INT PRIMARY KEY,
product_id INT NOT NULL,
FOREIGN KEY (product_id) REFERENCES Products(product_id)
);
CREATE TABLE FiltersType2 (
filter_id INT PRIMARY KEY,
product_id INT NOT NULL,
FOREIGN KEY (product_id) REFERENCES Products(product_id)
);
...and other filter tables...
Bergabung dengan produk ke tipe filter tertentu mudah jika Anda tahu tipe yang ingin Anda ikuti:
SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)
Jika Anda ingin tipe filter menjadi dinamis, Anda harus menulis kode aplikasi untuk membangun kueri SQL. SQL mengharuskan tabel ditentukan dan diperbaiki pada saat Anda menulis kueri. Anda tidak dapat membuat tabel bergabung dipilih secara dinamis berdasarkan nilai-nilai yang ditemukan di baris individu Products
.
Satu-satunya opsi lain adalah bergabung ke semua tabel filter menggunakan gabungan luar. Mereka yang tidak memiliki product_id yang cocok hanya akan dikembalikan sebagai satu baris nol. Tetapi Anda masih harus melakukan hardcode semua tabel yang tergabung, dan jika Anda menambahkan tabel filter baru, Anda harus memperbarui kode Anda.
SELECT * FROM Products
LEFT OUTER JOIN FiltersType1 USING (product_id)
LEFT OUTER JOIN FiltersType2 USING (product_id)
LEFT OUTER JOIN FiltersType3 USING (product_id)
...
Cara lain untuk bergabung ke semua tabel filter adalah melakukannya secara serial:
SELECT * FROM Product
INNER JOIN FiltersType1 USING (product_id)
UNION ALL
SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)
UNION ALL
SELECT * FROM Products
INNER JOIN FiltersType3 USING (product_id)
...
Tetapi format ini masih mengharuskan Anda untuk menulis referensi ke semua tabel. Tidak ada jalan keluarnya.
join
target akan berubah juga ...... Apakah saya terlalu rumit atau apa? Tolong!