Mam stół z tym układem:
CREATE TABLE Favorites
(
FavoriteId uuid NOT NULL PRIMARY KEY,
UserId uuid NOT NULL,
RecipeId uuid NOT NULL,
MenuId uuid
)
Chcę utworzyć unikalne ograniczenie podobne do tego:
ALTER TABLE Favorites
ADD CONSTRAINT Favorites_UniqueFavorite UNIQUE(UserId, MenuId, RecipeId);
Pozwoli to jednak na wiele wierszy z tym samym (UserId, RecipeId)
, jeśli MenuId IS NULL
. Chcę pozwolić NULL
na MenuId
przechowywanie faworyta, który nie ma menu powiązanych, ale chcę tylko co najwyżej jeden z tych wierszy na parę użytkownik / receptury.
Dotychczasowe pomysły to:
Użyj jakiegoś zakodowanego UUID (takiego jak wszystkie zera) zamiast null.
JednakMenuId
ma ograniczenie FK w menu każdego użytkownika, więc musiałbym stworzyć specjalne menu „zerowe” dla każdego użytkownika, co jest kłopotliwe.Sprawdź, czy istnieje wpis zerowy, używając wyzwalacza.
Myślę, że to kłopot i lubię unikać wyzwalaczy tam, gdzie to możliwe. Ponadto nie ufam im, aby zagwarantować, że moje dane nigdy nie będą w złym stanie.Po prostu zapomnij o tym i sprawdź wcześniejsze istnienie pustego wpisu w oprogramowaniu pośrednim lub w funkcji wstawiania i nie masz tego ograniczenia.
Używam Postgres 9.0.
Czy przeoczyłem jakąś metodę?
źródło
UserId
,RecipeId
), jeśliMenuId IS NULL
?Odpowiedzi:
Utwórz dwa indeksy częściowe :
W ten sposób może istnieć tylko jedna kombinacja
(user_id, recipe_id)
gdziemenu_id IS NULL
, skutecznie wdrażając pożądane ograniczenie.Możliwe wady: nie możesz mieć odwołania do klucza obcego
(user_id, menu_id, recipe_id)
, nie możesz bazowaćCLUSTER
na indeksie częściowym, a zapytania bez pasującegoWHERE
warunku nie mogą używać indeksu częściowego. (Wydaje się mało prawdopodobne, abyś chciał mieć odniesienie FK o szerokości trzech kolumn - zamiast tego użyj kolumny PK).Jeśli potrzebujesz pełnego indeksu, możesz alternatywnie usunąć
WHERE
warunek,favo_3col_uni_idx
a Twoje wymagania są nadal egzekwowane.Indeks, który obejmuje teraz całą tabelę, pokrywa się z drugą tabelą i staje się większy. W zależności od typowych zapytań i odsetka
NULL
wartości może to być przydatne lub nie. W skrajnych sytuacjach może nawet pomóc utrzymać wszystkie trzy indeksy (dwa częściowe i suma na górze).Poza tym: Radzę nie używać identyfikatorów mieszanych w PostgreSQL .
źródło
Możesz stworzyć unikalny indeks z koalescencją w MenuId:
Musisz po prostu wybrać UUID dla WĘGLA, który nigdy nie nastąpi w „prawdziwym życiu”. Prawdopodobnie nigdy nie zobaczyłbyś zerowego UUID w prawdziwym życiu, ale możesz dodać ograniczenie CHECK, jeśli jesteś paranoikiem (a ponieważ oni naprawdę chcą cię dostać ...):
źródło
(MenuId <> '00000000-0000-0000-0000-000000000000')
jednak.NULL
jest domyślnie dozwolone. Przy okazji są trzy rodzaje ludzi. Te paranoiczne i ludzie, którzy nie robią baz danych. Trzeci rodzaj od czasu do czasu z zakłopotaniem zamieszcza pytania na temat SO. ;)Możesz przechowywać ulubione bez powiązanego menu w osobnej tabeli:
źródło
FavoriteWithoutMenu
pierwszym. Jeśli tak, po prostu dodaję link do menu - w przeciwnym razieFavoriteWithoutMenu
najpierw tworzę wiersz, a następnie w razie potrzeby łączę go z menu. Utrudnia to również wybranie wszystkich ulubionych w jednym zapytaniu: musiałbym zrobić coś dziwnego, jak najpierw wybrać wszystkie łącza menu, a następnie wybrać wszystkie Ulubione, których identyfikatory nie istnieją w pierwszym zapytaniu. Nie jestem pewien, czy mi się to podoba.NULL MenuId
, wstawisz do tej tabeli. Jeśli nie, doFavorites
stołu. Ale zapytanie, tak, będzie bardziej skomplikowane.Myślę, że jest tu problem semantyczny. Moim zdaniem użytkownik może mieć (ale tylko jeden ) ulubiony przepis na przygotowanie określonego menu. (OP ma pomieszane menu i przepis; jeśli się mylę: proszę zamień MenuId i RecipeId poniżej) Oznacza to, że {użytkownik, menu} powinien być unikalnym kluczem w tej tabeli. I powinien wskazywać dokładnie jeden przepis. Jeśli użytkownik nie ma ulubionej receptury dla tego konkretnego menu, nie powinien istnieć wiersz dla tej pary kluczy {użytkownik, menu}. Ponadto: klucz zastępczy (FaVouRiteId) jest zbędny: złożone klucze podstawowe doskonale nadają się do tabel mapowania relacyjnego.
Doprowadziłoby to do ograniczenia definicji tabeli:
źródło