2011-05-17 29 views
13

¿Es posible predecir las operaciones que siguen a una cascada eliminar automáticamente? En mi software, me gustaría darle al usuario una advertencia con detalles sobre los datos que se eliminarían en ese momento.¿Quieres simular una CASCADA DELETE en MySQL?

+2

Buena pregunta, estoy interesado en la respuesta también. – Alp

+0

El único enfoque razonable en el que puedo pensar actualmente es 'MOSTRAR COLUMNAS COMPLETAS DE 'y luego recorrer las tablas correspondientes ... (hardcodes fkey_names => table_names ...) – user694971

Respuesta

3

Se puede hacer una copia de la base de datos y poner disparadores en la tabla after delete

DELIMITER $$ 

CREATE TRIGGER ad_table1_each AFTER DELETE ON table1 FOR EACH ROW 
BEGIN 
    INSERT INTO log VALUES (null     /*autoinc id*/ 
     , 'table1'        /*tablename*/ 
     , old.id        /*tableid*/ 
     , concat_ws(',',old.field1,old.field2 /*CSV's of fields*/ 
     , NOW()        /*timestamp*/ 
     , 'delete');       /*what action*/ 


    REPLACE INTO restore_table1 VALUES (old.id, 
     , old.field1 
     , old.field2 
     , ...); 

END $$ 

DELIMITER ; 

El registro es sólo una tabla con los siguientes campos:

id   integer autoincrement primary key 
tablename  varchar(45) 
table_id  integer 
fields  varchar(6000) 
delete_time timestamp 
action  enum('insert','update','delete') 

Si lo hace un SELECT @last_id:= max(id) FROM log antes la cascada de eliminación en la copia.
Luego puede hacer un SELECT * FROM log WHERE id > @last_id
y obtener todas las filas que se eliminarán en la cascada.

Después de eso, puede usar restore_table1 para recrear las filas que se eliminaron en la cascada en la base de datos de copias.

1

Creo que podría usar la solución de activación de Johan en combinación con una transacción que revierte. Esto evita tanto la necesidad de una segunda base de datos como la restauración manual de las entradas eliminadas.

  • añadir el gatillo y la tabla de registro
  • para cada intento de eliminación se inicia una transacción y eliminar las entradas
  • presentar la información del registro a su usuario para su aprobación
  • si el usuario está de acuerdo en cometer el transacción, de lo contrario Rollback
+0

El único problema es, si el motor que inicia sesión también admite transacciones, que el registro también se retrotraerá, por lo que debe usar un motor no transaccional para el registro, como MyISAM. – Johan

+0

La idea de transacción es genial. De hecho, yo no usaría triggers ni un log para eso. Simularía la eliminación y luego la reversión. – user694971

+1

@ user694971: Creo que necesita el registro si desea mostrar las entradas eliminadas al usuario. Sin él, solo puede mostrar las entradas restantes, a menos que la lógica de la aplicación pueda resolverlo. – Stefan

0

escribí un corte muy rápido que hace exactamente lo que necesita en PHP, ya que quería hacer exactamente lo mismo y no he encontrado ningún recurso para esa línea.

Puede ser demasiado tarde para usted, pero puede ayudar a otros.

function get_referencing_foreign_keys ($database, $table) { 
    $query = 'SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, REFERENCED_COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_SCHEMA = "'.$database.'" AND REFERENCED_TABLE_NAME = '.esc($table); 
    $result = rquery($query); 
    $foreign_keys = array(); 
    while ($row = mysql_fetch_row($result)) { 
     $foreign_keys[] = array('database' => $row[0], 'table' => $row[1], 'column' => $row[2], 'reference_column' => $row[3]); 
    } 

    return $foreign_keys; 
} 

function get_foreign_key_deleted_data_html ($database, $table, $where) { 
    $data = get_foreign_key_deleted_data ($database, $table, $where); 

    $html = ''; 
    foreach ($data as $key => $this_data) { 
     $html .= "<h2>$key</h2>\n"; 

     $html .= "<table>\n"; 
     $i = 0; 
     foreach ($this_data as $value) { 
      if($i == 0) { 
       $html .= "\t<tr>\n"; 
       foreach ($value as $column => $column_value) { 
        $html .= "\t\t<th>".htmlentities($column)."</th>\n"; 
       } 
       $html .= "\t</tr>\n"; 
      } 
      $html .= "\t<tr>\n"; 
      foreach ($value as $column => $column_value) { 
       $html .= "\t\t<td>".htmlentities($column_value)."</td>\n"; 
      } 
      $html .= "\t</tr>\n"; 
      $i++; 
     } 
     $html .= "</table>\n"; 
    } 

    return $html; 
} 

function get_foreign_key_deleted_data ($database, $table, $where) { 
    $GLOBALS['get_data_that_would_be_deleted'] = array(); 
    $data = get_data_that_would_be_deleted($database, $table, $where); 
    $GLOBALS['get_data_that_would_be_deleted'] = array(); 
    return $data; 
} 

function get_data_that_would_be_deleted ($database, $table, $where, $recursion = 100) { 
    if($recursion <= 0) { 
     die("Deep recursion!"); 
    } 

    if($recursion == 100) { 
     $GLOBALS['get_data_that_would_be_deleted'] = array(); 
    } 

    if($table) { 
     if(is_array($where)) { 
      $foreign_keys = get_referencing_foreign_keys($database, $table); 
      $data = array(); 

      $query = 'SELECT * FROM `'.$table.'`'; 
      if(count($where)) { 
       $query .= ' WHERE 1'; 
       foreach ($where as $name => $value) { 
        $query .= " AND `$name` = ".esc($value); 
       } 
      } 
      $result = rquery($query); 

      $to_check = array(); 

      while ($row = mysql_fetch_row($result)) { 
       $new_row = array(); 
       $i = 0; 
       foreach ($row as $this_row) { 
        $field_info = mysql_fetch_field($result, $i); 
        $new_row[$field_info->name] = $this_row; 
        foreach ($foreign_keys as $this_foreign_key) { 
         if($this_foreign_key['reference_column'] == $field_info->name) { 
          $to_check[] = array('value' => $this_row, 'foreign_key' => array('table' => $this_foreign_key['table'], 'column' => $this_foreign_key['column'], 'database' => $this_foreign_key['database'])); 
         } 
        } 
        $i++; 
       } 
       $GLOBALS['get_data_that_would_be_deleted'][$table][] = $new_row; 
      } 
      foreach ($to_check as $this_to_check) { 
       if(isset($this_to_check['value']) && !is_null($this_to_check['value'])) { 
        get_data_that_would_be_deleted($database, $this_to_check['foreign_key']['table'], array($this_to_check['foreign_key']['column'] => $this_to_check['value']), $recursion - 1);; 
       } 
      } 

      $data = $GLOBALS['get_data_that_would_be_deleted']; 

      return $data; 
     } else { 
      die("\$where needs to be an array with column_name => value pairs"); 
     } 
    } else { 
     die("\$table was not defined!"); 
    } 
} 

Imagínese que tengo una tabla llamada "mesa" en la base de datos "DB" y quiero borrar el uno con el identificador 180, entonces yo diría:

print(get_foreign_key_deleted_data_html('db', 'table', array('id' => 180))); 

e imprime una tabla completa con todas las filas y todos los valores que se eliminarán.

Pero como he dicho, este es un hack muy, muy rápido y sucio. Me agradaría cualquier informe de errores (¡y seguramente hay muchos de ellos!).

Cuestiones relacionadas