8

Is it possible via script/tool to generate authomatically many delete statements based on the tables fk relations, using Oracle PL/SQL?

In example: I have the table: CHICKEN (CHICKEN_CODE NUMBER) and there are 30 tables with fk references to its CHICKEN_CODE that I need to delete; there are also other 150 tables foreign-key-linked to that 30 tables that I need to delete first.

Is there some tool/script PL/SQL that I can run in order to generate all the necessary delete statements based on the FK relations for me?

(by the way, I know about cascade delete on the relations, but please pay attention: I CAN'T USE IT IN MY PRODUCTION DATABASE, because it's dangerous!)

I'm using Oracle DataBase 10G R2.

Please pay attention to this:

Generate Delete Statement From Foreign Key Relationships in SQL 2008?

Another user has just written it in SQL SERVER 2008, anyone is able to convert to Oracle 10G PL/SQL? I am not able to... :-(

Please assume that V_CHICKEN and V_NATION are the criteria to select the CHICKEN to delete from the root table: the condition is: "where COD_CHICKEN = V_CHICKEN AND COD_NATION = V_NATION" on the root table.

Community
  • 1
  • 1
UltraCommit
  • 2,236
  • 7
  • 43
  • 61
  • 1
    What you are proposing is a manual implementation of CASACDE DELETE. I fail to see how that could be less dangerous. Anyway, the vital question is this: are you going to zap *all* records in CHICKEN or just some (one)? – APC Apr 20 '10 at 17:19
  • In my particular situation, I need to ZAP only one record of the CHICKEN table... But - starting from this simple record - through referential integrity we descend to many and many tables depending on it... I can count an approx depth of level 7 (!!). – UltraCommit Apr 20 '10 at 18:37
  • 1
    I've updated my answer with a script for you to try. No guarantees though... – Igby Largeman Apr 21 '10 at 03:14
  • I am trying the script now. Perhaps it does not handle the propagation of the referential integrities to other users, how can I fix this? In the meantime, the script has just generated over 3129 DELETE STATEMENT that I was expected to write manually! :-) – UltraCommit Apr 21 '10 at 08:45
  • Please help me to understand why the stored procedure is cyclying always on the same 39 delete statement..... – UltraCommit Apr 21 '10 at 09:06
  • 1
    You know what, I don't think it's quite as hard as I thought. I might be able to fix it if I get time today. – Igby Largeman Apr 21 '10 at 17:54
  • 1
    I've updated my answer with a new script. It works for me but I suspect there could be issues if you have complex relationships. Give it a try and let me know how it goes. :) – Igby Largeman Apr 21 '10 at 21:11
  • 1
    Why are you programming in "discovery mode"? It's like "I have no idea what kind of database this is, nor how the tables are laid out and connected, I just know I need to delete a row in one of them". Don't you have a proper data access layer for this database? – Lasse V. Karlsen Apr 21 '10 at 21:13
  • 1
    ... OP sez: "cascade delete on the relations ...I CAN'T USE IT IN MY PRODUCTION DATABASE, because it's dangerous!". And you think getting a script from the internet (OK, SO) and running it on your production database is *less* dangerous? Ultimately, you are going to understand whatever solution you accept in great detail because it is your hide at stake. – Ira Baxter Jul 11 '10 at 23:09
  • I ask you to make yourself clear, I don't understand your last sentence. – UltraCommit Nov 05 '10 at 13:57
  • "I am going to understand whatever solution I accept in great detail because it is my hide at stake"???? WHAT DOES IT MEAN? I understand the single words, but not the meaning of the complete sentence. – UltraCommit Nov 05 '10 at 13:58
  • 1
    He means that when your butt is on the line (your job is at risk), you're going to make SURE the code you use is safe, regardless of where you got it. – Igby Largeman Nov 16 '10 at 01:04
  • Thanks for the translation, Charles :-) I've never seen/heard the expression "your butt is on the line" :-) But now it's clear. I've tried many times your procedure and it works great, of course I don't launch the delete without having previously carefully read them!!! ;-) I use to store the delete in a temporary table, then save them in a file, and then I read carefully them using UltraEdit or TOAD SQL Editor. – UltraCommit Nov 22 '10 at 11:13

3 Answers3

22

(My first answer became too long and difficult to edit, and it got Community Wikified, which is really annoying. Here is the latest version of the script.)

