Esistono DBMS che consentono una chiave esterna che fa riferimento a una vista (e non solo alle tabelle di base)?


22

Ispirato da una domanda di modellazione di Django: Modellazione di database con molteplici relazioni molti-a-molti in Django . Il db-design è qualcosa di simile:

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)
) ;

diagramma db

e il problema è come definire la BookAspectRatingtabella e imporre l'integrità referenziale, quindi non è possibile aggiungere una valutazione per una (Book, Aspect)combinazione non valida.

AFAIK, CHECKvincoli complessi (o ASSERTIONS) che coinvolgono sottoquery e più di una tabella, che potrebbero eventualmente risolverlo, non sono disponibili in nessun DBMS.

Un'altra idea è quella di utilizzare (pseudocodice) una vista:

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) ;

e una tabella che ha una chiave esterna per la vista sopra:

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)
) ;

Tre domande:

  • Esistono DBMS che consentono un (possibilmente materializzato) VIEWcon un PRIMARY KEY?

  • Esistono DBMS che consentono a FOREIGN KEYche REFERENCESa VIEW(e non solo una base TABLE)?

  • Questo problema di integrità potrebbe essere risolto altrimenti - con le funzionalità DBMS disponibili?


Una precisazione:

Dal momento che probabilmente non esiste una soluzione soddisfacente al 100% - e la domanda di Django non è nemmeno mia! - Sono più interessato a una strategia generale di possibile attacco al problema, non a una soluzione dettagliata. Quindi, una risposta come "in DBMS-X questo può essere fatto con i trigger nella tabella A" è perfettamente accettabile.


Pubblicare come commento le prime due domande - e non necessariamente per te, come sono sicuro che tu sia già a conoscenza - ma SQL Server non supporta le chiavi primarie o esterne per le viste.
Aaron Bertrand

@Aaron: si grazie. Ho letto che Oracle supporta i racconti PK nelle viste. Ma non sono sicuro se funzionerebbe in questa situazione. E la risposta alla seconda domanda (sugli FK alle viste) è probabilmente negativa in Oracle.
ypercubeᵀᴹ

Ma sono interessato a sapere se esiste un'altra soluzione (trigger, Controlla costraint o altra combinazione)
ypercubeᵀᴹ

Risposte:


9

Questa regola aziendale può essere applicata nel modello utilizzando solo i vincoli. La tabella seguente dovrebbe risolvere il tuo problema. Usalo al posto della tua vista:

    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)
    ) ;

Oh bello. L'unico problema che posso pensare è la complessità introdotta nell'inserimento / eliminazione di BookTags e TagAspects. Ogni volta che un nuovo BookTag (o TagAspect) viene rimosso, ad esempio, è necessario effettuare una ricerca per rimuovere le righe corrispondenti in questa tabella e / o modificare TagIDun altro Tag correlato alla stessa combinazione BookAspect.
ypercubeᵀᴹ

E una ricerca simile dovrebbe essere fatta per l'inserimento in quelle 2 tabelle. Ma regole complesse richiedono procedure complesse, quindi questo sembra davvero buono.
ypercubeᵀᴹ

@ypercube Quando si elimina un tag, è necessario controllare e possibilmente passare a un altro tag che collega lo stesso libro e aspetto. Quando si inseriscono nuovi tag, tuttavia, non è necessario eseguire alcun controllo fino a quando non è necessario inserire una valutazione.
AK,

1
Se lo strumento di risoluzione dei problemi e la persona che inserisce i dati è la stessa persona o se si espone il messaggio di errore all'utente finale, sicuramente. Stai pensando troppo ai negozi di una persona in cui stai facendo tutto.
Aaron Bertrand

4
@AaronBertrand Mi hai appena fatto un grande favore. Sto finendo un articolo intitolato "Sviluppo di database a bassa manutenzione" e ho dimenticato di menzionare che i server delle app devono registrare i messaggi di errore originali provenienti dai database. L'ho appena aggiunto. Grazie per avermelo ricordato;)
AK,

8

Penso che scoprirai che in molti casi non è possibile applicare regole aziendali complesse solo tramite il modello. Questo è uno di quei casi in cui, almeno in SQL Server, penso che un trigger (preferibilmente un trigger anziché un trigger) serva meglio al tuo scopo.


Ehi Aaron, puoi spiegare perché in questo caso un trigger è una scelta migliore di un'entità e alcuni vincoli?
AK,

2
@AlexKuznetsov Certo, perché non ho trascorso 17 ore a pensare a come implementarlo con più chiavi esterne multi-colonna e tutta la logica aggiuntiva che potrebbe essere richiesta per gestire comunque la convalida e la gestione degli errori?
Aaron Bertrand

2
Fai attenzione alle condizioni di gara che potrebbe introdurre un'implementazione ingenua dei trigger. Ad esempio, una transazione potrebbe disconnettere un libro dal tag e un'altra ancora pensa che sia OK connettersi all'aspetto corrispondente, semplicemente perché la prima transazione non è stata ancora impegnata. Le complessità introdotte dalla risposta di @AlexKuznetsov sono probabilmente inferiori alla complessità e alla fragilità del "protocollo" di blocco necessario per prevenire le condizioni di gara nei trigger, IMHO.
Branko Dimitrijevic,

8

In Oracle, un modo per applicare questo tipo di vincolo in modo dichiarativo sarebbe quello di creare una vista materializzata che è impostata per aggiornarsi rapidamente sul commit la cui query identifica tutte le righe non valide (ovvero le BookAspectRatingrighe che non hanno corrispondenza BookAspect_view). È quindi possibile creare un vincolo banale su quella vista materializzata che sarebbe violata se ci fossero righe nella vista materializzata. Ciò ha il vantaggio di ridurre al minimo la quantità di dati che è necessario duplicare nella vista materializzata. Può causare problemi, tuttavia, poiché il vincolo viene applicato solo nel punto in cui si sta eseguendo il commit della transazione - molte applicazioni non sono scritte per aspettarsi che un'operazione di commit possa fallire - e perché la violazione del vincolo può essere piuttosto difficile associare a una riga o una tabella particolare.


4

SIRA_PRISE lo consente.

Sebbene l'FK non sia più chiamato "FK", ma solo "vincolo del database" e la "vista" in effetti non deve nemmeno essere definita come vista, è possibile includere l'espressione che definisce l'espressione all'interno della dichiarazione del vincolo del database.

Il tuo vincolo sarebbe simile

SEMIMINUS(BOOKASPECT , JOIN(BOOKTAG , TAGASPECT))

e hai finito.

Nella maggior parte dei DBMS SQL, tuttavia, dovresti fare il lavoro di analisi sul tuo vincolo, determinare come può essere violato e implementare tutti i trigger necessari.


Lo so. Riflette ciò che pensavo fosse importante al momento della scrittura.
Erwin Smout,

3

In PostgreSQL, non riesco a immaginare una soluzione senza coinvolgere i trigger, ma certamente può essere risolto in questo modo (sia mantenendo una visione materializzata di qualche tipo o attivando prima BookAspectRating). Non ci sono chiavi esterne che fanno riferimento a una vista ( ERROR: referenced relation "v_munkalap" is not a table), per non parlare di una chiave primaria.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.