كيف تجد تبعيات مفتاح أجنبية تشير إلى سجل واحد في أوراكل؟
-
22-09-2019 - |
سؤال
لدي قاعدة بيانات Oracle كبيرة جدًا ، مع العديد من الجداول والملايين من الصفوف. أحتاج إلى حذف أحدهم ، لكنني أريد التأكد من أن إسقاطه لن يكسر أي صفوف أخرى تعتمد عليها تشير إليها كسجل مفتاح خارجي. هل هناك طريقة للحصول على قائمة بجميع السجلات الأخرى ، أو على الأقل مخططات الجدول ، التي تشير إلى هذا الصف؟ أعلم أنه يمكنني فقط محاولة حذفه بنفسي ، والاستمتاع بالاستثناء ، لكنني لن أقوم بتشغيل البرنامج النصي بنفسي وأحتاج إلى تنظيفه في المرة الأولى.
لديّ الأدوات SQL Developer من Oracle ، ومطور PL/SQL من AllRoundautomations تحت تصرفي.
شكرا مقدما!
المحلول
أنا دائما أنظر إلى المفاتيح الأجنبية لطاولة البداية وأعود في طريقي إلى العودة. عادة ما يكون لأدوات DB تبعيات أو عقدة قيود. أعلم أن مطور PL/SQL لديه طريقة لرؤية FK's ، ولكن لقد مر بعض الوقت منذ أن استخدمته ، لذلك لا يمكنني شرح ذلك ...
فقط استبدل 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
نصائح أخرى
هنا هو الحل الخاص بي لسرد جميع الإشارات إلى جدول:
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'
;
مع هذا الحل ، لديك أيضًا معلومات عن عمود الجدول الذي يشير إلى عمود الجدول المستهدف الخاص بك (ويمكنك التصفية عليه).
لقد واجهت مشكلة مماثلة مؤخرًا ، لكنني عانيت قريبًا ، أن العثور على التبعيات المباشرة لا يكفي. لذلك كتبت استعلامًا لإظهار شجرة من تبعيات المفاتيح الخارجية متعددة المستويات:
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 <-- ADDRESS
SHIPMENT <-- PACKING_LIST
PACKING_LIST <-- PACKING_LIST_DETAILS
PACKING_LIST <-- PACKING_UNIT
PACKING_UNIT <-- PACKING_LIST_ITEM
PACKING_LIST <-- PO_PACKING_LIST
...
يمكننا استخدام قاموس البيانات لتحديد الجداول التي تشير إلى المفتاح الأساسي للجدول المعني. من ذلك يمكننا إنشاء بعض 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>
هذا المثال يغش قليلا. تم ترميز اسم المفتاح الأساسي الهدف ، ويحتوي عمود المرجع على نفس الاسم على جميع الجداول التابعة. يتم ترك إصلاح هذه المشكلات كتمرين للقارئ ؛)
كان وضع مماثل. في حالتي ، كان لدي بعض السجلات التي انتهى بها الأمر مع نفس المعرف الذي يختلف فقط حسب الحالة. أراد التحقق من السجلات التابعة الموجودة لكل منها لمعرفة أي أسهل لحذف/تحديث
يطبع ما يلي جميع سجلات الأطفال التي تشير إلى السجل المحدد ، لكل طاولة طفل مع عدد لكل مجموعة السجل/السجل الرئيسي
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;
لقد فوجئت بمدى صعوبة العثور على ترتيب التبعية للجداول على أساس العلاقات الرئيسية الخارجية. كنت بحاجة إليها لأنني أردت حذف البيانات من جميع الجداول واستيرادها مرة أخرى. إليكم الاستعلام الذي كتبته لإدراج الجداول في ترتيب التبعية. تمكنت من السيناريو الحذف باستخدام الاستعلام أدناه ، واستيراد مرة أخرى باستخدام نتائج الاستعلام بترتيب عكسي.
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;
تستخدم قيود 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;