This script attempts to perform a cascading delete through recursion. It should avoid infinite loops when there are circular references. But it requires that all circular referential constraints have ON DELETE SET NULL or ON DELETE CASCADE.

CREATE OR REPLACE PROCEDURE delete_cascade(
    table_owner          VARCHAR2,
    parent_table         VARCHAR2,
    where_clause         VARCHAR2
) IS
    /*   Example call:  execute delete_cascade('MY_SCHEMA', 'MY_MASTER', 'where ID=1'); */

    child_cons     VARCHAR2(30);
    parent_cons    VARCHAR2(30);
    child_table    VARCHAR2(30);
    child_cols     VARCHAR(500);
    parent_cols    VARCHAR(500);
    delete_command VARCHAR(10000);
    new_where_clause VARCHAR2(10000);

    /* gets the foreign key constraints on other tables which depend on columns in parent_table */
    CURSOR cons_cursor IS
        SELECT owner, constraint_name, r_constraint_name, table_name, delete_rule
          FROM all_constraints
         WHERE constraint_type = 'R'
           AND delete_rule = 'NO ACTION'
           AND r_constraint_name IN (SELECT constraint_name
                                       FROM all_constraints
                                      WHERE constraint_type IN ('P', 'U')
                                        AND table_name = parent_table
                                        AND owner = table_owner)
           AND NOT table_name = parent_table; -- ignore self-referencing constraints


    /* for the current constraint, gets the child columns and corresponding parent columns */
    CURSOR columns_cursor IS
        SELECT cc1.column_name AS child_col, cc2.column_name AS parent_col
          FROM all_cons_columns cc1, all_cons_columns cc2
         WHERE cc1.constraint_name = child_cons
           AND cc1.table_name = child_table
           AND cc2.constraint_name = parent_cons
           AND cc1.position = cc2.position
        ORDER BY cc1.position;
BEGIN
    /* loops through all the constraints which refer back to parent_table */
    FOR cons IN cons_cursor LOOP
        child_cons   := cons.constraint_name;
        parent_cons  := cons.r_constraint_name;
        child_table  := cons.table_name;
        child_cols   := '';
        parent_cols  := '';

        /* loops through the child/parent column pairs, building the column lists of the DELETE statement */
        FOR cols IN columns_cursor LOOP
            IF child_cols IS NULL THEN
                child_cols  := cols.child_col;
            ELSE
                child_cols  := child_cols || ', ' || cols.child_col;
            END IF;

            IF parent_cols IS NULL THEN
                parent_cols  := cols.parent_col;
            ELSE
                parent_cols  := parent_cols || ', ' || cols.parent_col;
            END IF;
        END LOOP;

        /* construct the WHERE clause of the delete statement, including a subquery to get the related parent rows */
        new_where_clause  :=
            'where (' || child_cols || ') in (select ' || parent_cols || ' from ' || table_owner || '.' || parent_table ||
            ' ' || where_clause || ')';

        delete_cascade(cons.owner, child_table, new_where_clause);
    END LOOP;

    /* construct the delete statement for the current table */
    delete_command  := 'delete from ' || table_owner || '.' || parent_table || ' ' || where_clause;

    -- this just prints the delete command
    DBMS_OUTPUT.put_line(delete_command || ';');

    -- uncomment if you want to actually execute it:
    --EXECUTE IMMEDIATE delete_command;

    -- remember to issue a COMMIT (not included here, for safety)
