草庐IT

mysql - 如何在 MySQL 中递归删除行(即也删除外键链接的行)?

coder 2023-10-26 原文

我在我的 MySQL 数据库中使用约束。但是现在,当我试图删除一个与其他条目具有外键关系的条目时,这让我很头疼。我总是收到此错误:

Cannot delete or update a parent row: a foreign key constraint fails

我能否向 delete 语句传递任何参数或任何内容,以便它递归删除与我要删除的行具有外键关系的所有行?

最佳答案

更新:现在已将其制作成博客文章:https://stevettt.blogspot.co.uk/2018/02/how-to-automate-deletion-of-rows-in.html


我编写了一个存储过程,它将递归地从所有外键-链接表中删除(无需关闭外键检查或打开级联删除)。实现具有一定的复杂性,但可以被视为“黑盒”:只需指定模式(数据库)名称、表和 WHERE 子句以限制要删除的记录,剩下的就由它来完成。

演示

Rextester 在线演示:http://rextester.com/MDMRA15991

SQL

-- ------------------------------------------------------------------------------------
-- USAGE
-- ------------------------------------------------------------------------------------
-- CALL delete_recursive(<schema name>, <table name>, <WHERE clause>, <delete flag>);
-- where:
-- <schema name> is the name of the MySQL schema
-- <table name> is the name of the base table to delete records from
-- <WHERE clase> is a SQL WHERE clause to filter which records that are to be deleted
-- <delete flag> is either TRUE or FALSE: If TRUE, the records *will* be deleted.
--               If FALSE, the SQL will be output without actually deleting anything.
-- Example:
-- CALL delete_recursive('mydb', 'mytable', 'WHERE mypk IN (1, 2, 3)', TRUE);
DROP PROCEDURE IF EXISTS delete_recursive;
DELIMITER //
CREATE PROCEDURE delete_recursive(schema_name VARCHAR(64),
                                  tbl_name VARCHAR(64),
                                  where_clause TEXT,
                                  do_delete BIT)
