2010-06-23 15 views
16

Tengo una tabla con 3 columnas:¿Cómo puedo romper la integridad referencial brevemente, dentro de una transacción, sin desactivar la restricción de clave externa?

ID, PARENT_ID, NAME 

PARENT_ID tiene una relación de clave externa con ID en la misma tabla. Esta tabla está modelando una jerarquía.

veces el ID de un registro va a cambiar. Quiero poder actualizar el registro ID, luego actualizar los registros dependientes 'PARENT_ID para apuntar al nuevo ID.

El problema es que cuando intento actualizar el ID de un registro, se rompe la integridad y falla inmediatamente.

Me doy cuenta de que podría insertar un nuevo registro con el nuevo ID, luego actualizar los elementos secundarios, luego eliminar el registro anterior, pero tenemos un montón de factores desencadenantes que se estropearían si lo hiciera.

¿Hay alguna manera de actualizar temporalmente el padre con la promesa de la actualización de los niños (obviamente que fracasaría al confirmar) sin desactivar brevemente la tecla extranjero?

+1

"A veces el ID de un registro cambiará" - es posible que desee considerar el uso de una http://en.wikipedia.org/wiki/Surrogate_key para que esto no suceda ... –

Respuesta

19

Lo que quiere es un 'deferrable constraint'.

Puede elegir entre los dos tipos de restricciones diferibles, 'INICIALMENTE INMEDIATA' e 'INICIALMENTE DIFERIDA' para controlar el comportamiento predeterminado, ya sea que la base de datos deba revisar la restricción de forma predeterminada después de cada instrucción, o restricciones al final de la transacción.

+0

lo siento, el enlace debe corregirse ahora – Chi

+0

El enlace está roto. –

+0

Los enlaces funcionan ahora, pero es increíblemente lento de cargar (en mi conexión de alta velocidad). La única preocupación es a qué versiones de Oracle esto se aplica, no se puede decir desde el enlace. –

1

Debe usar una restricción diferible (vea la respuesta de Chi).
De lo contrario, con el fin de añadir un valor que fallará la restricción de clave externa, ya sea que usted tiene que desactivar o caer & volver a crear la restricción de clave externa.

Situaciones como éstas emplean una clave sustituta que puede ser alterado por los usuarios como sea necesario, sin afectar a la integridad referencial. Para ampliar esta idea, en la actualidad la configuración es:

  • ID (pk)
  • PARENT_ID (clave externa, la columna referencias ID - por lo que es uno mismo de referencia)

..y el negocio las reglas son que la identificación puede cambiar. Lo cual es fundamentalmente malo desde una perspectiva de diseño: la clave principal es inmutable, única y no puede ser nula. Así que la solución a la situación en la que está construyendo su modelo de datos es utilizar:

  • ID (pk)
  • PARENT_ID (clave externa, la columna referencias ID - por lo que es uno mismo de referencia)
  • SURROGATE_KEY (restricción única)

el SURROGATE_KEY es la columna que soporta el cambio sin afectar a la integridad referencial - la relación padre-hijo & está intacto. Esto significa que un usuario puede ajustar la clave sustituta a sus corazones delicia sin necesidad de restricciones diferidas, activar/desactivar o gota/recrear las claves foráneas, en la actualización CASCADE ...

Por regla general, en el modelado de datos que NUNCA muestran los valores de las principales claves para el usuario debido a situaciones como estas. Por ejemplo, tengo un cliente que quiere que su número de trabajo cambie a comienzos de año, con el año al comienzo del número (IE: 201000001 sería el primer trabajo creado en 2010).¿Qué sucede cuando el cliente vende la empresa y el nuevo propietario necesita un esquema diferente para su contabilidad? O bien, ¿qué pasa si la numeración no se puede mantener durante la transición a un proveedor de base de datos diferente?

+0

Consideré agregar un sustituto Clave, pero luego la integridad de mi tabla depende de tener una serie de factores desencadenantes para garantizar que nadie rompa nada, en lugar de una única restricción de clave externa ... parece una frustrante contrapartida. –

+0

@Renderln: Lo aborrezco también, pero decirle a los usuarios "¡NO!" es lo que me impide ser un analista de negocios =) –

+0

@OMG Ponies: tienes el nombre más memorable en todos los stackoverflow. Cada vez que lo veo, me río y me siento mejor acerca de mi día. –

9

Respondió más lento que Chi, pero consideró que sería bueno incluir una muestra de código para que la respuesta se encuentre en SO.