END;
Igby Largeman
  • 16,495
  • 3
  • 60
  • 86
  • 1
    Chicken: if this has been any help at all, I would appreciate an upvote; maybe even accepted answer? :) (On *this* answer, as I cannot gain any rep for the first one because it is now community wiki). – Igby Largeman Apr 22 '10 at 20:02
  • VoteUp requires 15 reputation, so I can't NOW give you an upvote... but in the future, of course ;-) – UltraCommit Apr 23 '10 at 13:09
  • 1
    I wish I could see your database so I could (maybe) get this thing to work for you! – Igby Largeman Apr 23 '10 at 15:55
  • Imagine the output of your stored procedure: it is composed by 145 DELETE statements! :-) The maximum length of the DELETE statements is: 1556 characters! :-) – UltraCommit Apr 23 '10 at 21:27
  • OF COURSE, I'll vote UP for your answer: I have only to wait for the 15th reputation (at present, my reputation score is 13). – UltraCommit Apr 23 '10 at 21:29
  • NOTE: My boss asked to me to write MANUALLY those 145 delete statements........... This is why I have asked for help!!! :-) – UltraCommit Apr 23 '10 at 21:31
  • I think you are the first for having written such a useful stored procedure!! :-) All databases have referential integrities, and one common problem is often to delete old records respecting the foreign keys, and avoiding to leave "dirty data" on the DB! – UltraCommit Apr 23 '10 at 21:33
  • MICROSOFT WORD statistics of the output of the stored procedure: Pages: 25 Words: 7860 Characters (no spaces): 68745 Characters (with spaces): 76.460 Paragraphs: 141 Lines: 1011 – UltraCommit Apr 23 '10 at 21:37
  • 1
    1556 characters, wow :) How many levels deep is it (how many ")" are at the end of the statement)? I have learned an important lesson from this... If I ever design a large database, I think I'll make all constraints deferrable. :) – Igby Largeman Apr 23 '10 at 21:49
  • The DELETE statement with length of 1556 characters ends with six consecutive right parenthesis )))))) :-) – UltraCommit Apr 24 '10 at 11:48
  • Finally I can vote up your USEFUL answer, my reputation score is now 23 and no more 13 :-) – UltraCommit Apr 24 '10 at 11:50
  • I won Supporter bronze badge voting up your question! ;-) – UltraCommit Apr 24 '10 at 11:51
  • 1
    Those long (6 level) deletes might take quite a while to run if the tables are large (depends on which indexes exist of course). Have you run the deletes on your production DB yet? – Igby Largeman Apr 25 '10 at 02:19
  • 1
    I recommend editing your question to make it simpler now - maybe remove the code samples and just leave the question text so anyone reading it in future won't be confused. – Igby Largeman Apr 25 '10 at 02:22
  • 1
    I should have used joins instead of nested subqueries... maybe that can be version 2.0. :) – Igby Largeman Apr 25 '10 at 02:39
  • Hello Charles, the 6-level-deletes are really fast, because they involve a few records. The most nested condition is related to only one row, and is a condition that uses an index built on the two fields of the condition: this rows references to the child tables involving not more than 30-40 records totally. – UltraCommit Apr 26 '10 at 08:33
  • I follow your suggestion, editing the question leaving OUT my WRONG examples ;-) – UltraCommit Apr 26 '10 at 08:33
  • I think that your solution with nested subqueries is more and more readable than the solution with joins ;-) – UltraCommit Apr 26 '10 at 08:39
  • 1
    If you mean the output, I think you're right about that. The delete statements with nested queries are very easy to read (when formatted nicely). But huge JOINs with all those ANDs in them would be really ugly! – Igby Largeman Apr 27 '10 at 21:53
  • I use "Formatter Plus" by Quest Software, in order to format nicely the queries ;-) – UltraCommit Apr 28 '10 at 08:05
  • I use Toad for Oracle Xpert 10.1.1.8 :-) – UltraCommit Apr 30 '10 at 15:39
  • Charles, could you help me with my second question related to SQL Plus? http://stackoverflow.com/questions/2735942/export-as-insert-statements-but-in-sql-plus-the-line-overrides-2500-characters – UltraCommit May 01 '10 at 12:14
3

The problem is if the top level key column isn't propagated all the way down to the bottom. If you can do DELETE FROM grandchild WHERE parent_id = :1, it is fine. If you have to do,

DELETE FROM grandchild
WHERE child_id in (SELECT id FROM child WHERE parent_id = :1)

then going down six or seven deep will give you ugly (and probably slow) queries.

While you said you can't make the constraints CASCADE, can you make them deferrable initally immediate ? That way existing code should not be impacted. Your 'delete' session would make all constraints deferred. Then delete from the parent, delete from the child where the record wasn't in the parent, delete from the grandchild where there's no match in the child etc...

Gary Myers
  • 34,963
  • 3
  • 49
  • 74
  • I cannot make the constraints CASCADE, because I cannot modify the database: I can only INSERT, DELETE, SELECT and UPDATE, without modifying the database using Data Definition Language statements. – UltraCommit Apr 21 '10 at 09:08
2

This is a great exercise in developing your PL/SQL skills and general Oracle knowledge!

You need to identify all constrained columns in all tables with relations descending from your master table. You can get all the information you need from two views: ALL_CONSTRAINTS and ALL_CONS_COLUMNS. (If all the tables are in the same schema as the user executing the script, you can use USER_CONSTRAINTS and USER_CONS_COLUMNS if you prefer)

