Czy istnieją DBMS, które pozwalają na klucz obcy, który odwołuje się do widoku (i nie tylko tabel podstawowych)?

22

Inspirowane pytaniem dotyczącym modelowania Django: Modelowanie bazy danych z wieloma relacjami wiele do wielu w Django . DB-design jest podobny do:

CREATE TABLE Book
( BookID INT NOT NULL
, BookTitle VARCHAR(200) NOT NULL
, PRIMARY KEY (BookID)
) ;

CREATE TABLE Tag
( TagID INT NOT NULL
, TagName VARCHAR(50) NOT NULL
, PRIMARY KEY (TagID)
) ;

CREATE TABLE BookTag
( BookID INT NOT NULL
, TagID INT NOT NULL
, PRIMARY KEY (BookID, TagID)
, FOREIGN KEY (BookID)  REFERENCES Book (BookID)
, FOREIGN KEY (TagID)   REFERENCES Tag (TagID)
) ;

CREATE TABLE Aspect
( AspectID INT NOT NULL
, AspectName VARCHAR(50) NOT NULL
, PRIMARY KEY (AspectID)
) ;

CREATE TABLE TagAspect
( TagID INT NOT NULL
, AspectID INT NOT NULL
, PRIMARY KEY (TagID, AspectID) 
, FOREIGN KEY (TagID)   REFERENCES Tag (TagID)
, FOREIGN KEY (AspectID)  REFERENCES Aspect (AspectID)
) ;

schemat db

problem polega na tym, jak zdefiniować BookAspectRatingtabelę i wymusić integralność referencyjną, więc nie można dodać oceny dla (Book, Aspect)kombinacji, która jest nieprawidłowa.

AFAIK, złożone CHECKograniczenia (lub ASSERTIONS), które obejmują podzapytania i więcej niż jedną tabelę, które mogłyby rozwiązać ten problem, nie są dostępne w żadnym DBMS.

Innym pomysłem jest użycie (pseudokodu) widoku:

CREATE VIEW BookAspect_view
  AS
SELECT DISTINCT
    bt.BookId
  , ta.AspectId
FROM 
    BookTag AS bt
  JOIN 
    Tag AS t  ON t.TagID = bt.TagID
  JOIN 
    TagAspect AS ta  ON ta.TagID = bt.TagID 
WITH PRIMARY KEY (BookId, AspectId) ;

oraz tabelę, która ma klucz obcy do powyższego widoku:

CREATE TABLE BookAspectRating
( BookID INT NOT NULL
, AspectID INT NOT NULL
, PersonID INT NOT NULL
, Rating INT NOT NULL
, PRIMARY KEY (BookID, AspectID, PersonID)
, FOREIGN KEY (PersonID)   REFERENCES Person (PersonID)
, FOREIGN KEY (BookID, AspectID) 
    REFERENCES BookAspect_view (BookID, AspectID)
) ;

Trzy pytania:

  • Czy istnieją DBMS, które pozwalają (ewentualnie zmaterializować) się VIEWz PRIMARY KEY?

  • Czy istnieją DBMS, które pozwalają FOREIGN KEY, że REFERENCESjest VIEW(a nie tylko podstawy TABLE)?

  • Czy problem integralności mógłby zostać rozwiązany w inny sposób - dzięki dostępnym funkcjom DBMS?


Wyjaśnienie:

Ponieważ prawdopodobnie nie ma w 100% satysfakcjonującego rozwiązania - a pytanie Django nie jest nawet moje! - Bardziej interesuje mnie ogólna strategia możliwego ataku na problem, a nie szczegółowe rozwiązanie. Tak więc odpowiedź typu „w DBMS-X można to zrobić za pomocą wyzwalaczy z tabeli A” jest całkowicie akceptowalna.

ypercubeᵀᴹ
źródło
Publikowanie jako komentarz do twoich pierwszych dwóch pytań - i niekoniecznie dla ciebie, jak już jestem pewien - ale SQL Server nie obsługuje kluczy podstawowych ani obcych dla widoków.
Aaron Bertrand
@Aaron: tak, dziękuję. Czytałem, że Oracle obsługuje ograniczenia kosztów PK w widokach. Ale nie jestem pewien, czy to zadziała w tej sytuacji. A odpowiedź na drugie pytanie (dotyczące FK do wyświetleń) jest prawdopodobnie w Oracle negatywna.
ypercubeᵀᴹ
Ale jestem zainteresowany, aby dowiedzieć się, czy istnieje jakieś inne rozwiązanie (wyzwalacze, sprawdź ograniczenia kosztów lub inne kombinacje)
ypercube12

Odpowiedzi:

9

Tę regułę biznesową można wymusić w modelu przy użyciu tylko ograniczeń. Poniższa tabela powinna rozwiązać twój problem. Użyj go zamiast swojego widoku:

    CREATE TABLE BookAspectCommonTagLink
    (  BookID INT NOT NULL
    , AspectID INT NOT NULL
    , TagID INT NOT NULL
--TagID is deliberately left out of PK
    , PRIMARY KEY (BookID, AspectID)
    , FOREIGN KEY (BookID, TagID) 
        REFERENCES BookTag (BookID, TagID)
    , FOREIGN KEY (AspectID, TagID) 
        REFERENCES AspectTag (AspectID, TagID)
    ) ;
