Mysql 对于未找到,设置循环_done=TRUE; 设置行_deleted=0; 设置循环_done=FALSE; 开放游标; 重复 将游标提取到表\u name\u; 调用dofat\u从\u表中删除\u孤儿\u(表\u名称\u); 设置行数=行数+行数(); 直到循环结束重复; 关闭游标; 结束// 创建过程从所有表格中删除孤立表格() 开始 调用dofat_store_tables_targeted_by_foreign_key(); 重复 从所有表中调用dofat\u delete\u olivers\u(@rows\u deleted); 直到@rows\u deleted=0结束重复; 结束// 定界符;

Mysql 对于未找到,设置循环_done=TRUE; 设置行_deleted=0; 设置循环_done=FALSE; 开放游标; 重复 将游标提取到表\u name\u; 调用dofat\u从\u表中删除\u孤儿\u(表\u名称\u); 设置行数=行数+行数(); 直到循环结束重复; 关闭游标; 结束// 创建过程从所有表格中删除孤立表格() 开始 调用dofat_store_tables_targeted_by_foreign_key(); 重复 从所有表中调用dofat\u delete\u olivers\u(@rows\u deleted); 直到@rows\u deleted=0结束重复; 结束// 定界符;,mysql,stored-procedures,garbage-collection,Mysql,Stored Procedures,Garbage Collection,顺便说一句,这个练习教会了我一些事情,使使用MySQL存储过程编写如此复杂的代码成为一项令人沮丧的工作。我提到所有这些只是因为它们可能会帮助你,或者一个好奇的未来读者,理解上面代码中疯狂的风格选择 对于简单的事情,非常冗长的语法和样板文件。例如 需要在不同的行上声明和分配 需要在过程定义周围设置分隔符 需要使用PREPARE/EXECUTE组合来使用动态SQL) 完全缺乏: 从CONCAT(…)制备stmt是一个语法错误,而@foo=CONCAT(…);从@foo处准备stmt不可用 e

顺便说一句,这个练习教会了我一些事情,使使用MySQL存储过程编写如此复杂的代码成为一项令人沮丧的工作。我提到所有这些只是因为它们可能会帮助你,或者一个好奇的未来读者,理解上面代码中疯狂的风格选择

  • 对于简单的事情,非常冗长的语法和样板文件。例如
    • 需要在不同的行上声明和分配
    • 需要在过程定义周围设置分隔符
    • 需要使用
      PREPARE
      /
      EXECUTE
      组合来使用动态SQL)
  • 完全缺乏:
    • 从CONCAT(…)制备stmt
      是一个语法错误,而
      @foo=CONCAT(…);从@foo处准备stmt不可用
    • executestmtusing@foo
      可以,但是
      executestmtusingfoo
      其中
      foo
      是过程变量,是语法错误
    • SELECT
      语句和最后一个语句是SELECT语句的过程都返回一个结果集,但几乎所有您希望对结果集执行的操作(如循环或检查某个内容是否在
      中)都只能针对
      SELECT
      语句,不是
      调用
      语句
    • 可以将会话变量作为输出参数传递给存储过程,但不能将存储过程变量作为输出参数传递给存储过程
  • 完全任意的限制和奇怪的行为让你蒙蔽了双眼:
    • 函数中不允许动态SQL,仅在过程中允许
    • 使用光标从列中提取到同名的过程变量时,始终会将变量设置为
      NULL
      ,但不会引发任何警告或错误
  • 缺乏在过程之间干净地传递结果集的能力

    结果集是SQL中的基本类型;它们是
    SELECT
    s返回的内容,当从应用层使用SQL时,您可以将它们视为对象。但是在MySQL存储过程中,不能将它们分配给变量,也不能将它们从一个存储过程传递到另一个存储过程。如果确实需要此功能,则必须让一个存储过程将结果集写入临时表,以便另一个存储过程可以读取它

  • 古怪和不熟悉的结构和习惯用法:
    • 为变量赋值的三种等效方法—
      设置foo=bar
      选择foo=bar
      将bar选择为foo
    • 您希望对所有状态使用过程变量,并避免使用会话变量,原因与在普通编程语言中避免全局变量相同。但事实上,您需要在任何地方使用会话变量,因为许多语言构造(如OUT params和
      EXECUTE
      )都不接受任何其他类型的变量
    • 使用光标在结果集上循环的语法看起来很奇怪