This query will find all the foreign key constraints which refer back to a given table (CUSTOMER in this example):

SELECT constraint_name, table_name, constraint_type
  FROM all_constraints
 WHERE constraint_type = 'R'
   AND r_constraint_name IN (SELECT constraint_name
                               FROM all_constraints
                              WHERE constraint_type IN ('P', 'U')
                                AND table_name = 'CUSTOMER');


CONSTRAINT_NAME                C
------------------------------ -
CUSTOMER_FK1                   R
CUSTOMER_FK4                   R
CUSTOMER_FK5                   R
CUSTOMER_FK3                   R
CUSTOMER_FK2                   R

Now, for each of the results from that query, you can use the CONSTRAINT_NAME column to get a table and column name which you can use to write DELETE statements to delete all child rows in all child tables.

This example gets the table and column name for a constraint called CUSTOMER_FK1

SELECT table_name, column_name
  FROM user_cons_columns
 WHERE constraint_name = 'CUSTOMER_FK1'

TABLE_NAME                    COLUMN_NAME                       
----------------------------- ------------------------------------
RESERVATION                   CUSTOMER_UID

So you could do, for example:

DELETE FROM reservation
 WHERE customer_uid = 00153464

or

DELETE FROM reservation
 WHERE customer_uid IN (SELECT customer_uid
                          FROM customer
                         WHERE customer_type = 'X')

But your child tables also have child tables, so of course you will have to delete those child rows (call them grandchild rows) first. Supposing there is a table called reservation_detail which has a foreign key relationship with reservation, your delete command for reservation_detail might look like:

