Удаление строк из родительской и дочерней таблиц
предположим, две таблицы в Oracle 10G
TableA (Parent) --> TableB (Child)
каждая строка в таблице имеет несколько дочерних строк, связанных с ней в TableB.
Я хочу удалить определенные строки в таблице, что означает, что я должен сначала удалить связанные строки в tableB.
это удаляет дочерние записи
delete from tableB where last_update_Dtm = sysdate-30;
чтобы удалить родительские строки для строк, только что удаленных в дочерней таблице, я мог бы сделать что-то вроде этого
Delete from TableA where not exists (select 1 from tableB where tableA.key=tableB.key);
выше будет также удалить строки в дочерней таблице, где (last_update_Dtm = sysdate-30) имеет значение false. У TableA нет столбца last_update_dtm, поэтому нет способа узнать, какие строки удалить без записей в дочерней таблице.
Я мог бы сохранить ключи в дочерней таблице до удаления, но это кажется дорогостоящим подходом. Каков правильный способ удаления строк в обеих таблицах?
редактировать
чтобы лучше объяснить, что я пытаюсь достичь, следующий запрос сделал бы то, что я пытаюсь сделать, если бы не было ограничений между двумя таблицами.
Delete from tableA
Where exists (
Select 1 from tableB
where tableA.key=tableB.key
and tableB.last_update_dtm=sysdate-30)
Delete from tableB where last_update_dtm=systdate-30
3 ответов
два возможных подхода.
Если у вас есть внешний ключ, объявите его как On-delete-cascade и удалите родительские строки старше 30 дней. Все дочерние строки будут удалены автоматически.
-
основываясь на вашем описании, похоже, вы знаете родительские строки, которые хотите удалить, и должны удалить соответствующие дочерние строки. Вы пробовали SQL так?
delete from child_table where parent_id in ( select parent_id from parent_table where updd_tms != (sysdate-30)
-- Теперь удалите родительскую таблицу записи
delete from parent_table where updd_tms != (sysdate-30);
---- на основе вашего требования, похоже, вам придется использовать PL/SQL. Я посмотрю, может ли кто-то опубликовать чистое решение SQL для этого (в этом случае это определенно будет путь).
declare
v_sqlcode number;
PRAGMA EXCEPTION_INIT(foreign_key_violated, -02291);
begin
for v_rec in (select parent_id, child id from child_table
where updd_tms != (sysdate-30) ) loop
-- delete the children
delete from child_table where child_id = v_rec.child_id;
-- delete the parent. If we get foreign key violation,
-- stop this step and continue the loop
begin
delete from parent_table
where parent_id = v_rec.parent_id;
exception
when foreign_key_violated
then null;
end;
end loop;
end;
/
Если у детей есть FKS, связывающий их с родителем, то вы можете использовать DELETE CASCADE на родителе.
например
CREATE TABLE supplier
( supplier_id numeric(10) not null,
supplier_name varchar2(50) not null,
contact_name varchar2(50),
CONSTRAINT supplier_pk PRIMARY KEY (supplier_id)
);
CREATE TABLE products
( product_id numeric(10) not null,
supplier_id numeric(10) not null,
CONSTRAINT fk_supplier
FOREIGN KEY (supplier_id)
REFERENCES supplier(supplier_id)
ON DELETE CASCADE
);
удалите поставщика, и он будет delate все продукты для этого поставщика
вот полный пример, как это можно сделать. Однако вам нужны привилегии запроса flashback для дочерней таблицы.
вот настройки.
create table parent_tab
(parent_id number primary key,
val varchar2(20));
create table child_tab
(child_id number primary key,
parent_id number,
child_val number,
constraint child_par_fk foreign key (parent_id) references parent_tab);
insert into parent_tab values (1,'Red');
insert into parent_tab values (2,'Green');
insert into parent_tab values (3,'Blue');
insert into parent_tab values (4,'Black');
insert into parent_tab values (5,'White');
insert into child_tab values (10,1,100);
insert into child_tab values (20,3,100);
insert into child_tab values (30,3,100);
insert into child_tab values (40,4,100);
insert into child_tab values (50,5,200);
commit;
select * from parent_tab
where parent_id not in (select parent_id from child_tab);
теперь удалить набор детей (с родителями 1,3 и 4, но не 5).
delete from child_tab where child_val = 100;
затем получите parent_ids из текущего зафиксированного состояния child_tab (т. е. как они были до вашего удаления) и удалите те, которые ваш сеанс не удалил. Это дает вам подмножество, которое было удаленный. Затем вы можете удалить их из parent_tab
delete from parent_tab
where parent_id in
(select parent_id from child_tab as of scn dbms_flashback.get_system_change_number
minus
select parent_id from child_tab);
"зеленый" все еще там (так как у него все равно не было записи в дочерней таблице), а "красный" все еще там (так как у него все еще есть запись в дочерней таблице)
select * from parent_tab
where parent_id not in (select parent_id from child_tab);
select * from parent_tab;
это экзотическая / необычная операция, поэтому, если бы я это делал, я, вероятно, был бы немного осторожен и заблокировал как дочерние, так и родительские таблицы в эксклюзивном режиме в начале транзакции. Кроме того, если бы детский стол был большим, это не было бы особенно performant поэтому я бы выбрал решение PL/SQL, такое как Rajesh.