尽管存在这些障碍,但如果您有决心,您仍然可以将这样的小程序与存储过程拼凑在一起。

因为我有一些奇怪的SQL语法错误,下面是一个使用公认答案和Groovy中的SQL的解决方案。使用
orphanedNodeStatistics()
获取每个表中要删除的节点数,
dumpOrphanedNodes(String tableName)
转储要删除的节点的PKs,
deleteOrphanedNodes(String tableName)
删除它们

要删除它们,请遍历由
tableTargetedByForeignKeys()返回的集合

导入groovy.sql.sql
类孤雌醇{
Sql;
字符串模式;
设置表targetedbyforeignkeys(){
def查询=“”\
选择引用的\u表\u名称
来自信息\u SCHEMA.key\u列\u用法
其中引用的_表_架构=?
'''
def结果=新树集()
eachRow(查询,[schema]){row->
结果
条件
结果
一块一块{
打印(String.format(“%16d”,长度相同))
}
println()
}
}
}
int countorbenedNodes(字符串表名){
def query=“”\
选择计数(*)
来自${tableName}
其中${conditionsToFindOrphans(tableName)}
“.toString()
int结果;
sql.eachRow(查询){row->
结果=行[0]
}
返回结果
}
int deleteOrientedNodes(字符串表名){
def query=“”\
删除
来自${tableName}
其中${conditionsToFindOrphans(tableName)}
“.toString()
int result=sql.execute(查询)
返回结果
}
无效孤立节点统计(){
def tableNames=tablesTargetedByForeignKeys()
for(字符串tableName:tableNames){
int n=countorbenedNodes(表名)
println(String.format(“%8d%s”,n,tableName))
}
}
}

()

这个问题展示了如何收集FK相关信息:这个问题解释了如何删除与单个FK关系相关的所有行:存储过程中的动态SQL:让我以较短的形式重申规范,以确保我理解它:目标是定位任何外键约束引用的每个表,然后在每一个表中确定没有从任何地方引用的每一行并将其删除?反复这样做直到
SELECT CONCAT(
 'SELECT ', GROUP_CONCAT(DISTINCT CONCAT(K.CONSTRAINT_NAME, '.', P.COLUMN_NAME,
  ' AS `', P.TABLE_SCHEMA, '.', P.TABLE_NAME, '.', P.COLUMN_NAME, '`') ORDER BY P.ORDINAL_POSITION), ' ',
 'FROM ', K.TABLE_SCHEMA, '.', K.TABLE_NAME, ' AS ', K.CONSTRAINT_NAME, ' ',
 'LEFT OUTER JOIN ', K.REFERENCED_TABLE_SCHEMA, '.', K.REFERENCED_TABLE_NAME, ' AS ', K.REFERENCED_TABLE_NAME, ' ',
 ' ON (', GROUP_CONCAT(CONCAT(K.CONSTRAINT_NAME, '.', K.COLUMN_NAME) ORDER BY K.ORDINAL_POSITION),
 ') = (', GROUP_CONCAT(CONCAT(K.REFERENCED_TABLE_NAME, '.', K.REFERENCED_COLUMN_NAME) ORDER BY K.ORDINAL_POSITION), ') ',
 'WHERE ', K.REFERENCED_TABLE_NAME, '.', K.REFERENCED_COLUMN_NAME, ' IS NULL;'
 ) AS _SQL
 FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE K
 INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE P
 ON (K.TABLE_SCHEMA, K.TABLE_NAME) = (P.TABLE_SCHEMA, P.TABLE_NAME)
 AND P.CONSTRAINT_NAME = 'PRIMARY'
 WHERE K.REFERENCED_TABLE_NAME IS NOT NULL
 GROUP BY K.CONSTRAINT_NAME;
SELECT CONCAT(
 'SELECT ', GROUP_CONCAT(CONCAT(K.REFERENCED_TABLE_NAME, '.', K.REFERENCED_COLUMN_NAME) ORDER BY K.ORDINAL_POSITION), ' ',

 'FROM ', K.REFERENCED_TABLE_SCHEMA, '.', K.REFERENCED_TABLE_NAME, ' AS ', K.REFERENCED_TABLE_NAME, ' ',
 'LEFT OUTER JOIN ', K.TABLE_SCHEMA, '.', K.TABLE_NAME, ' AS ', K.CONSTRAINT_NAME, ' ',
 ' ON (', GROUP_CONCAT(CONCAT(K.CONSTRAINT_NAME, '.', K.COLUMN_NAME) ORDER BY K.ORDINAL_POSITION),
 ') = (', GROUP_CONCAT(CONCAT(K.REFERENCED_TABLE_NAME, '.', K.REFERENCED_COLUMN_NAME) ORDER BY K.ORDINAL_POSITION), ') ',
 'WHERE ', K.CONSTRAINT_NAME, '.', K.COLUMN_NAME, ' IS NULL;'
 ) AS _SQL
 FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE K
 INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE P
 ON (K.TABLE_SCHEMA, K.TABLE_NAME) = (P.TABLE_SCHEMA, P.TABLE_NAME)
 AND P.CONSTRAINT_NAME = 'PRIMARY'
 WHERE K.REFERENCED_TABLE_NAME IS NOT NULL
 GROUP BY K.CONSTRAINT_NAME;
CALL delete_orphans_from_all_tables();
delimiter //
CREATE PROCEDURE dofat_store_tables_targeted_by_foreign_keys ()
BEGIN
    -- This procedure creates a temporary table called TargetTableNames
    -- containing the names of all tables that are the target of any foreign
    -- key relation.

    SET @db_name = DATABASE();

    DROP TEMPORARY TABLE IF EXISTS TargetTableNames;
    CREATE TEMPORARY TABLE TargetTableNames (
        table_name VARCHAR(255) NOT NULL
    );

    PREPARE stmt FROM 
   'INSERT INTO TargetTableNames(table_name)
    SELECT DISTINCT referenced_table_name
    FROM INFORMATION_SCHEMA.key_column_usage
    WHERE referenced_table_schema = ?';

    EXECUTE stmt USING @db_name;
END//

CREATE PROCEDURE dofat_deletion_clause_for_table(
    IN table_name VARCHAR(255), OUT result text
)
DETERMINISTIC
BEGIN
    -- Given a table Foo, where Foo.col1 is referenced by Bar.col1, and
    -- Foo.col2 is referenced by Qwe.col3, this will return a string like:
    --
    -- NOT (Foo.col1 IN (SELECT col1 FROM BAR) <=> 1) AND
    -- NOT (Foo.col2 IN (SELECT col3 FROM Qwe) <=> 1)
    --
    -- This is used by dofat_delete_orphans_from_table to target only orphaned
    -- rows.
    --
    -- The odd-looking `NOT (x IN y <=> 1)` construct is used in favour of the
    -- more obvious (x NOT IN y) construct to handle nulls properly; note that
    -- (x NOT IN y) will evaluate to NULL if either x is NULL or if x is not in
    -- y and *any* value in y is NULL.

    SET @db_name = DATABASE();
    SET @table_name = table_name;

    PREPARE stmt FROM 
   'SELECT GROUP_CONCAT(
        CONCAT(
            \'NOT (\', @table_name, \'.\', referenced_column_name, \' IN (\',
            \'SELECT \', column_name, \' FROM \', table_name, \')\',
            \' <=> 1)\'
        )
        SEPARATOR \' AND \'
    ) INTO @result
    FROM INFORMATION_SCHEMA.key_column_usage 
    WHERE
        referenced_table_schema = ?
        AND referenced_table_name = ?';
    EXECUTE stmt USING @db_name, @table_name;

    SET result = @result;
END//

CREATE PROCEDURE dofat_delete_orphans_from_table (table_name varchar(255))
BEGIN
    -- Takes as an argument the name of a table that is the target of at least
    -- one foreign key.
    -- Deletes from that table all rows that are not currently referenced by
    -- any foreign key.

    CALL dofat_deletion_clause_for_table(table_name, @deletion_clause);
    SET @stmt = CONCAT(
       'DELETE FROM ', @table_name,
       ' WHERE ', @deletion_clause
    );

    PREPARE stmt FROM @stmt;
    EXECUTE stmt;
END//

CREATE PROCEDURE dofat_delete_orphans_from_all_tables_iter(
    OUT rows_deleted INT
)
BEGIN    
    -- dofat_store_tables_targeted_by_foreign_keys must be called before this
    -- will work.
    --
    -- Loops ONCE over all tables that are currently referenced by a foreign
    -- key. For each table, deletes all rows that are not currently referenced.
    -- Note that this is not guaranteed to leave all tables without orphans,
    -- since the deletion of rows from a table late in the sequence may leave
    -- rows from a table early in the sequence orphaned.
    DECLARE loop_done BOOL;

    -- Variable name needs to differ from the column name we use to populate it
    -- because of bug http://bugs.mysql.com/bug.php?id=28227
    DECLARE table_name_ VARCHAR(255); 

    DECLARE curs CURSOR FOR SELECT table_name FROM TargetTableNames;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET loop_done = TRUE;

    SET rows_deleted = 0;
    SET loop_done = FALSE;

    OPEN curs;
    REPEAT
        FETCH curs INTO table_name_;
        CALL dofat_delete_orphans_from_table(table_name_);
        SET rows_deleted = rows_deleted + ROW_COUNT();
    UNTIL loop_done END REPEAT;
    CLOSE curs;
END//

CREATE PROCEDURE delete_orphans_from_all_tables ()
BEGIN    
    CALL dofat_store_tables_targeted_by_foreign_keys();
    REPEAT
        CALL dofat_delete_orphans_from_all_tables_iter(@rows_deleted);
    UNTIL @rows_deleted = 0 END REPEAT;
END//
delimiter ;
import groovy.sql.Sql

class OrphanNodesTool {

    Sql sql;
    String schema;

    Set<String> tablesTargetedByForeignKeys() {
        def query = '''\
SELECT referenced_table_name
FROM INFORMATION_SCHEMA.key_column_usage
WHERE referenced_table_schema = ?
'''
        def result = new TreeSet()
        sql.eachRow( query, [ schema ] ) { row ->
            result << row[0]
        }
        return result
    }

    String conditionsToFindOrphans( String tableName ) {
        List<String> conditions = []

        def query = '''\
SELECT referenced_column_name, column_name, table_name
FROM INFORMATION_SCHEMA.key_column_usage
WHERE referenced_table_schema = ?
    AND referenced_table_name = ?
'''
        sql.eachRow( query, [ schema, tableName ] ) { row ->
            conditions << "NOT (${tableName}.${row.referenced_column_name} IN (SELECT ${row.column_name} FROM ${row.table_name}) <=> 1)"
        }

        return conditions.join( '\nAND ' )
    }

    List<Long> listOrphanedNodes( String tableName ) {
        def query = """\
SELECT ${tableName}.${tableName}_ID
FROM ${tableName}
WHERE ${conditionsToFindOrphans(tableName)}
""".toString()

        def result = []
        sql.eachRow( query ) { row ->
            result << row[0]
        }
        return result
    }

    void dumpOrphanedNodes( String tableName ) {
        def pks = listOrphanedNodes( tableName )
        println( String.format( "%8d %s", pks.size(), tableName ) )
        if( pks.size() < 10 ) {
            pks.each {
                println( String.format( "%16d", it as long ) )
            }
        } else {
            pks.collate( 20 ) { chunk ->
                chunk.each {
                    print( String.format( "%16d ", it as long ) )
                }
                println()
            }
        }
    }

    int countOrphanedNodes( String tableName ) {
        def query = """\
SELECT COUNT(*)
FROM ${tableName}
WHERE ${conditionsToFindOrphans(tableName)}
""".toString()

        int result;
        sql.eachRow( query ) { row ->
                result = row[0]
        }
        return result
    }

    int deleteOrphanedNodes( String tableName ) {
        def query = """\
DELETE
FROM ${tableName}
WHERE ${conditionsToFindOrphans(tableName)}
""".toString()

        int result = sql.execute( query )
        return result
    }

    void orphanedNodeStatistics() {
        def tableNames = tablesTargetedByForeignKeys()
        for( String tableName : tableNames ) {
            int n = countOrphanedNodes( tableName )
            println( String.format( "%8d %s", n, tableName ) )
        }
    }
}