DELETE FROM reservation_detail 
 WHERE reservation_uid in (SELECT reservation_uid     
                             FROM reservation 
                            WHERE customer_uid IN (SELECT customer_uid
                                                     FROM customer
                                                    WHERE customer_type = 'X')

And if reservation_detail also has children... you get the idea. Of course you could use joins instead of nested queries, but the principle is the same: the more levels deep your dependencies go, the more complex your delete commands become.

So now you know how to do it, the challenge is to write a generic PL/SQL script to delete all child rows, grandchild rows, great-grandchild rows ... (ad infinitum) for any given table, from the bottom up. You will have to employ recursion. Should be a fun program to write!

(Last edit: removed the script; see my other answer for the final solution.)

Igby Largeman
  • 16,495
  • 3
  • 60
  • 86
  • Please help! I've tried to write the procedure without success. I need that procedure within a few days... My referential integrities are 5 pages long, A3 size.... help!! – UltraCommit Apr 20 '10 at 20:19
  • 1
    When I get some time I'll write you some code. Meanwhile, try google... somebody must have written this already. – Igby Largeman Apr 20 '10 at 21:06
  • I am trying with Google since 3 or 4 days without interesting results.... Of course I have always typed the wrong keywords... I have tried with: code, generation, delete statement, referential integrity, dba_constraints, and so on... I should be grateful if you could help me, because I am not able to complete this task. In the meantime, I'll continue to search with Google. – UltraCommit Apr 20 '10 at 21:20
  • 1
    I'd try: oracle, pl/sql, delete, table, data, children, recursive, recursion. I had a quick look and didn't see anything obvious though. – Igby Largeman Apr 20 '10 at 21:36
  • 1
    By the way, the script I've provided above can potentially open a lot of cursors if you have relationships many levels deep. Depending on your server's settings, you could hit the OPEN_CURSORS limit. – Igby Largeman Apr 21 '10 at 03:13
  • First of all, thank you very much for your kind help! ;-) Now I am trying the script, please note that the referential integrities propagate also in other 5 schemas... I hope that the script handles this case, too. – UltraCommit Apr 21 '10 at 08:36
  • The number of the records is increasing and increasing, but the DISTINCT delete statements are only 39..... Have I met a loop??? – UltraCommit Apr 21 '10 at 09:02
  • 9527 records written in the RESULT_TABLE (I've written the delete statements in a temporary table, instead of displaying them on the screen with dbms_output.put_line), but the distinct records are 39, and it's running now... – UltraCommit Apr 21 '10 at 09:03
  • I have stopped the running script, in the referential integrities there are loops, how can I handle them??? – UltraCommit Apr 21 '10 at 09:34
  • I have 3 delete rules on the referential integrities: CASCADE, NO ACTION and SET NULL. I've modified the CURSOR fetching only NO ACTION referential integrities, is it correct? CURSOR cons_cursor IS (....omissis....) AND delete_rule = 'NO ACTION'; I've noted that the LOOPS have always the CASCADE option. – UltraCommit Apr 21 '10 at 10:14
  • I have just noted that the NESTED conditions will be lost: it will be kept only the condition of level 1. – UltraCommit Apr 21 '10 at 12:09
  • I'm not sure what you're telling me... However, I have realised that my script is no good. It would delete *everything* from all the child tables... Stupid; that's what happens when I code in a hurry. :( Sorry, but this is a tough one. I doubt I'm going to have time to crack it. – Igby Largeman Apr 21 '10 at 15:19
  • Don't worry friend, it's all day that I am studying your program... ;-) It's 90% perfect, but it does not handle the where conditions when invoking recursion: it only keeps the "surface" where condition. I have not found the solution, I have only thought to a second parameter that is a string incremented ad every recursion... but now I am having problems with "PLS-00306: wrong number or types of arguments in call to 'DELETE_CASCADE'", and I don't know why..... Help!! :-) – UltraCommit Apr 21 '10 at 15:34
  • Right - every DELETE includes a subquery in its **where** clause. What I forgot was, that subquery also needs a **where** clause: it must only delete the rows which are children of the rows *to be deleted* from its parent table. To build this **where** clause, we need to know which rows we will delete from the parent table, but to know that we also need to know which rows will be deleted from the parent's parent... and so on. So, from the top (master table) down the tree, each delete will have multiple nested subqueries (the lower you go, the more nested subqueries). It could get ugly. – Igby Largeman Apr 21 '10 at 17:38
  • If you're getting PLS-00306, it must be caused by your change to the script... somehow you're calling it without passing in all the parameters. (I don't think it was possible to get this error with my original script) – Igby Largeman Apr 21 '10 at 17:40
  • I have turned the stored procedure into a function, so it will be easier to merge the strings with recursive calls.... Now I am running it again, but I am not so satisfied for what am I seeing... (help) – UltraCommit Apr 21 '10 at 18:11
  • I've fixed my script. Good luck. :) – Igby Largeman Apr 21 '10 at 21:19
  • Tomorrow morning, when I'll come back to my office, I'll try again your script, I'm 99% sure it works fine! ;-) Thank you again for your kind, prompt, fast help!! ;-) – UltraCommit Apr 21 '10 at 21:25
  • Of course, instead of EXECUTE IMMEDIATE your command, I store it in a table called RIS, in order to avoid problems ;-) – UltraCommit Apr 22 '10 at 12:52
  • I have added the condition: AND DELETE_RULE = 'NO ACTION' in order to avoid deletion in case of referential integrities of type 'CASCADE' and of type 'SET NULL'. Do you agree?? – UltraCommit Apr 22 '10 at 13:00
  • CURSOR cons_cursor IS SELECT owner, constraint_name, r_constraint_name, table_name FROM all_constraints WHERE constraint_type = 'R' AND r_constraint_name IN (SELECT constraint_name FROM all_constraints WHERE constraint_type IN ('P', 'U') AND table_name = parent_table AND owner = table_owner) AND DELETE_RULE = 'NO ACTION'; – UltraCommit Apr 22 '10 at 13:00
  • Your script is PERFECT, but my idea to add the code: AND DELETE_RULE = 'NO ACTION' is WRONG. Now I run the script again, without my added code. In my database there are some tables that reference themselves, with the same fields...... – UltraCommit Apr 22 '10 at 14:17
  • Oh, no, now I obtain the following error!! :-( ORA-01461: can bind a LONG value only for insert into a LONG column – UltraCommit Apr 22 '10 at 14:26
  • The problem are the referential integrities auto-referential, as I have written above... How can I detect loops?? – UltraCommit Apr 22 '10 at 14:29
  • SELECT owner, constraint_name, r_constraint_name, table_name FROM all_constraints WHERE constraint_type = 'R' AND r_constraint_name IN (SELECT constraint_name FROM all_constraints WHERE constraint_type IN ('P', 'U') AND table_name = parent_table AND owner = table_owner) AND table_name != parent_table; I have added the condition TABLE_NAME != PARENT_TABLE to avoid LOOPS... – UltraCommit Apr 22 '10 at 14:36
  • My condition TABLE_NAME != PARENT_TABLE avoids ONLY the loops of depth = 1. I have not only cases as follows: TABLE A => REFERENTIAL INTEGRITY => TABLE A ... but I have also cases as follows: TABLE A => TABLE B => TABLE C => TABLE D => TABLE A ... how can I fix this??? HELP! – UltraCommit Apr 22 '10 at 15:25
  • I'm assuming your circular references are based on Unique constraints on nullable columns. This means the script will also need to generate UPDATE statements to set the parent columns to NULL before the parent rows can be deleted... Not the mention the loop condition it needs to handle. – Igby Largeman Apr 22 '10 at 16:22
  • Now I try to copy the creation script of a table with circular references, in order to show you the critical situation. – UltraCommit Apr 22 '10 at 16:46
  • Are your constraints deferrable? Do `select * FROM all_constraints` and look at the DEFERRABLE column of the constraints on all of your tables. – Igby Largeman Apr 22 '10 at 16:51
  • ALTER TABLE CIRCLE ADD ( CONSTRAINT FK000001 FOREIGN KEY (COD_CHICKEN, COD_NATION, NUMERICAL_CODE) REFERENCES MONSTER (COD_CHICKEN,COD_NATION, NUMERICAL_CODE) ON DELETE SET NULL, CONSTRAINT FK000002 FOREIGN KEY (COD_TRTT, NUM_ANN_SRI, TIP_FORM_TRTT) REFERENCES TOWER (COD_TRTT,NUM_ANN_SRI,TIP_FORM_TRTT) ON DELETE SET NULL, CONSTRAINT FK000003 FOREIGN KEY (COD_CHICKEN1) REFERENCES OCTOPUS (COD_CHICKEN) ON DELETE SET NULL, CONSTRAINT FK000004 FOREIGN KEY (COD_BRK) REFERENCES BRIDGE (COD_BRK) ON DELETE SET NULL, (... CONTINUE ...) – UltraCommit Apr 22 '10 at 16:52
  • CONSTRAINT FK000005 FOREIGN KEY (TIP_CIRCLE) REFERENCES TYPE_CIRCLE (TIP_CIRCLE), CONSTRAINT FK000006 FOREIGN KEY (COD_CHICKEN_TYPE) REFERENCES TYPE_MODE (COD_CHICKEN_TYPE), CONSTRAINT FK000007 FOREIGN KEY (COD_DVS) REFERENCES DVD_CDROM (COD_DVD), CONSTRAINT FK000008 FOREIGN KEY (COD_AFF) REFERENCES AFFERMATION (COD_AFFERMATION) ON DELETE SET NULL, CONSTRAINT FK000009 FOREIGN KEY (COD_CHICKEN, COD_NATION, COD_CIRCLE, COD_VRN_CIRCLE) REFERENCES CIRCLE (COD_CHICKEN,COD_NATION,COD_CIRCLE,COD_VRN_CIRCLE) ON DELETE CASCADE); – UltraCommit Apr 22 '10 at 16:53
  • My constraints are NOT deferrable :-( – UltraCommit Apr 22 '10 at 16:53
  • Is FK000009 on CIRCLE or a different table? If it is on CIRCLE, wouldn't it be completely pointless? How can a constraint reference the same columns in its own table? – Igby Largeman Apr 22 '10 at 17:05
  • If all of the circular constraints have ON DELETE SET NULL, it would really help. From the example you posted above, it looks like that might be the case. – Igby Largeman Apr 22 '10 at 17:13
  • I don't know how can a constraint reference the same columns of the same table, but in production environment, so it is :-) Please assume that all of the circular constraints have ON DELETE SET NULL. – UltraCommit Apr 22 '10 at 17:18
  • I am not sure that all the circular constraints have ON DELETE SET NULL, please assume so... I have difficult in writing the creation scripts, because I can't publish the real names (of course). – UltraCommit Apr 22 '10 at 17:19
  • Now I try to post another example, so you can see if our assumption (on delete set null) is correct. – UltraCommit Apr 22 '10 at 17:25
  • ALTER TABLE AUTO_MOTO_PRP ADD ( CONSTRAINT FK000013 FOREIGN KEY (COD_CHICKEN, COD_PRP, PROGRESS_CODE_PRP) REFERENCES MOV_PRP (COD_CHICKEN,COD_PRP,PROGRESS_CODE_PRP), CONSTRAINT FK000014 FOREIGN KEY (COD_CHICKEN, COD_LIV_OGZ) REFERENCES LIV_OGZ (COD_COMP_PTF,COD_LIV_OGZ), CONSTRAINT FK000015 FOREIGN KEY (COD_CHICKEN, COD_GRU_ARZ) REFERENCES GRU_ARZ (COD_CHICKEN,COD_GRU_ARZ) ON DELETE SET NULL, CONSTRAINT FK000016 FOREIGN KEY (COD_CHICKEN0, COD_NATION, PROGRESS_CODE) REFERENCES AUTO_MOTO (COD_CHICKEN,COD_NATION,PROGRESS_CODE) ON DELETE SET NULL); – UltraCommit Apr 22 '10 at 17:40
  • ALTER TABLE AUTO_MOTO ADD ( CONSTRAINT FK000022 FOREIGN KEY (COD_CHICKEN, COD_NATION, PROGRESS_CODE) REFERENCES MOV (COD_CHICKEN,COD_NATION,PROGRESS_CODE), CONSTRAINT FK000023 FOREIGN KEY (COD_CHICKEN, COD_LIV_OGZ) REFERENCES LIV_OGZ (COD_COMP_PTF,COD_LIV_OGZ), CONSTRAINT FK000024 FOREIGN KEY (COD_CHICKEN, COD_GRU_ARZ) REFERENCES GRU_ARZ (COD_CHICKEN,COD_GRU_ARZ) ON DELETE SET NULL, CONSTRAINT FK000025 FOREIGN KEY (COD_CHICKEN0, COD_PRP, PROGRESS_CODE_PRP) REFERENCES AUTO_MOTO_PRP (COD_CHICKEN,COD_PRP,PROGRESS_CODE_PRP) ON DELETE SET NULL); – UltraCommit Apr 22 '10 at 17:41
  • In this last case, we have two crossed tables, which reference themselves. – UltraCommit Apr 22 '10 at 17:42
  • I've added a new version of the script. IF all of your circular constraints have ON DELETE SET NULL, this should prevent the infinite looping problem. Try it and let's find the next problem... :) – Igby Largeman Apr 22 '10 at 17:43
  • Oh, I forgot about the self-referencing constraints. You will probably have to modify the cursor for those again. – Igby Largeman Apr 22 '10 at 17:44
  • I try immediately!! :-) I add again the condition: TABLE_NAME != PARENT_TABLE as before. – UltraCommit Apr 22 '10 at 17:49
  • I have tried to pass the critical tables, without starting from the root. The procedure doesn't loop (!!!). Now I make the global run, and then try to launch it in test environment (don't worry per production data). – UltraCommit Apr 22 '10 at 17:56
  • Something went wrong: the critical tables have NOT a DELETE STATEMENT... Is it normal? Can I try in test environment? – UltraCommit Apr 22 '10 at 18:00
  • I just added `AND delete_rule = 'NO ACTION'`, this is simpler. – Igby Largeman Apr 22 '10 at 18:04
  • Are you saying there is no delete statement being generated for some tables? Maybe my change caused that... but my head is going to implode trying to figure out why! – Igby Largeman Apr 22 '10 at 18:04
  • ORA-01407: cannot update ("PROPRIETOR"."CIRCLE"."COD_CHICKEN") to NULL ORA-06512: at line 4490 – UltraCommit Apr 22 '10 at 18:06
  • Of course, CIRCLE is the self-integrity-referenced table... I substitute TABLE_NAME != PARENT_TABLE with your suggestion: AND DELETE_RULE = 'NO ACTION'. – UltraCommit Apr 22 '10 at 18:08
  • ORA-01407 means you have a constraint with ON DELETE SET NULL, but the constrained column is a NOT NULL column. That would be a ridiculous situation! – Igby Largeman Apr 22 '10 at 18:09
  • I know... but I have not designed the database ;-) With your suggestion, the procedure is running now... – UltraCommit Apr 22 '10 at 18:10
  • 44 delete statements written till now – UltraCommit Apr 22 '10 at 18:27
  • Uh oh.. that's insanely slow! – Igby Largeman Apr 22 '10 at 18:36
  • Don't worry... The only problem is that I don't see the critical tables as delete statement... – UltraCommit Apr 22 '10 at 18:38
  • Remember it won't delete rows from any table until ALL dependants have been deleted. Maybe it hasn't got that far yet. – Igby Largeman Apr 22 '10 at 18:41
  • It is normal that it is insanely slow, because the printed graph of the referential integrities fulls 5 pages A3 size! – UltraCommit Apr 22 '10 at 18:42
  • Without the self-referenced-integrities, we have solved the problem! But in my case, we have them..... – UltraCommit Apr 22 '10 at 18:43
  • The self-referencing constraints are still causing trouble? That means some of them must be NO ACTION. We need to modify the cursor to ignore self-referencing constraints. – Igby Largeman Apr 22 '10 at 18:53
  • I don't see any cycle in the delete statement: all the delete statement obtained are UNIQUE. – UltraCommit Apr 22 '10 at 18:54
  • If it goes into a loop on a self-referencing constraint, put `and table_name <> parent_table` back into the query. – Igby Largeman Apr 22 '10 at 18:56
  • The only problem is that now the procedure does NOT enter in the loop, it avoids them at all... FINISHED!!! It has finished running! Now I copy the delete statements to UltraEdit, transform them in a PL/SQL block, and then try to run. – UltraCommit Apr 22 '10 at 18:58
  • 133 delete statements at all. – UltraCommit Apr 22 '10 at 18:58
  • How many tables are there? You should have at least that many delete statements, probably many more. – Igby Largeman Apr 22 '10 at 19:02
  • I am sorry :-( ORA-01407: cannot update ("PROPRIETOR"."CIRCLE"."COD_CHICKEN") to NULL ORA-06512: at line 1302 I state that a delete statement for the CIRCLE table does not exist in the output of the procedure. – UltraCommit Apr 22 '10 at 19:02
  • I can see that the procedure does NOT enter in the loops, because the tables involved in the loop are not present. It avoids the loops without running on them for only one time. – UltraCommit Apr 22 '10 at 19:04
  • It is completely insane to have an ON DELETE SET NULL constraint for a NOT NULL column! That means you MUST delete the child before the parent, which is impossible if you're dealing with a circular reference where the "parent" is really the child (or descendant) of the "child". The only way you could ever delete the "parent" of CIRCLE is if you have a strange series or constraints like this : `C -> B -> A -> C` where ALL of the constraints are ON DELETE SET NULL, and the C->B and B->A constraints are on nullable columns. – Igby Largeman Apr 22 '10 at 19:25
  • (cont) But then you would have delete in this order: A, C, B. (In this example A is CIRCLE, and C is the table which is throwing the ORA-01407). If this is how your database is set up, I don't think I have the time or energy to write a script to handle it. I suggest you talk to the DBA about removing these silly FK's. Sorry :( – Igby Largeman Apr 22 '10 at 19:26
  • IF your database is set up like I described above (which seems unlikely), then you should be able to do all the deletes with TWO passes. Ignore all the exceptions in the first pass (but continue), and then the second pass would handle all those tables. – Igby Largeman Apr 22 '10 at 19:31
  • My problem is that I cannot commit during the execution: at the end of the procedure, the executor can decide whether commit or rollback. – UltraCommit Apr 23 '10 at 13:08
  • Don't let my procedure excecute any commands; I was thinking you could take the output from my procedure (the series of DELETEs) and execute it all twice, continuing past any exceptions. But I'm not sure if it would work, and it requires that your constraints are set up in the odd way I described above. – Igby Largeman Apr 23 '10 at 15:55
  • Incredible! I have solved in this way: I have run the procedure for the sub-table which gives problem, and then I have obtained a few DELETE statements which I have manually inserted BEFORE the critical point in the script with 133 delete statements: IT WORKS!! :-) – UltraCommit Apr 23 '10 at 16:23
  • PERHAPS, there are two types of the auto-referential integrities constraints: the first type has CASCADE DELETE, and it solves itself. The second type has ON DELETE SET NULL, and is NOT autoresolving. The solution is to run your PERFECT procedure in the sub-loop, and then manually insert the few delete statements, so obtained, in the TOTAL script. – UltraCommit Apr 23 '10 at 16:26
  • I must observe, as you suggest, that in my database there are NONSENSE constraints, this means that I have ON DELETE SET NULL with reference to a NOT-NULLABLE field (COD_CHICKEN). Absurd. – UltraCommit Apr 23 '10 at 16:27
  • Well done!! Now find out who designed your database and ask them what the heck they were thinking. :) – Igby Largeman Apr 23 '10 at 16:36