Como Chi contestó, las restricciones diferibles lo hacen posible.

SQL> drop table t; 

Table dropped. 

SQL> create table T (ID number 
    2  , parent_ID number null 
    3  , name varchar2(40) not null 
    4  , constraint T_PK primary key (ID) 
    5  , constraint T_HIREARCHY_FK foreign key (parent_ID) 
    6   references T(ID) deferrable initially immediate); 

Table created. 

SQL> insert into T values (1, null, 'Big Boss'); 

1 row created. 

SQL> insert into T values (2, 1, 'Worker Bee'); 

1 row created. 

SQL> commit; 

Commit complete. 

SQL> -- Since initially immediate, the following statement will fail: 
SQL> update T 
    2 set ID = 1000 
    3 where ID = 1; 
update T 
* 
ERROR at line 1: 
ORA-02292: integrity constraint (S.T_HIREARCHY_FK) violated - child record found 


SQL> set constraints all deferred; 

Constraint set. 

SQL> update T 
    2 set ID = 1000 
    3 where ID = 1; 

1 row updated. 

SQL> update T 
    2 set parent_ID = 1000 
    3 where parent_ID = 1; 

1 row updated. 

SQL> commit; 

Commit complete. 

SQL> select * from T; 

     ID PARENT_ID NAME 
---------- ---------- ---------------------------------------- 
     1000   Big Boss 
     2  1000 Worker Bee 

SQL> -- set constraints all deferred during that transaction 
SQL> -- and the transaction has commited, the next 
SQL> -- statement will fail 
SQL> update T 
    2 set ID = 1 
    3 where ID = 1000; 
update T 
* 
ERROR at line 1: 
ORA-02292: integrity constraint S.T_HIREARCHY_FK) violated - child record found 

creo, pero no pudo encontrar la referencia, que DEFERRABILITY se define durante la creación de la restricción y no se puede modificar más tarde. El valor predeterminado no es diferible. Para cambiar a restricciones diferibles, tendrá que hacer un drop de una sola vez y agregar restricción. (Debidamente programada, controlada, etc.)

SQL> drop table t; 

Table dropped. 

SQL> create table T (ID number 
    2  , parent_ID number null 
    3  , name varchar2(40) not null 
    4  , constraint T_PK primary key (ID) 
    5  , constraint T_HIREARCHY_FK foreign key (parent_ID) 
    6   references T(ID)); 

Table created. 

SQL> alter table T drop constraint T_HIREARCHY_FK; 

Table altered. 

SQL> alter table T add constraint T_HIREARCHY_FK foreign key (parent_ID) 
    2  references T(ID) deferrable initially deferred; 

Table altered. 
1

Si esto fuera cualquier otra base de datos de Oracle, además, se puede declarar la clave externa con ON UPDATE CASCADE. Luego, si cambia la identificación de un padre, propagaría el cambio de forma atómica al parent_id del niño.

Desafortunadamente, Oracle implementa eliminaciones en cascada pero no actualizaciones en cascada.

(Esta respuesta es a título indicativo, ya que en realidad no soluciona el problema.)

+0

Agradable además. Aunque no estoy de acuerdo con su palabra "Desafortunadamente", ya que creo que debería desalentarse. Las claves primarias deben ser inmutables. –

+0

@Rob van Wijk: Estoy de acuerdo en que * pseudokeys * debe ser inmutable. ¿Qué pasa con las claves primarias naturales? –

+1

La única clave primaria natural aceptable para mí es una que nunca cambiará. Eso es porque no quiero molestarme en actualizar la mitad del modelo de datos completo, con el tiempo de inactividad asociado. Eso es difícil de explicar al negocio. Y, las llaves naturales inmutables son raras. Incluso algunas claves naturales que parecen inmutables a primera vista, cambian. Solo piense en códigos de país (recuerde Yugoslavia, Zaire), códigos de moneda (Euro), números de cuenta bancaria (cambiando de 9 dígitos a 10) y así sucesivamente. Un ejemplo de una clave natural aceptable que puedo pensar: la columna de fecha de una tabla de tiempo DW. –

7

El consejo común con situaciones como esta es emplear deferrable constraints. Sin embargo, creo que estas situaciones casi siempre son un fracaso de la lógica de la aplicación o el modelo de datos. Por ejemplo, la inserción de un registro hijo y un registro primario en la misma transacción puede ser un problema si lo ejecutamos como dos declaraciones:

