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!Ini bukan solusi paling elegan di dunia, tetapi Anda bisa menggunakan warisan tabel konkret untuk membuat karya ini.
Secara konseptual Anda mengusulkan gagasan tentang kelas "hal-hal yang dapat menjadi area populer" dari mana ketiga jenis tempat Anda mewarisi. Anda bisa mewakili ini sebagai tabel yang disebut, misalnya,
places
di mana setiap baris memiliki hubungan satu-ke-satu dengan berturut-turut diregions
,countries
, ataustates
. (Atribut yang dibagi antara wilayah, negara, atau negara bagian, jika ada, dapat didorong ke tabel tempat ini.) Andapopular_place_id
kemudian akan menjadi referensi kunci asing ke baris di tabel tempat yang kemudian akan membawa Anda ke suatu wilayah, negara , atau negara.Solusi yang Anda usulkan dengan kolom kedua untuk menggambarkan jenis asosiasi terjadi pada bagaimana Rails menangani asosiasi polimorfik, tetapi saya bukan penggemar itu secara umum. Bill menjelaskan dengan sangat terinci mengapa asosiasi polimorfik bukan teman Anda.
sumber
Berikut ini adalah koreksi terhadap pendekatan "luar biasa" Bill Karwin, menggunakan kunci majemuk
( place_type, place_id )
untuk menyelesaikan dugaan pelanggaran bentuk normal:Apa yang desain ini tidak dapat memastikan bahwa untuk setiap baris di
places
sana ada baris dalamstates
ataucountries
(tetapi tidak keduanya). Ini adalah batasan kunci asing dalam SQL. Dalam DBMS yang sesuai dengan Standar SQL-92, Anda dapat mendefinisikan batasan antar-tabel yang dapat ditangguhkan yang akan memungkinkan Anda untuk mencapai hal yang sama tetapi kikuk, melibatkan transaksi dan DBMS semacam itu belum membuatnya dipasarkan.sumber
Saya menyadari bahwa utas ini sudah tua, tetapi saya melihat ini dan solusi muncul di benak saya dan saya pikir saya akan membuangnya di sana.
Wilayah, Negara, dan Negara adalah Lokasi Geografis yang hidup dalam hierarki.
Anda dapat menghindari masalah Anda sama sekali dengan membuat tabel domain yang disebut geografis_lokasi_type yang akan Anda isi dengan tiga baris (Wilayah, Negara, Negara).
Selanjutnya, alih-alih dari tiga tabel lokasi, buatlah tabel geografis_lokasi tunggal yang memiliki kunci asing geografi_location_type_id (sehingga Anda tahu jika turunannya adalah Wilayah, Negara atau Negara).
Modelkan hierarki dengan membuat tabel ini sebagai referensi-sendiri sehingga sebuah instance State menyimpan fKey ke instance induknya yang pada gilirannya menyimpan fKey pada instance Region induknya. Contoh wilayah akan menahan NULL di fKey itu. Ini tidak berbeda dengan apa yang akan Anda lakukan dengan tiga tabel (Anda akan memiliki 1 - banyak hubungan antara wilayah dan negara dan antara negara dan negara) kecuali sekarang semuanya dalam satu tabel.
Tabel popular_user_location akan menjadi tabel resolusi ruang lingkup antara pengguna dan georgraphical_location (begitu banyak pengguna dapat menyukai banyak tempat).
Soooo ...
Tidak yakin apa target DB; di atas adalah MS SQL Server.
sumber
Yah, saya punya dua tabel:
a) Nomor lagu b) Judul lagu ....
dan saya punya yang ketiga
Masalahnya adalah bahwa beberapa jenis daftar putar memiliki tautan ke daftar putar lain. Tetapi dalam mysql kita tidak memiliki kunci asing yang dikaitkan dengan dua tabel.
Solusi saya: Saya akan meletakkan kolom ketiga di songs_to_playlist_relation. Kolom itu akan menjadi boolean. Jika 1 lagu, maka yang lain akan menautkan ke tabel daftar putar.
Begitu:
a) Nomor daftar putar (int) b) Adalah lagu (boolean) c) Nomor relatif (nomor lagu atau nomor daftar putar) (int) ( bukan kunci asing ke tabel mana pun)
Itu saja!sumber