2010-12-10 63 views
2

J'utilise Constraints dans ma base de données MySQL. Mais maintenant ça me donne mal à la tête quand j'essaye de supprimer une entrée sur laquelle d'autres entrées ont une relation de clé étrangère. Je reçois toujours cette erreur:Comment supprimer des lignes de manière récursive (c'est-à-dire supprimer également des lignes liées à des clés étrangères) dans MySQL?

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

Puis-je passer la suppression-déclaration tout paramètre ou quoi que ce soit, de sorte qu'il supprime récursivement toutes les lignes qui ont une relation de clé étrangère à la ligne que je suis en train de supprimer?

Répondre

0

Regardez ceci:

In what order are ON DELETE CASCADE constraints processed?

Mais je pense que vous pouvez utiliser sur CASCADE Supprimer de ma recherche. Si j'ai tort, je suis sûr que la communauté me le fera savoir. Je crois que vous devrez modifier vos tableaux, si possible.

Voir aussi ceci:

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

+0

Je pense qu'il a demandé un paramètre/variante du 'delete' déclaration, non pas pour la' ON SUPPRIMER CASCADE' changement de schéma. –

0

MISE À JOUR: ont maintenant fait cela dans un billet de blog: https://stevettt.blogspot.co.uk/2018/02/how-to-automate-deletion-of-rows-in.html


J'ai écrit une procédure stockée qui récursive supprimer toutes les tables liées à des clés étrangères (sans avoir besoin de désactiver les vérifications de clés étrangères ou d'activer les suppressions en cascade). L'implémentation a une certaine complexité mais peut être traitée comme une "boîte noire": spécifiez simplement le nom du schéma (base de données), de la table et d'une clause WHERE pour restreindre les enregistrements à supprimer et elle fera le reste.

Démo

Rextester démo en ligne: 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 ; 

Limites

  1. CREATE TEMPORARY TABLES Cette autorisation est requise pour l'utilisateur exécutant la procédure stockée pour le (s) schéma (s) utilisé (s).
  2. MySQL ne prend en charge qu'une profondeur de récursivité maximale de 255, de sorte que cette méthode tomberait si un grand nombre de liens de clé étrangère étaient présents (ce qui est peu probable).
  3. Les références de clé étrangère "circulaire"/"cyclique" (par exemple, table A a une clé étrangère dans la table B et table B une clé étrangère dans la table A) ne sont pas supportées actuellement et provoqueraient une boucle infinie.
  4. Il n'est pas conçu pour une utilisation sur un système «en direct»: les données étant supprimées récursivement, les suppressions ultérieures peuvent échouer si davantage de données ont été ajoutées entre la suppression des enregistrements parent et enfant.