Supongamos que tenemos una tabla que tiene una restricción de clave externa para sí misma, como tal:
CREATE TABLE Foo
(FooId BIGINT PRIMARY KEY,
ParentFooId BIGINT,
FOREIGN KEY([ParentFooId]) REFERENCES Foo ([FooId]) )
INSERT INTO Foo (FooId, ParentFooId)
VALUES (1, NULL), (2, 1), (3, 2)
UPDATE Foo SET ParentFooId = 3 WHERE FooId = 1
Esta tabla tendrá los siguientes registros:
FooId ParentFooId
----- -----------
1 3
2 1
3 2
Hay casos en los que este tipo de diseño podría tener sentido (por ejemplo, la típica relación "empleado-y-jefe-empleado"), y en cualquier caso: estoy en una situación en la que tengo esto en mi esquema.
Desafortunadamente, este tipo de diseño permite circularidad en los registros de datos, como se muestra en el ejemplo anterior.
Mi pregunta entonces es:
- ¿Es posible escribir una restricción que verifique esto? y
- ¿Es factible escribir una restricción que verifique esto? (si es necesario solo hasta una cierta profundidad)
Para la parte (2) de esta pregunta, puede ser relevante mencionar que espero solo cientos o quizás en algunos casos miles de registros en mi tabla, normalmente no anidados a más de 5 a 10 niveles.
PD. MS SQL Server 2008
Actualización 14 de marzo de 2012
Hubo varias buenas respuestas. Ahora he aceptado el que me ayudó a comprender la posibilidad / factibilidad mencionada. Sin embargo, hay varias otras respuestas excelentes, algunas con sugerencias de implementación, por lo que si aterrizó aquí con la misma pregunta, eche un vistazo a todas las respuestas;)
fuente
HIERARCHYID
que parece ser una implementación MSSQL2008 nativa del modelo de conjunto anidado.He visto 2 formas principales de hacer cumplir esto:
1, la antigua manera:
La columna FooHierarchy contendría un valor como este:
Donde los números se asignan a la columna FooId. A continuación, debería exigir que la columna Jerarquía termine con "| id" y que el resto de la cadena coincida con la FooHieratchy del PADRE.
2, la NUEVA forma:
SQL Server 2008 tiene un nuevo tipo de datos llamado HierarchyID , que hace todo esto por usted.
Opera en el mismo principio que en el modo ANTIGUO, pero es manejado eficientemente por SQL Server, y es adecuado para usarse como REEMPLAZO para su columna "ParentID".
fuente
HIERARCHYID
impide la creación de bucles de jerarquía?Es posible: puede invocar un UDF escalar desde su restricción CHECK, y puede detectar ciclos de cualquier longitud. Desafortunadamente, este enfoque es extremadamente lento y poco confiable: puede tener falsos positivos y falsos negativos.
En cambio, usaría el camino materializado.
Otra forma de evitar ciclos es tener un CHECK (ID> ParentID), que probablemente tampoco sea muy factible.
Otra forma de evitar ciclos es agregar dos columnas más, LevelInHierarchy y ParentLevelInHierarchy, hacer que (ParentID, ParentLevelInHierarchy) haga referencia a (ID, LevelInHierarchy) y tener un CHECK (LevelInHierarchy> ParentLevelInHierarchy).
fuente
Creo que es posible:
Podría haber pasado algo por alto (lo siento, no puedo probarlo a fondo), pero parece funcionar.
fuente
Aquí hay otra opción: un disparador que permite actualizaciones de varias filas y no impone ciclos. Funciona atravesando la cadena ancestral hasta que encuentra un elemento raíz (con NULL padre), lo que demuestra que no hay ciclo. Está limitado a 10 generaciones ya que, por supuesto, un ciclo es interminable.
Solo funciona con el conjunto actual de filas modificadas, por lo que siempre que las actualizaciones no toquen una gran cantidad de elementos muy profundos en la tabla, el rendimiento no debería ser tan malo. Tiene que subir toda la cadena por cada elemento, por lo que tendrá un impacto en el rendimiento.
Un disparador verdaderamente "inteligente" buscaría ciclos directamente verificando si un artículo se alcanzaba a sí mismo y luego rescatando. Sin embargo, esto requiere verificar el estado de todos los nodos encontrados previamente durante cada ciclo y, por lo tanto, toma un ciclo WHILE y más codificación de la que quería hacer en este momento. Esto no debería ser realmente más costoso porque la operación normal sería no tener ciclos y en este caso será más rápido trabajar solo con la generación anterior en lugar de todos los nodos anteriores durante cada ciclo.
Me encantaría la opinión de @AlexKuznetsov o de cualquier otra persona sobre cómo le iría en el aislamiento de instantáneas. Sospecho que no sería muy bueno, pero me gustaría entenderlo mejor.
Actualizar
Descubrí cómo evitar una unión adicional a la tabla Inserted. Si alguien ve una mejor manera de hacer GROUP BY para detectar aquellos que no contienen NULL, hágamelo saber.
También agregué un interruptor para LEER COMPROMETIDO si la sesión actual está en el nivel de AISLAMIENTO INSTANTÁNEO. Esto evitará inconsistencias, aunque desafortunadamente causará un mayor bloqueo. Eso es inevitable para la tarea en cuestión.
fuente
Si sus registros están anidados en más de 1 nivel, una restricción no funcionará (supongo que quiere decir, por ejemplo, el registro 1 es el padre del registro 2 y el registro 3 es el padre del registro 1). La única forma de hacerlo sería en el código principal o con un activador, pero si está mirando una tabla grande y varios niveles, esto sería bastante intenso.
fuente