Mi datos de prueba:

SQL> select * from t23 order by id, parent_id 
    2/

     ID PARENT_ID NAME 
---------- ---------- ------------------------------ 
     110   parent 1 
     111   parent 2 
     210  110 child 0 
     220  111 child 1 
     221  111 child 2 
     222  111 child 3 

6 rows selected. 

SQL> 

La manera incorrecta de hacer las cosas:

SQL> insert into t23 (id, parent_id, name) values (444, 333, 'new child') 
    2/
insert into t23 (id, parent_id, name) values (444, 333, 'new child') 
* 
ERROR at line 1: 
ORA-02291: integrity constraint (APC.T23_T23_FK) violated - parent key not 
found 


SQL> insert into t23 (id, parent_id, name) values (333, null, 'new parent') 
    2/

1 row created. 

SQL> 

Sin embargo, Oracle soporta una synatx INSERT de varias mesas, que nos permite insertar los registros primarios y secundarios en la misma instrucción, obviando así la necesidad de restricciones diferibles:

La situación en la que se encuentra es similar: desea actualizar la clave principal del registro principal pero no puede debido a la existencia de registros secundarios: Y no puede actualizar los registros secundarios porque allí no hay una clave padre Catch-22:

SQL> update t23 
    2  set id = 555 
    3 where id = 111 
    4/
update t23 
* 
ERROR at line 1: 
ORA-02292: integrity constraint (APC.T23_T23_FK) violated - child record found 


SQL> update t23 
    2  set parent_id = 555 
    3 where parent_id = 111 
    4/
update t23 
* 
ERROR at line 1: 
ORA-02291: integrity constraint (APC.T23_T23_FK) violated - parent key not 
found 


SQL> 

Una vez más, la solución es hacerlo en una sola instrucción:

SQL> update t23 
    2  set id = decode(id, 111, 555, id) 
    3   , parent_id = decode(parent_id, 111, 555, parent_id) 
    4 where id = 111 
    5  or parent_id = 111 
    6/

4 rows updated. 

SQL> select * from t23 order by id, parent_id 
    2/

     ID PARENT_ID NAME 
---------- ---------- ------------------------------ 
     110   parent 1 
     210  110 child 0 
     220  555 child 1 
     221  555 child 2 
     222  555 child 3 
     333   new parent 
     444  333 new child 
     555   parent 2 

8 rows selected. 

SQL> 

La sintaxis de la instrucción UPDATE es un poco torpe, pero por lo general son kludges.El punto es que no deberíamos tener que actualizar las columnas de clave principal muy a menudo. De hecho, como la inmutabilidad es una de las características de la "clave primaria", no deberíamos tener que actualizarlas en absoluto. Necesitarlo es una falla del modelo de datos. Una forma de evitar tales fallas es usar una clave primaria sintética (sustituta), y simplemente aplicar la unicidad de la clave natural (también conocida como negocio) con una restricción única.

Entonces, ¿por qué Oracle ofrece restricciones diferibles? Son útiles cuando realizamos migraciones de datos o cargas masivas de datos. Nos permiten limpiar datos en la base de datos sin tablas intermedias. Realmente no deberíamos necesitarlos para las tareas de aplicación regulares.

+0

Gracias por publicar esto. Probé la actualización individual tanto para padres como para niños, y falló. Pensé que no funcionaría. Evidentemente hubo un error en mi prueba. La tuya funciona bien. –

3

Las recomendaciones para utilizar una clave sustituta son excelentes, IMO.

Más generalmente, el problema con esta tabla es que carece de una clave principal. Recordemos que una clave primaria debe ser tres cosas:

  1. únicos
  2. no nulo
  3. que no cambian

bases de datos que conozco cumplir (1) y (2), pero no creen que hacen cumplir (3), lo cual es desafortunado. Y eso es lo que te está pateando en el trasero: si cambias tu "clave principal", debes buscar todas las referencias a ese campo clave y hacer alteraciones equivalentes si no quieres romper la integridad. La solución, como han dicho otros, es tener una verdadera clave primaria, una que sea única, no nula y que no cambie.

Hay razones para todas estas pequeñas reglas. Esta es una gran oportunidad para comprender la parte "inmutable" de las reglas clave principales.

Comparte y disfruta.

+0

Sí, y las llaves compuestas y naturales son imposibles, y el conejito de Pascua comió mi tarea. – orbfish

Cuestiones relacionadas