BEGIN
  DECLARE next_schema_name, next_tbl_name VARCHAR(64);
  DECLARE from_clause, next_where_clause, next_col_names, ref_col_names TEXT;
  DECLARE done INT DEFAULT FALSE;
  DECLARE cursor1 CURSOR FOR
    SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAMES, REF_COLUMN_NAMES FROM temp_kcu;
  DECLARE cursor2 CURSOR FOR
    SELECT table_schema, table_name, where_sql FROM temp_deletes ORDER BY id;
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

  -- Set maximum recursion depth
  SET @@SESSION.max_sp_recursion_depth = 255;

  -- Increment current recursion depth since the stored procedure has been entered.
  SET @recursion_depth = IFNULL(@recursion_depth + 1, 0);

  -- Create temporary table for storing the deletes if it doesn't already exist
  IF @recursion_depth = 0 THEN
    DROP TEMPORARY TABLE IF EXISTS temp_deletes;
    CREATE TEMPORARY TABLE temp_deletes (
      id INT NOT NULL AUTO_INCREMENT,
      table_schema VARCHAR(64),
      table_name VARCHAR(64),
      where_sql TEXT,
      Notes TEXT,
      PRIMARY KEY(id)
    );
  END IF;

  -- Construct FROM clause (including the WHERE clause) for this table.
  SET from_clause = 
    CONCAT(' FROM ', schema_name, '.', tbl_name, ' WHERE ', where_clause);

  -- Find out whether there are any foreign keys to this table
  SET @query = CONCAT('SELECT COUNT(*) INTO @count', from_clause);
  PREPARE stmt FROM @query;
  EXECUTE stmt;
  DEALLOCATE PREPARE stmt;

  IF @count > 0 THEN
    -- There are foriegn keys to this table so all linked rows must be deleted first:
    -- Firstly, fill a temporary table with the foreign key metadata.
    DROP TEMPORARY TABLE IF EXISTS temp_kcu;
    SET @query = CONCAT(
      'CREATE TEMPORARY TABLE temp_kcu AS ',
      'SELECT TABLE_SCHEMA, TABLE_NAME, ',
      'GROUP_CONCAT(CONCAT(COLUMN_NAME) SEPARATOR '', '') AS COLUMN_NAMES, ', 
      'GROUP_CONCAT(CONCAT(REFERENCED_COLUMN_NAME) SEPARATOR '', '')
        AS REF_COLUMN_NAMES ',
      'FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE ',
      'WHERE REFERENCED_TABLE_SCHEMA = ''', schema_name,
      ''' AND REFERENCED_TABLE_NAME = ''', tbl_name, ''' ',
      'GROUP BY CONSTRAINT_NAME');
    PREPARE stmt FROM @query;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;

    -- Loop through all foreign keys to this table using a cursor.
    OPEN cursor1;
    read_loop: LOOP
      FETCH cursor1 INTO next_schema_name, next_tbl_name, next_col_names,
            ref_col_names;
      IF done THEN
        -- No more rows so exit the loop.
        LEAVE read_loop;
      END IF;

      -- Recursively call the stored procedure to delete linked rows
      -- for this foreign key.
      IF INSTR(next_col_names, ',') = 0 THEN
        SET next_where_clause = CONCAT(
          next_col_names, ' IN (SELECT ', ref_col_names, from_clause, ')');
      ELSE
        SET next_where_clause = CONCAT(
          '(', next_col_names, ') IN (SELECT ', ref_col_names, from_clause, ')');
      END IF;
      CALL delete_recursive(
        next_schema_name, next_tbl_name, next_where_clause, do_delete);
    END LOOP;
    CLOSE cursor1;
  END IF;

  -- Build the DELETE statement
  SET @query = CONCAT(
    'DELETE FROM ', schema_name, '.', tbl_name, ' WHERE ', where_clause);

  -- Get the number of primary key columns
  SET @pk_column_count = (SELECT COUNT(*)
                          FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
                          WHERE TABLE_SCHEMA = schema_name
                            AND TABLE_NAME = tbl_name
                            AND CONSTRAINT_NAME = 'PRIMARY');
  IF @pk_column_count = 0 THEN
    -- No primary key so just output the number of rows to be deleted
    SET @query = CONCAT(
      'SET @notes = CONCAT(''No primary key; number of rows to delete = '',
      (SELECT COUNT(*) FROM ', schema_name, '.', tbl_name, ' WHERE ',
      where_clause, '))');
    PREPARE stmt FROM @query;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
  ELSEIF @pk_column_count = 1 THEN
    -- 1 primary key column.
    -- Output the primary keys of the records to be deleted
    SET @pk_column = (SELECT COLUMN_NAME
                      FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
                      WHERE TABLE_SCHEMA = schema_name
                        AND TABLE_NAME = tbl_name
                        AND CONSTRAINT_NAME = 'PRIMARY');
    SET @pk_column_csv = CONCAT('CONCAT('''''''', ', @pk_column, ', '''''''')');
    SET @query = CONCAT(
      'SET @notes = (SELECT CONCAT(''', @pk_column, ' IN ('', GROUP_CONCAT(',
      @pk_column_csv, ' SEPARATOR '', ''), '')'') FROM ',
      schema_name, '.', tbl_name, ' WHERE ', where_clause, ')');
    PREPARE stmt FROM @query;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
  ELSE
    -- Multiple primary key columns.
    -- Output the primary keys of the records to be deleted.
    SET @pk_columns = (SELECT GROUP_CONCAT(COLUMN_NAME SEPARATOR ', ')
                       FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
                       WHERE TABLE_SCHEMA = schema_name
                         AND TABLE_NAME = tbl_name
                         AND CONSTRAINT_NAME = 'PRIMARY');
    SET @pk_columns_csv = (SELECT CONCAT('CONCAT(''('''''', ', GROUP_CONCAT(COLUMN_NAME
                             SEPARATOR ', '''''', '''''', '), ', '''''')'')')
                           FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
                           WHERE TABLE_SCHEMA = schema_name
                             AND TABLE_NAME = tbl_name
                             AND CONSTRAINT_NAME = 'PRIMARY');      
    SET @query = CONCAT(
     'SET @notes = (SELECT CONCAT(''(', @pk_columns,
     ') IN ('', GROUP_CONCAT(', @pk_columns_csv, ' SEPARATOR '', ''), '')'') FROM ',
      schema_name, '.', tbl_name, ' WHERE ', where_clause, ')');
    PREPARE stmt FROM @query;
    EXECUTE stmt;
    DEALLOCATE PREPARE stmt;
  END IF;

  IF @notes IS NULL THEN
    SET @notes = 'No affected rows.';
  END IF;

  -- Save details of the DELETE statement to be executed
  INSERT INTO temp_deletes (table_schema, table_name, where_sql, Notes)
  VALUES (schema_name, tbl_name, where_clause, @notes);

  IF @recursion_depth = 0 THEN
    -- Output the deletes.
    SELECT CONCAT('DELETE FROM ', schema_name, '.', table_name,
                  ' WHERE ', where_sql) `SQL`,
           Notes
    FROM temp_deletes ORDER BY id;

    IF do_delete THEN
      -- Perform the deletes: Loop through all delete queries using a cursor.
      SET done = FALSE;
      OPEN cursor2;
      read_loop: LOOP
        FETCH cursor2 INTO schema_name, tbl_name, where_clause;
        IF done THEN
          -- No more rows so exit the loop.
          LEAVE read_loop;
        END IF;

        SET @query = CONCAT(
          'DELETE FROM ', schema_name, '.', tbl_name, ' WHERE ', where_clause);

        PREPARE stmt FROM @query;
        EXECUTE stmt;
        DEALLOCATE PREPARE stmt;
      END LOOP;
      CLOSE cursor2;
    END IF;

    -- Tidy up
    DROP TEMPORARY TABLE IF EXISTS temp_deletes;
  END IF;

  -- Decrement current recursion depth since the stored procedure is being exited.
  SET @recursion_depth = @recursion_depth - 1;
END;//
DELIMITER ;

限制

  1. CREATE TEMPORARY TABLES运行正在使用的模式的存储过程的用户需要许可。
  2. MySQL 仅支持 255 的最大递归深度,因此如果存在大量外键链接(似乎不太可能),此方法将失效。
  3. 目前不支持“循环”/“循环”外键引用(例如,表 A 有指向表 B 的外键,表 B 有指向表 A 的外键),否则会导致无限循环。
  4. 它不是为在“实时”系统上使用而设计的:由于数据是递归删除的,如果在删除子记录和父记录之间添加了更多数据,以后的删除可能会失败。

关于mysql - 如何在 MySQL 中递归删除行(即也删除外键链接的行)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4413372/

有关mysql - 如何在 MySQL 中递归删除行(即也删除外键链接的行)?的更多相关文章

  1. ruby - 如何在 Ruby 中顺序创建 PI - 2

    出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits

  2. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  3. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  4. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

  5. ruby - 我可以使用 Ruby 从 CSV 中删除列吗? - 2

    查看Ruby的CSV库的文档,我非常确定这是可能且简单的。我只需要使用Ruby删除CSV文件的前三列,但我没有成功运行它。 最佳答案 csv_table=CSV.read(file_path_in,:headers=>true)csv_table.delete("header_name")csv_table.to_csv#=>ThenewCSVinstringformat检查CSV::Table文档:http://ruby-doc.org/stdlib-1.9.2/libdoc/csv/rdoc/CSV/Table.html

  6. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

    exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

  7. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  8. ruby - 如何在 Ruby 中拆分参数字符串 Bash 样式? - 2

    我正在为一个项目制作一个简单的shell,我希望像在Bash中一样解析参数字符串。foobar"helloworld"fooz应该变成:["foo","bar","helloworld","fooz"]等等。到目前为止,我一直在使用CSV::parse_line,将列分隔符设置为""和.compact输出。问题是我现在必须选择是要支持单引号还是双引号。CSV不支持超过一个分隔符。Python有一个名为shlex的模块:>>>shlex.split("Test'helloworld'foo")['Test','helloworld','foo']>>>shlex.split('Test"

  9. ruby - 我可以使用 aws-sdk-ruby 在 AWS S3 上使用事务性文件删除/上传吗? - 2

    我发现ActiveRecord::Base.transaction在复杂方法中非常有效。我想知道是否可以在如下事务中从AWSS3上传/删除文件:S3Object.transactiondo#writeintofiles#raiseanexceptionend引发异常后,每个操作都应在S3上回滚。S3Object这可能吗?? 最佳答案 虽然S3API具有批量删除功能,但它不支持事务,因为每个删除操作都可以独立于其他操作成功/失败。该API不提供任何批量上传功能(通过PUT或POST),因此每个上传操作都是通过一个独立的API调用完成的

  10. ruby - 如何在 Lion 上安装 Xcode 4.6,需要用 RVM 升级 ruby - 2

    我实际上是在尝试使用RVM在我的OSX10.7.5上更新ruby,并在输入以下命令后:rvminstallruby我得到了以下回复:Searchingforbinaryrubies,thismighttakesometime.Checkingrequirementsforosx.Installingrequirementsforosx.Updatingsystem.......Errorrunning'requirements_osx_brew_update_systemruby-2.0.0-p247',pleaseread/Users/username/.rvm/log/138121

随机推荐