Is it possible to predict the operations that follow a DELETE CASCADE automatically? In my software I would like to give the user a warning with details about the data that would be deleted then.
是否有可能自动预测删除级联之后的操作?在我的软件中,我想给用户一个警告,详细说明将要删除的数据。
3 个解决方案
#1
3
You can make a copy of the database and put triggers on the after delete
您可以复制数据库并将触发器放在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 ;
The log table is just a table with the following fields:
日志表只是一个包含以下字段的表:
id integer autoincrement primary key
tablename varchar(45)
table_id integer
fields varchar(6000)
delete_time timestamp
action enum('insert','update','delete')
If you do a SELECT @last_id:= max(id) FROM log
before the delete cascade on the copy.
Then you can do a SELECT * FROM log WHERE id > @last_id
and get all the rows that will be deleted in the cascade.
如果在拷贝上的删除级联之前从日志中选择@last_id:= max(id)。然后,您可以从日志中选择id > @last_id并获取在级联中删除的所有行。
After that you can use the restore_table1 to recreate the rows that were deleted in the cascade in the copy database.
之后,可以使用restore_table1重新创建复制数据库中级联中删除的行。
#2
1
I think you could use Johan's trigger solution in combination with a transaction that you roll back. This avoids both the need for a second database and for the manual restore of the deleted entries.
我认为您可以结合使用Johan的触发器解决方案和回滚的事务。这既避免了需要第二个数据库,也避免了手动恢复已删除条目。
- add the trigger and the log table
- 添加触发器和日志表
- for each attempted deletion start a transaction and delete the entries
- 对于每一个尝试的删除,启动一个事务并删除条目。
- present the information from the log to your user for approval
- 将日志中的信息显示给用户以供批准
- if the user agrees commit the transaction, otherwise rollback
- 如果用户同意提交事务,则回滚
#3
0
I wrote a very quick hack that does exactly what you need in PHP, since I wanted to do the exact same thing and haven't found any resources for that online.
我写了一个非常快速的技巧,它完全满足PHP的需要,因为我想做同样的事情,而且还没有找到任何在线的资源。
It might be too late for you, but it may help others.
对你来说可能太晚了,但它可能会帮助别人。
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!");
}
}
Imagine I have a table called "table" in the database "db" and I want to delete the one with the id 180, then I'd call:
假设我在数据库中有一个名为“table”的表,我想要删除id为180的表,然后调用:
print(get_foreign_key_deleted_data_html('db', 'table', array('id' => 180)));
and it prints a full table with all the rows and all the values that would be deleted.
它打印出一个包含所有行和所有将要删除的值的完整的表。
But as I've said, this is a very, very quick and dirty hack. I'd be glad for any bug-report (and there surely are a lot of them!).
但是就像我说的,这是一个非常,非常快而且肮脏的方法。我很高兴有任何错误报告(肯定有很多!)
#1
3
You can make a copy of the database and put triggers on the after delete
您可以复制数据库并将触发器放在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 ;
The log table is just a table with the following fields:
日志表只是一个包含以下字段的表:
id integer autoincrement primary key
tablename varchar(45)
table_id integer
fields varchar(6000)
delete_time timestamp
action enum('insert','update','delete')
If you do a SELECT @last_id:= max(id) FROM log
before the delete cascade on the copy.
Then you can do a SELECT * FROM log WHERE id > @last_id
and get all the rows that will be deleted in the cascade.
如果在拷贝上的删除级联之前从日志中选择@last_id:= max(id)。然后,您可以从日志中选择id > @last_id并获取在级联中删除的所有行。
After that you can use the restore_table1 to recreate the rows that were deleted in the cascade in the copy database.
之后,可以使用restore_table1重新创建复制数据库中级联中删除的行。
#2
1
I think you could use Johan's trigger solution in combination with a transaction that you roll back. This avoids both the need for a second database and for the manual restore of the deleted entries.
我认为您可以结合使用Johan的触发器解决方案和回滚的事务。这既避免了需要第二个数据库,也避免了手动恢复已删除条目。
- add the trigger and the log table
- 添加触发器和日志表
- for each attempted deletion start a transaction and delete the entries
- 对于每一个尝试的删除,启动一个事务并删除条目。
- present the information from the log to your user for approval
- 将日志中的信息显示给用户以供批准
- if the user agrees commit the transaction, otherwise rollback
- 如果用户同意提交事务,则回滚
#3
0
I wrote a very quick hack that does exactly what you need in PHP, since I wanted to do the exact same thing and haven't found any resources for that online.
我写了一个非常快速的技巧,它完全满足PHP的需要,因为我想做同样的事情,而且还没有找到任何在线的资源。
It might be too late for you, but it may help others.
对你来说可能太晚了,但它可能会帮助别人。
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!");
}
}
Imagine I have a table called "table" in the database "db" and I want to delete the one with the id 180, then I'd call:
假设我在数据库中有一个名为“table”的表,我想要删除id为180的表,然后调用:
print(get_foreign_key_deleted_data_html('db', 'table', array('id' => 180)));
and it prints a full table with all the rows and all the values that would be deleted.
它打印出一个包含所有行和所有将要删除的值的完整的表。
But as I've said, this is a very, very quick and dirty hack. I'd be glad for any bug-report (and there surely are a lot of them!).
但是就像我说的,这是一个非常,非常快而且肮脏的方法。我很高兴有任何错误报告(肯定有很多!)