Как найти зависимости от внешнего ключа, указывающие на одну запись в Oracle?
У меня очень большая база данных Oracle, в которой много таблиц и миллионы строк. Мне нужно удалить один из них, но хочу удостовериться, что его удаление не приведет к поломке любых других зависимых строк, которые указывают на него как запись внешнего ключа. Есть ли способ получить список всех других записей или, по крайней мере, табличных схем, указывающих на эту строку? Я знаю, что могу просто попытаться удалить его сам и поймать исключение, но я не буду запускать script сам, и мне нужно, чтобы он выполнял очистку в первый раз.
У меня есть инструменты SQL Developer от Oracle и PL/SQL Developer от AllRoundAutomations в моем распоряжении.
Спасибо заранее!
Ответы
Ответ 1
Я всегда смотрю на внешние ключи для стартовой таблицы и возвращаюсь. Инструменты БД обычно имеют узел зависимостей или ограничений. Я знаю, что у PL/SQL Developer есть способ увидеть FK, но с тех пор, как я его использовал, прошло довольно много времени, поэтому я не могу это объяснить...
просто замените XXXXXXXXXXXX на имя таблицы...
/* The following query lists all relationships */
select
a.owner||'.'||a.table_name "Referenced Table"
,b.owner||'.'||b.table_name "Referenced by"
,b.constraint_name "Foreign Key"
from all_constraints a, all_constraints b
where
b.constraint_type = 'R'
and a.constraint_name = b.r_constraint_name
and b.table_name='XXXXXXXXXXXX' -- Table name
order by a.owner||'.'||a.table_name
Ответ 2
Вот мое решение для перечисления всех ссылок на таблицу:
select
src_cc.owner as src_owner,
src_cc.table_name as src_table,
src_cc.column_name as src_column,
dest_cc.owner as dest_owner,
dest_cc.table_name as dest_table,
dest_cc.column_name as dest_column,
c.constraint_name
from
all_constraints c
inner join all_cons_columns dest_cc on
c.r_constraint_name = dest_cc.constraint_name
and c.r_owner = dest_cc.owner
inner join all_cons_columns src_cc on
c.constraint_name = src_cc.constraint_name
and c.owner = src_cc.owner
where
c.constraint_type = 'R'
and dest_cc.owner = 'MY_TARGET_SCHEMA'
and dest_cc.table_name = 'MY_TARGET_TABLE'
--and dest_cc.column_name = 'MY_OPTIONNAL_TARGET_COLUMN'
;
С помощью этого решения у вас также есть информация о том, в каком столбце таблицы есть ссылка на какой столбец вашей целевой таблицы (и вы можете ее фильтровать).
Ответ 3
Недавно у меня была аналогичная проблема, но вскоре я обнаружил, что найти прямые зависимости недостаточно. Поэтому я написал запрос, чтобы показать дерево многоуровневых зависимостей внешнего ключа:
SELECT LPAD(' ',4*(LEVEL-1)) || table1 || ' <-- ' || table2 tables, table2_fkey
FROM
(SELECT a.table_name table1, b.table_name table2, b.constraint_name table2_fkey
FROM user_constraints a, user_constraints b
WHERE a.constraint_type IN('P', 'U')
AND b.constraint_type = 'R'
AND a.constraint_name = b.r_constraint_name
AND a.table_name != b.table_name
AND b.table_name <> 'MYTABLE')
CONNECT BY PRIOR table2 = table1 AND LEVEL <= 5
START WITH table1 = 'MYTABLE';
Это дает такой результат, когда вы используете SHIPMENT как MYTABLE в моей базе данных:
SHIPMENT <-- ADDRESS
SHIPMENT <-- PACKING_LIST
PACKING_LIST <-- PACKING_LIST_DETAILS
PACKING_LIST <-- PACKING_UNIT
PACKING_UNIT <-- PACKING_LIST_ITEM
PACKING_LIST <-- PO_PACKING_LIST
...
Ответ 4
Мы можем использовать словарь данных для определения таблиц, которые ссылаются на первичный ключ рассматриваемой таблицы. Из этого мы можем сгенерировать некоторый динамический SQL для запроса этих таблиц для значения, которое мы хотим изменить:
SQL> declare
2 n pls_integer;
3 tot pls_integer := 0;
4 begin
5 for lrec in ( select table_name from user_constraints
6 where r_constraint_name = 'T23_PK' )
7 loop
8 execute immediate 'select count(*) from '||lrec.table_name
9 ||' where col2 = :1' into n using &&target_val;
10 if n = 0 then
11 dbms_output.put_line('No impact on '||lrec.table_name);
12 else
13 dbms_output.put_line('Uh oh! '||lrec.table_name||' has '||n||' hits!');
14 end if;
15 tot := tot + n;
16 end loop;
17 if tot = 0
18 then
19 delete from t23 where col2 = &&target_val;
20 dbms_output.put_line('row deleted!');
21 else
22 dbms_output.put_line('delete aborted!');
23 end if;
24 end;
25 /
Enter value for target_val: 6
No impact on T34
Uh oh! T42 has 2 hits!
No impact on T69
delete aborted!
PL/SQL procedure successfully completed.
SQL>
Этот пример немного обманывает. Имя целевого первичного ключа является жестко запрограммированным, а столбец ссылок имеет одинаковое имя во всех зависимых таблицах. Фиксация этих проблем оставлена в качестве упражнения для читателя;)
Ответ 5
Я был удивлен, как трудно было найти порядок зависимостей таблиц на основе отношений внешнего ключа. Мне это нужно, потому что я хотел удалить данные из всех таблиц и импортировать их снова. Вот запрос, который я написал, чтобы перечислить таблицы в порядке зависимости. Я смог script удалить с помощью запроса ниже и снова импортировать, используя результаты запроса в обратном порядке.
SELECT referenced_table
,MAX(lvl) for_deleting
,MIN(lvl) for_inserting
FROM
( -- Hierarchy of dependencies
SELECT LEVEL lvl
,t.table_name referenced_table
,b.table_name referenced_by
FROM user_constraints A
JOIN user_constraints b
ON A.constraint_name = b.r_constraint_name
and b.constraint_type = 'R'
RIGHT JOIN user_tables t
ON t.table_name = A.table_name
START WITH b.table_name IS NULL
CONNECT BY b.table_name = PRIOR t.table_name
)
GROUP BY referenced_table
ORDER BY for_deleting, for_inserting;
Ответ 6
Ограничения Oracle используют табличные индексы для сравнения данных.
Чтобы узнать, какие таблицы ссылаются на одну таблицу, просто найдите индекс в обратном порядке.
/* Toggle ENABLED and DISABLE status for any referencing constraint: */
select 'ALTER TABLE '||b.owner||'.'||b.table_name||' '||
decode(b.status, 'ENABLED', 'DISABLE ', 'ENABLE ')||
'CONSTRAINT '||b.constraint_name||';'
from all_indexes a,
all_constraints b
where a.table_name='XXXXXXXXXXXX' -- Table name
and a.index_name = b.r_constraint_name;
Obs: Отключение ссылок значительно улучшает время выполнения команд DML (обновление, удаление и вставка).
Это может помочь в массовых операциях, когда вы знаете, что все данные согласованы.
/* List which columns are referenced in each constraint */
select ' TABLE "'||b.owner||'.'||b.table_name||'"'||
'('||listagg (c.column_name, ',') within group (order by c.column_name)||')'||
' FK "'||b.constraint_name||'" -> '||a.table_name||
' INDEX "'||a.index_name||'"'
"REFERENCES"
from all_indexes a,
all_constraints b,
all_cons_columns c
where rtrim(a.table_name) like 'XXXXXXXXXXXX' -- Table name
and a.index_name = b.r_constraint_name
and c.constraint_name = b.constraint_name
group by b.owner, b.table_name, b.constraint_name, a.table_name, a.index_name
order by 1;
Ответ 7
Была похожая ситуация. В моем случае у меня было несколько записей, которые заканчивались тем же идентификатором, отличающимся только регистром. Хотел проверить, какие зависимые записи существуют для каждого, чтобы знать, что было проще всего удалить/обновить
Далее выводятся все дочерние записи, указывающие на данную запись, для каждой дочерней таблицы со счетчиком для каждой комбинации таблицы/основной записи.
declare
--
-- Finds and prints out how many children there are per table and value for each value of a given field
--
-- Name of the table to base the query on
cTable constant varchar2(20) := 'FOO';
-- Name of the column to base the query on
cCol constant varchar2(10) := 'ID';
-- Cursor to find interesting values (e.g. duplicates) in master table
cursor cVals is
select id
from foo f
where exists ( select 1 from foo f2
where upper(f.id) = upper(f2.id)
and f.rowid != f2.rowid );
-- Everything below here should just work
vNum number(18,0);
vSql varchar2(4000);
cOutColSize number(2,0) := 30;
cursor cReferencingTables is
select
consChild.table_name,
consChild.constraint_name,
colChild.column_name
from user_constraints consMast
inner join user_constraints consChild on consMast.constraint_name = consChild.r_constraint_name
inner join USER_CONS_COLUMNS colChild on consChild.CONSTRAINT_NAME = colChild.CONSTRAINT_NAME
inner join USER_CONS_COLUMNS colMast on colMast.CONSTRAINT_NAME = consMast.CONSTRAINT_NAME
where consChild.constraint_type = 'R'
and consMast.table_name = cTable
and colMast.column_name = cCol
order by consMast.table_name, consChild.table_name;
begin
dbms_output.put_line(
rpad('Table', cOutColSize) ||
rpad('Column', cOutColSize) ||
rpad('Value', cOutColSize) ||
rpad('Number', cOutColSize)
);
for rRef in cReferencingTables loop
for rVals in cVals loop
vSql := 'select count(1) from ' || rRef.table_name || ' where ' || rRef.column_name || ' = ''' || rVals.id || '''';
execute immediate vSql into vNum;
if vNum > 0 then
dbms_output.put_line(
rpad(rRef.table_name, cOutColSize) ||
rpad(rRef.column_name, cOutColSize) ||
rpad(rVals.id, cOutColSize) ||
rpad(vNum, cOutColSize) );
end if;
end loop;
end loop;
end;