¿Hay DBMS que permiten una clave externa que hace referencia a una vista (y no solo tablas base)?


22

Inspirado en una pregunta de modelado de Django: modelado de bases de datos con múltiples relaciones de muchos a muchos en Django . El diseño db es algo así como:

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

diagrama db

y el problema es cómo definir la BookAspectRatingtabla y hacer cumplir la integridad referencial, por lo que no se puede agregar una calificación para una (Book, Aspect)combinación que no es válida.

AFAIK, las CHECKrestricciones complejas (o ASSERTIONS) que involucran subconsultas y más de una tabla, que posiblemente podrían resolver esto, no están disponibles en ningún DBMS.

Otra idea es usar (pseudocódigo) 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) ;

y una tabla que tiene una clave externa para la vista anterior:

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

Tres preguntas:

  • ¿Hay DBMS que permiten un (posiblemente materializado) VIEWcon un PRIMARY KEY?

  • ¿Hay DBMS que permiten FOREIGN KEYque REFERENCESa VIEW(y no solo una base TABLE)?

  • ¿Podría resolverse este problema de integridad de otra manera, con las funciones DBMS disponibles?


Aclaración:

Dado que probablemente no haya una solución 100% satisfactoria, ¡y la pregunta de Django ni siquiera es mía! - Estoy más interesado en una estrategia general de posible ataque al problema, no en una solución detallada. Entonces, una respuesta como "en DBMS-X, esto se puede hacer con disparadores en la tabla A" es perfectamente aceptable.


Publicar como un comentario a sus dos primeras preguntas, y no necesariamente para usted, como estoy seguro de que ya sabe, pero SQL Server no admite claves primarias o externas para las vistas.
Aaron Bertrand

@ Aaron: sí, gracias. He leído que Oracle admite las restricciones de PK en las vistas. Pero no estoy seguro si funcionaría en esta situación. Y la respuesta a la segunda pregunta (sobre FKs para vistas) es probablemente negativa en Oracle.
ypercubeᵀᴹ

Pero estoy interesado en saber si hay alguna otra solución (disparadores, verificar costraints u otro combo)
ypercubeᵀᴹ

Respuestas:


9

Esta regla de negocio se puede aplicar en el modelo utilizando solo restricciones. La siguiente tabla debería resolver su problema. Úselo en lugar de su 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 bien. El único problema que puedo pensar es la complejidad introducida al insertar / eliminar BookTags y TagAspects. Cada vez que se elimina una nueva BookTag (o TagAspect), por ejemplo, se debe realizar una búsqueda para eliminar las filas correspondientes en esta tabla y / o cambiar la TagIDetiqueta a otra que esté relacionada con la misma combinación de BookAspect.
ypercubeᵀᴹ

Y se debería hacer una búsqueda similar para insertar en esas 2 tablas. Pero las reglas complejas requieren procedimientos complejos, por lo que se ve muy bien.
ypercubeᵀᴹ

@ypercube Cuando elimina una etiqueta, debe verificar y posiblemente cambiar a otra etiqueta que vincule el mismo Libro y Aspecto. Sin embargo, cuando inserta nuevas etiquetas, no es necesario hacer ninguna verificación hasta que necesite insertar una calificación.
AK

1
Si el solucionador de problemas y la persona que ingresa los datos es la misma persona, o si expone el mensaje de error al usuario final, seguro. Estás pensando demasiado en las tiendas de una persona donde estás haciendo todo.
Aaron Bertrand

44
@AaronBertrand Me acabas de hacer un gran favor. Estoy terminando un artículo titulado "Desarrollo de bases de datos de bajo mantenimiento", y olvidé mencionar que los servidores de aplicaciones deben registrar los mensajes de error originales que provienen de las bases de datos. Acabo de agregarlo. Gracias por recordar;)
AK

8

Creo que encontrará que en muchos casos, las reglas comerciales complejas no se pueden hacer cumplir solo a través del modelo. Este es uno de esos casos en los que, al menos en SQL Server, creo que un desencadenante (preferiblemente un desencadenante) sirve mejor a su propósito.


Hola Aaron, ¿puedes explicar por qué en este caso un desencadenante es una mejor opción que una entidad y algunas restricciones?
AK

2
@AlexKuznetsov Claro, porque no pasé 17 horas pensando en cómo implementar esto con múltiples claves externas de múltiples columnas, y toda la lógica adicional que podría ser necesaria para lidiar con la validación y el manejo de errores de todos modos.
Aaron Bertrand

2
Tenga cuidado con las condiciones de carrera que podría introducir una implementación ingenua de desencadenantes. Por ejemplo, una transacción puede desconectar un libro de la etiqueta y otra todavía piensa que está bien conectarse al aspecto correspondiente, simplemente porque la primera transacción aún no se ha confirmado. Las complejidades introducidas por la respuesta de @AlexKuznetsov son probablemente menores que la complejidad y fragilidad del "protocolo" de bloqueo necesario para prevenir las condiciones de carrera en los disparadores, en mi humilde opinión.
Branko Dimitrijevic

8

En Oracle, una forma de imponer este tipo de restricción de manera declarativa sería crear una vista materializada que se configure para actualizarse rápidamente en el commit cuya consulta identifica todas las filas no válidas (es decir, BookAspectRatingfilas que no tienen coincidencia BookAspect_view). Luego puede crear una restricción trivial en esa vista materializada que se violaría si hubiera filas en la vista materializada. Esto tiene el beneficio de minimizar la cantidad de datos que debe duplicar en la vista materializada. Sin embargo, puede causar problemas, ya que la restricción solo se aplica en el momento en que está confirmando la transacción (muchas aplicaciones no están escritas para esperar que una operación de confirmación pueda fallar) y porque la violación de la restricción puede ser algo difícil asociar con una fila particular o una tabla particular.


4

SIRA_PRISE lo permite.

Aunque el FK ya no se llama "FK", sino solo "restricción de la base de datos", y la "vista" en realidad ni siquiera tiene que definirse como una vista, solo puede incluir la expresión que define la vista dentro de la declaración del restricción de la base de datos.

Tu restricción se vería algo así

SEMIMINUS(BOOKASPECT , JOIN(BOOKTAG , TAGASPECT))

y tu estas listo.

Sin embargo, en la mayoría de los DBMS de SQL, tendría que hacer el trabajo de análisis de su restricción, determinar cómo se puede violar e implementar todos los desencadenantes necesarios.


Lo sé. Refleja lo que pensé que era importante al momento de escribir.
Erwin Smout

3

En PostgreSQL, no puedo imaginar una solución sin involucrar desencadenantes, pero ciertamente se puede resolver de esa manera (ya sea manteniendo una vista materializada de algún tipo o antes de activarlo BookAspectRating). No hay claves externas que hagan referencia a una vista ( ERROR: referenced relation "v_munkalap" is not a table), y mucho menos a una clave principal.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.