none
¿Cómo eliminar error de múltiples rutas de cascada? RRS feed

  • Pregunta

  • DBA's, les tengo una pregunta interesante, creo yo:  Tengo las siguientes 3 tablas:

    TablaA (ID, ...)

    TablaB (ID, IDDeA Foreign Key a TablaA(ID) update en cascada, ...)

    TablaC(ID, IDDeB Foreign Key a TablaB(ID) update en cascada, IDDeA Foreign Key a TablaA(ID) update en cascada, ...)

    Puedo crear tablas A y B sin problema, pero SQL Server 2012 se rehúsa a crear C porque dice que crearía múltiples rutas de cascada (error 1785, nivel 16).

    Creo entender a qué se refiere con "múltiples rutas", pero no veo eso en mi escenario.  Si el ID de tabla A cambiara, crearía un cambio en TablaB.IDDeA yTablaC.IDDeA, pero no veo en ninguna parte que sea posible que estas cascadas generen otras cascadas.

    ¿Entonces qué debo hacer?  Porque los objetos definidos en A pueden ser dueños de objetos B y C; algunos objetos C pueden estar ligados a objetos B pero no siempre; a veces están ligados a objetos A directamente o bien no tienen ligamen con objetos A del todo (TablaC.IDDeA puede ser nulo).

    ¿Cómo mantengo integridad referencial con este modelo o bien un modelo similar?


    Jose R. MCP
    Code Samples

    lunes, 9 de julio de 2012 16:48

Respuestas

  • Después de pensarlo largo y tendido, creo que la mejor alternativa es de hecho crear contenedores "por defecto" para todos los usuarios.  No queda más que pagar el precio de los datos adicionales.  No quiero meter cosas raras y esta sería una alternativa congruente con la normalización de bases de datos.

    Jose R. MCP
    Code Samples

    • Marcado como respuesta webJose martes, 10 de julio de 2012 2:12
    martes, 10 de julio de 2012 2:12