AK
źródło
Och, miło. Jedyny problem, jaki mogę wymyślić, to złożoność wprowadzana podczas wstawiania / usuwania BookTags i TagAspects. Za każdym razem, gdy usuwany jest nowy BookTag (lub TagAspect), należy przeprowadzić wyszukiwanie w celu usunięcia odpowiednich wierszy w tej tabeli i / lub zmiany na TagIDinny Tag związany z tą samą kombinacją BookAspect.
ypercubeᵀᴹ
I podobne wyszukiwanie musiałoby zostać przeprowadzone w celu wstawienia do tych 2 tabel. Ale złożone reguły wymagają skomplikowanych procedur, więc wygląda to naprawdę dobrze.
ypercubeᵀᴹ
@ypercube Kiedy usuwasz znacznik, musisz sprawdzić i ewentualnie przełączyć na inny znacznik łączący tę samą książkę i format. Jednak po wstawieniu nowych tagów nie trzeba wykonywać żadnych kontroli, dopóki nie trzeba wstawić oceny.
AK
1
Jeśli narzędzie do rozwiązywania problemów i osoba wprowadzająca dane to ta sama osoba lub jeśli ujawnisz komunikat o błędzie użytkownikowi końcowemu, na pewno. Za dużo myślisz o sklepach jednoosobowych, w których robisz wszystko.
Aaron Bertrand
4
@AaronBertrand Właśnie zrobiłeś mi wielką przysługę. Kończę artykuł zatytułowany „Opracowywanie baz danych o niskich kosztach utrzymania” i zapomniałem wspomnieć, że serwery aplikacji muszą rejestrować oryginalne komunikaty o błędach pochodzące z baz danych. Właśnie to dodałem. Dziękujemy za przypomnienie;)
AK
8

Myślę, że przekonasz się, że w wielu przypadkach złożonych reguł biznesowych nie można egzekwować za pomocą samego modelu. Jest to jeden z tych przypadków, w których, przynajmniej w SQL Server, myślę, że wyzwalacz (najlepiej zamiast wyzwalacza) lepiej służy twojemu celowi.

Aaron Bertrand
źródło
Hej, Aaron, czy możesz wyjaśnić, dlaczego w tym przypadku wyzwalacz jest lepszym wyborem niż byt i kilka ograniczeń?
AK
2
@AlexKuznetsov Pewnie, ponieważ nie spędziłem 17 godzin zastanawiając się, jak to zaimplementować z wieloma wielokolumnowymi kluczami obcymi i całą dodatkową logiką, która może być wymagana do poradzenia sobie z sprawdzaniem poprawności i obsługą błędów?
Aaron Bertrand
2
Uważaj na warunki wyścigu, które może wprowadzić naiwna implementacja wyzwalaczy. Na przykład jedna transakcja może odłączyć książkę od tagu, a inna nadal uważa, że ​​połączenie z odpowiednim aspektem jest w porządku, po prostu dlatego, że pierwsza transakcja jeszcze się nie popełniła. Złożoność wprowadzona przez odpowiedź @AlexKuznetsov jest prawdopodobnie mniejsza niż złożoność i kruchość blokującego „protokołu” niezbędnego do uniknięcia warunków wyścigu w wyzwalaczach, IMHO.
Branko Dimitrijevic
8

W Oracle jednym ze sposobów wymuszania tego rodzaju ograniczenia w sposób deklaratywny byłoby utworzenie zmaterializowanego widoku, który jest ustawiony na szybkie odświeżanie przy zatwierdzeniu, którego zapytanie identyfikuje wszystkie nieprawidłowe wiersze (tj. BookAspectRatingWiersze, które nie pasują do siebie BookAspect_view). Następnie można utworzyć trywialne ograniczenie dla zmaterializowanego widoku, które zostałoby naruszone, gdyby w zmaterializowanym widoku były wiersze. Ma to tę zaletę, że minimalizuje ilość danych, które należy powielić w zmaterializowanym widoku. Może to jednak powodować problemy, ponieważ ograniczenie jest egzekwowane tylko w miejscu, w którym dokonujesz transakcji - wiele aplikacji nie jest napisanych, aby oczekiwać, że operacja zatwierdzenia może się nie powieść - i ponieważ naruszenie ograniczenia może być nieco trudne skojarzyć z określonym wierszem lub określoną tabelą.

Justin Cave
źródło
4

SIRA_PRISE pozwala na to.

Chociaż FK nie jest już nazywany „FK”, a jedynie „ograniczenie bazy danych”, a „widok” w rzeczywistości nie musi nawet być definiowany jako widok, można po prostu dołączyć wyrażenie definiujące widok do deklaracji ograniczenie bazy danych.

Twoje ograniczenie wyglądałoby mniej więcej tak

SEMIMINUS(BOOKASPECT , JOIN(BOOKTAG , TAGASPECT))

i jesteś skończony.

Jednak w większości SQL DBMS musiałbyś wykonać analizę na swoim ograniczeniu, ustalić, w jaki sposób można go złamać i zaimplementować wszystkie potrzebne wyzwalacze.

Erwin Smout
źródło
Wiem. Odzwierciedla to, co uważałem za ważne w momencie pisania.
Erwin Smout,
3

W PostgreSQL nie wyobrażam sobie rozwiązania bez angażowania wyzwalaczy, ale z pewnością można je rozwiązać w ten sposób (utrzymując jakiś zmaterializowany widok lub przed włączeniem wyzwalacza BookAspectRating). Brak kluczy obcych odwołujących się do view ( ERROR: referenced relation "v_munkalap" is not a table), nie mówiąc już o kluczu podstawowym.

dezso
źródło