Как сгенерировать операторы DELETE в PL / SQL, основываясь на таблицах отношений FK? - PullRequest
6 голосов
/ 20 апреля 2010

Можно ли с помощью скрипта / инструмента автоматически генерировать много операторов удаления на основе таблиц fk отношений, используя Oracle PL / SQL?

Например: у меня есть таблица: CHICKEN (CHICKEN_CODE NUMBER) и там30 таблиц с ссылками fk на его CHICKEN_CODE, которые мне нужно удалить;Есть также другие 150 таблиц, связанных внешним ключом с этими 30 таблицами, которые мне нужно сначала удалить.

Есть ли какой-нибудь инструмент / скрипт PL / SQL, который я могу запустить, чтобы сгенерировать все необходимые операторы удаленияна основании отношений FK для меня?

(кстати, я знаю о каскадном удалении отношений, но, пожалуйста, обратите внимание: я не могу использовать его в своей базе данных, потому что это опасно!)

Я использую Oracle DataBase 10G R2.

Обратите внимание на это:

Создание оператора удаления из отношений внешнего ключа в SQL 2008?

Другой пользователь только что написал это в SQL SERVER 2008, кто-нибудь может конвертировать в Oracle 10G PL / SQL?Я не могу ...: - (

Предположим, что V_CHICKEN и V_NATION являются критериями для выбора КУРИЦА для удаления из корневой таблицы: условие: "где COD_CHICKEN = V_CHICKEN AND COD_NATION = V_NATION"в корневой таблице.

Ответы [ 3 ]

21 голосов
/ 23 апреля 2010

(Мой первый ответ стал слишком длинным и трудным для редактирования, и он получил Community Wikified, что очень раздражает. Вот последняя версия скрипта.)

Этот скрипт пытается выполнить каскадное удалениечерез рекурсию.Следует избегать бесконечных циклов, когда есть круговые ссылки.Но для этого необходимо, чтобы все круговые референциальные ограничения имели ON DELETE SET NULL или 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;
3 голосов
/ 21 апреля 2010

Проблема в том, что ключевой столбец верхнего уровня не распространяется до самого низа. Если вы можете выполнить команду DELETE FROM внучат WHERE parent_id =: 1, это нормально. Если вам нужно сделать,

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

тогда падение на шесть или семь приведет к уродливым (и, вероятно, медленным) запросам.

Хотя вы сказали, что не можете устанавливать ограничения CASCADE, можете ли вы сделать их отсроченными изначально сразу? Таким образом, существующий код не должен подвергаться воздействию. Ваш сеанс «удаления» сделает все ограничения отложенными. Затем удалите из родительского, удалите из дочернего, где запись не была в родительском, удалите из внука, где в дочернем элементе нет соответствия и т. Д.

2 голосов
/ 20 апреля 2010

Это отличное упражнение для развития ваших навыков PL / SQL и общих знаний Oracle!

Вам необходимо идентифицировать все ограниченные столбцы во всех таблицах с отношениями, исходящими из вашей основной таблицы. Вы можете получить всю необходимую информацию из двух представлений: ALL_CONSTRAINTS и ALL_CONS_COLUMNS . (Если все таблицы находятся в той же схеме, что и пользователь, выполняющий сценарий, вы можете использовать USER_CONSTRAINTS и USER_CONS_COLUMNS, если хотите)

Этот запрос найдет все ограничения внешнего ключа, которые ссылаются на данную таблицу (CUSTOMER в этом примере):

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

Теперь для каждого из результатов этого запроса вы можете использовать столбец CONSTRAINT_NAME, чтобы получить имя таблицы и столбца, которые можно использовать для написания операторов DELETE, чтобы удалить все дочерние строки во всех дочерних таблицах.

В этом примере показано имя таблицы и столбца для ограничения с именем CUSTOMER_FK1

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

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

Чтобы вы могли сделать, например:

DELETE FROM reservation
 WHERE customer_uid = 00153464

или

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

Но ваши дочерние таблицы также имеют дочерние таблицы, поэтому, конечно, вам придется сначала удалить эти дочерние строки (назовите их строками внуков). Предположим, что существует таблица с именем booking_detail, имеющая отношение внешнего ключа к резервированию, ваша команда удаления дляservation_detail может выглядеть следующим образом:

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')

И если в booking_detail тоже есть дети ... вы поняли. Конечно, вы можете использовать объединения вместо вложенных запросов, но принцип тот же: чем глубже уровень ваших зависимостей, тем сложнее становятся ваши команды удаления.

Итак, теперь вы знаете, как это сделать, задача состоит в том, чтобы написать общий сценарий PL / SQL, чтобы удалить все дочерние строки, строки внуков, строки правнуков ... (до бесконечности) для любой данной таблицы из вверх дном. Вам нужно будет использовать рекурсию . Должна быть веселая программа для написания!

(Последнее редактирование: удален сценарий; см. Мой другой ответ для окончательного решения.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...