Todas las respuestas

  • Pega el script de creación de las tablas tal cual lo estás intentando crear y tal vez podamos ver alternativas.

    En cualquier caso, el artículo http://support.microsoft.com/kb/321843 explica la causa del error y una posible solución

    lunes, 9 de julio de 2012 18:16
  • Estos no son los scripts originales pero supongo que para recrear el problema son suficientes:

    Use tempdb;
    Go
    
    Create Table dbo.tblUsuarios
    (
    	ID int Not Null Identity(1, 1) Constraint PKC_dbo_tblUsuarios Primary Key Clustered
    	, Nombre nvarchar(50) Not Null
    );
    
    Create Table dbo.tblContenedores
    (
    	ID int Not Null Identity(1, 1) Constraint PKC_dbo_tblContenedores Primary Key Clustered
    	, UsuarioID int Null Constraint FK_dbo_tblContenedores_UsuarioDebeExistir Foreign Key References dbo.tblUsuarios(ID) On Update Cascade
    );
    
    Create Table dbo.tblActivos
    (
    	ID int Not Null Identity(1, 1) Constraint PKC_dbo_tblActivos Primary Key Clustered
    	, ContenedorID int Not Null Constraint FK_dbo_tblActivos_ContenedorDebeExistir Foreign Key References dbo.tblContenedores(ID) On Update Cascade
    	, UsuarioID int Null Constraint FK_dbo_tblActivos_UsuarioDebeExistir Foreign Key References dbo.tblActivos(ID) On Update Cascade
    );
    Go


    En este ejemplo, la creación de la última tabla fallará.  Esto es SQL Server 2012 y se migrará en producción a SQL Azure.


    Jose R. MCP
    Code Samples

    lunes, 9 de julio de 2012 18:41
  • Estos no son los scripts originales pero supongo que para recrear el problema son suficientes:

    Use tempdb;
    Go
    
    Create Table dbo.tblUsuarios
    (
    	ID int Not Null Identity(1, 1) Constraint PKC_dbo_tblUsuarios Primary Key Clustered
    	, Nombre nvarchar(50) Not Null
    );
    
    Create Table dbo.tblContenedores
    (
    	ID int Not Null Identity(1, 1) Constraint PKC_dbo_tblContenedores Primary Key Clustered
    	, UsuarioID int Null Constraint FK_dbo_tblContenedores_UsuarioDebeExistir Foreign Key References dbo.tblUsuarios(ID) On Update Cascade
    );
    
    Create Table dbo.tblActivos
    (
    	ID int Not Null Identity(1, 1) Constraint PKC_dbo_tblActivos Primary Key Clustered
    	, ContenedorID int Not Null Constraint FK_dbo_tblActivos_ContenedorDebeExistir Foreign Key References dbo.tblContenedores(ID) On Update Cascade
    	, UsuarioID int Null Constraint FK_dbo_tblActivos_UsuarioDebeExistir Foreign Key References dbo.tblActivos(ID) On Update Cascade
    );
    Go


    En este ejemplo, la creación de la última tabla fallará.  Esto es SQL Server 2012 y se migrará en producción a SQL Azure.


    Jose R. MCP
    Code Samples

    Según, mi corta experiencia en base de datos, es decir seguramente me equivoque, pero no estará mal el modelo?. No entiendo que la tabla C, referencie a la tabla B y a la tabla A (que ya esta referencia en B).

    No se si me explico, quieres referenciar 2 veces a la tabla A, una directa y una indirecta, y la tabla C se convertiría en algo poco estable...  Creo que con alguna tabla intermedia solucionarías el embrollo.

    Que es exactamente la idea de estas 3 tablas?

    Saludos

    lunes, 9 de julio de 2012 18:55
  • Comprendo su argumento, Gabriel, y puede ser que tenga razón, pero el modelo de negocios dice así (y por lo tanto este primer diseño trata de seguirlo al pie de la letra):

    1. Todos los activos deben pertenecer a un contenedor.
    2. Si un activo pertenece a un contenedor, entonces dicho activo automáticamente pertenece al usuario dueño del contenedor.
    3. Existe al menos un contenedor que no pertenece a ningún usuario; es un contenedor compartido.
    4. El contenedor compartido puede contener activos "anónimos" (que no le pertenecen a ningún usuario) o bien puede contener activos que pertenecen a alguien.

    Entonces el punto 2 dice que tblActivos.UsuarioID es redundante, pero el punto 4 dice que lo necesito para los activos no-anónimos dentro del contenedor compartido.

    Actualmente contemplo la posibilidad de crear un contenedor "por defecto" para cada usuario y modificar #4 de forma que el contenedor compartido (o los contenedores compartidos, si en un futuro se hacen más) únicamente almacenen activos anónimos, dejando entonces de ser "compartidos".  Pasaría de "compartido" a "lo que nadie quiere", jeje.  Sin embargo me enfrento entonces a la posibilidad de crear decenas de miles de contenedores "innecesarios" únicamente para satisfacer a SQL Server en este extraño requerimiento.  Me parece un desperdicio de bytes, especialmente porque va para Windows Azure, donde cobran por byte. :-S

    Así que por ahora me gustaría continuar explorando más posibilidades.  ¡Pongan sus ingenios a trabajar para mí! Jejeje.


    Jose R. MCP
    Code Samples

    lunes, 9 de julio de 2012 19:48
  • Después de pensarlo largo y tendido, creo que la mejor alternativa es de hecho crear contenedores "por defecto" para todos los usuarios.  No queda más que pagar el precio de los datos adicionales.  No quiero meter cosas raras y esta sería una alternativa congruente con la normalización de bases de datos.

    Jose R. MCP
    Code Samples

    • Marcado como respuesta webJose martes, 10 de julio de 2012 2:12
    martes, 10 de julio de 2012 2:12
  • Hola:

    Por aportar algo, con el modelo 'simple' que presentas, se podría enfocarse como una relación 1-n entre tblContenedor y tblActivos:

    tblUsuarios (Id, Nombre)

    tblContenedor (Id, Nombre)

    tblActivos (Id, IdContenedor Not Null, IdUsuario Null)

    1) Todos los activos pertenecen a un Contenedor. Ok.

    2) Si un Activo pertenece a un contenedor, pertenece al usuario de ese contenedor. Ok (tengo el IdUsuario en la misma tabla)

       Nota: El Usuario único de un contenedor no está en la tabla tblContenedor, es el único hijo de tblActivos.

    3) Existe al menos un contenedor sin usuario. Ok (ninguno tiene usuario, los usuarios están en la tabla tblActivos)

    4) El contenedor compartido puede tener activos anonimos. Ok (tblActivos con IdUsuario = Null)

    4bis)   El contenedor compartido puede tener activos no anonimos. Ok  (tblActivos con IdUsuario Not Null)

    Posiblemente otras restricciones del modelo impidan que éste sea válido, pero con las que disponemos es un 'posible' modelo a tener en cuenta.

    Saludos

    martes, 10 de julio de 2012 7:22
  • Gracias Focus Media, el problema con ese modelo es que para poder definir un contenedor para un usuario uno entonces debe tener al menos un activo.  Si no hay activo, no hay forma de asignar el contenedor, y eso también sería una regla de negocio:  Pueden haber contenedores vacíos.

    Jose R. MCP
    Code Samples

    martes, 10 de julio de 2012 13:24