Saya punya tabel dengan tata letak ini:
CREATE TABLE Favorites
(
FavoriteId uuid NOT NULL PRIMARY KEY,
UserId uuid NOT NULL,
RecipeId uuid NOT NULL,
MenuId uuid
)
Saya ingin membuat batasan unik yang serupa dengan ini:
ALTER TABLE Favorites
ADD CONSTRAINT Favorites_UniqueFavorite UNIQUE(UserId, MenuId, RecipeId);
Namun, ini akan memungkinkan beberapa baris dengan yang sama (UserId, RecipeId)
, jika MenuId IS NULL
. Saya ingin mengizinkan NULL
dalam MenuId
untuk menyimpan favorit yang tidak ada menu terkait, tapi aku hanya ingin paling banyak satu baris ini per pasang user / resep.
Ide-ide yang saya miliki sejauh ini adalah:
Gunakan beberapa UUID hard-coded (seperti semua nol) bukan nol.
Namun,MenuId
memiliki batasan FK pada menu masing-masing pengguna, jadi saya kemudian harus membuat menu "null" khusus untuk setiap pengguna yang merepotkan.Periksa keberadaan entri nol menggunakan pemicu sebagai gantinya.
Saya pikir ini merepotkan dan saya suka menghindari pemicu sedapat mungkin. Plus, saya tidak percaya mereka untuk menjamin data saya tidak pernah dalam keadaan buruk.Lupakan saja dan periksa keberadaan entri nol sebelumnya di middle-ware atau fungsi insert, dan tidak memiliki kendala ini.
Saya menggunakan Postgres 9.0.
Apakah ada metode yang saya abaikan?
sumber
UserId
,RecipeId
), jikaMenuId IS NULL
?Jawaban:
Buat dua indeks parsial :
Dengan cara ini, hanya ada satu kombinasi di
(user_id, recipe_id)
manamenu_id IS NULL
, secara efektif mengimplementasikan kendala yang diinginkan.Kemungkinan kelemahan: Anda tidak dapat memiliki referensi kunci asing
(user_id, menu_id, recipe_id)
, Anda tidak dapat mendasarkanCLUSTER
pada indeks parsial, dan permintaan tanpaWHERE
kondisi yang cocok tidak dapat menggunakan indeks parsial. (Sepertinya tidak mungkin Anda ingin referensi FK tiga kolom lebar - gunakan kolom PK sebagai gantinya).Jika Anda membutuhkan indeks yang lengkap , Anda dapat menghilangkan
WHERE
kondisi tersebut dari alternatiffavo_3col_uni_idx
dan persyaratan Anda masih diberlakukan.Indeks, sekarang terdiri dari seluruh tabel, tumpang tindih dengan yang lain dan semakin besar. Bergantung pada permintaan tipikal dan persentase
NULL
nilai, ini mungkin bermanfaat atau tidak. Dalam situasi ekstrem, bahkan mungkin membantu mempertahankan ketiga indeks (dua yang parsial dan total di atas).Selain itu: Saya menyarankan untuk tidak menggunakan pengidentifikasi kasus campuran dalam PostgreSQL .
sumber
Anda bisa membuat indeks unik dengan menyatu di MenuId:
Anda hanya perlu memilih UUID untuk COALESCE yang tidak akan pernah terjadi di "kehidupan nyata". Anda mungkin tidak akan pernah melihat UUID nol dalam kehidupan nyata, tetapi Anda bisa menambahkan PERIKSA kendala jika Anda paranoid (dan karena mereka benar-benar keluar untuk membuat Anda ...):
sumber
(MenuId <> '00000000-0000-0000-0000-000000000000')
juga.NULL
diizinkan secara default. Btw, ada tiga jenis orang. Yang paranoid, dan orang yang tidak melakukan database. Jenis ketiga kadang-kadang memposting pertanyaan pada SO dengan bingung. ;)Anda dapat menyimpan favorit tanpa menu terkait dalam tabel terpisah:
sumber
FavoriteWithoutMenu
terlebih dahulu. Jika demikian, saya hanya menambahkan tautan menu - jika tidak, saya membuatFavoriteWithoutMenu
baris pertama dan kemudian menautkannya ke menu jika perlu. Itu juga membuat memilih semua favorit dalam satu permintaan sangat sulit: Saya harus melakukan sesuatu yang aneh seperti memilih semua tautan menu terlebih dahulu, dan kemudian memilih semua Favorit yang ID-nya tidak ada di dalam permintaan pertama. Saya tidak yakin apakah saya suka itu.NULL MenuId
, Anda memasukkan ke dalam tabel ini. Jika tidak, keFavorites
meja. Tapi bertanya, ya, itu akan lebih rumit.Saya pikir ada masalah semantik di sini. Dalam pandangan saya, pengguna dapat memiliki (tetapi hanya satu ) resep favorit untuk menyiapkan menu tertentu. (OP memiliki menu dan resep yang campur aduk; jika saya salah: silakan tukar MenuId dan RecipeId di bawah) Itu menyiratkan bahwa {user, menu} harus menjadi kunci unik dalam tabel ini. Dan itu harus menunjuk tepat satu resep. Jika pengguna tidak memiliki resep favorit untuk menu khusus ini, tidak ada baris untuk pasangan kunci {user, menu} ini. Juga: kunci pengganti (FaVouRiteId) tidak perlu: kunci primer komposit sangat valid untuk tabel pemetaan relasional.
Itu akan mengarah pada definisi tabel yang dikurangi:
sumber