【问题标题】:How ro store rowids in order to delete them after corresponding child records are deleted如何在删除相应的子记录后存储 rowid 以便将其删除
【发布时间】:2021-07-08 14:04:49
【问题描述】:

我想删除我通过以下选择收集的某些行:

DELETE from REMINDER 
where exists 
(
SELECT r.rowid
FROM reminder r
inner join reminder_users u on u.reminder_id = r.id
inner join device d on d.id = (regexp_replace(r.origin_values, '[^0-9]', '')) and d.NEXT_TEST_DATE_INTERNAL <> u.deadline
inner join device_test t on t.id = d.NEXT_TEST_INT_ID
where u.receipt = 0 and t.TEST_INTERNAL_EXTERNAL = 0 and r.NAME like '%Interne%' and r.NAME not like '%Externe%' and u.DEADLINE > sysdate
);

问题是,有一个表“reminder_users”包含父表“提醒”的子记录。所以我必须先用以下语句删除子记录:

DELETE from REMINDER_USERS
where exists
(
SELECT u.ROWID
from reminder_users u
inner join reminder r on r.id = u.reminder_id
inner join device d on d.id = (regexp_replace(r.origin_values, '[^0-9]', '')) and d.NEXT_TEST_DATE_INTERNAL <> u.deadline
inner join device_test t on t.id = d.NEXT_TEST_INT_ID
where u.receipt = 0 and t.TEST_INTERNAL_EXTERNAL = 0 and r.NAME like '%Interne%' and r.NAME not like '%Externe%' and u.DEADLINE > sysdate
);

当我执行第二个操作时,第一条语句显然返回零行,因为我删除了子记录。这就是为什么我正在寻找一种方法来存储第一次操作中的 rowid,然后删除子记录,最后从表“提醒”中删除以前存储的数据。

我必须在这里使用 CURSOR 吗? (注意:如果查询返回行,总会有至少 2 行受到影响)。我试图声明一个存储行的变量,但后来我得到一个“ORA-01422:精确提取返回的行数超过请求的行数”错误...

提前致谢!

【问题讨论】:

  • 如果REMINDER_USERSREMINDER 的子表,那么应该有一个外键将两个表绑定在一起。如果外键有ON DELETE CASCADE子句,那么REMINDER_USERS表会在REMINDER被删除时自动删除。
  • 我不想使用ON DELETE CASCADE,因为允许用户在REMINDER 表中执行删除。如果他们这样做了,并且REMINDER_USERS 表中有一行(指向不同的用户)不应被删除,则阻止删除。

标签: oracle plsql cursor parent-child


【解决方案1】:

reminderreminder_users 之间绝对应该有一个外键约束。如果reminder_users 中有子记录,您不应该能够reminder 中删除。这只是基本的数据库参照完整性。

如果该外键被定义为on delete cascade,那么您就完成了。从reminder 中删除将删除reminder_users 中的依赖记录。

假设您不能这样做,您可以做的一件事是先获取要删除的 id,然后再删除它们。您需要创建架构级别的type

create type id_tt as table of number;

declare
    l_reminder_ids id_tt;
begin
    select r.id 
      bulk collect into l_reminder_ids
      from reminder r
     inner join reminder_users u on u.reminder_id = r.id
     inner join device d 
             on d.id = (regexp_replace(r.origin_values, '[^0-9]', '')) 
            and d.NEXT_TEST_DATE_INTERNAL <> u.deadline
     inner join device_test t on t.id = d.NEXT_TEST_INT_ID
     where u.receipt = 0 
       and t.TEST_INTERNAL_EXTERNAL = 0 
       and r.NAME like '%Interne%' 
       and r.NAME not like '%Externe%' 
       and u.DEADLINE > sysdate
       for update;

    delete from reminder_users where reminder_id member of l_reminder_ids;

    delete from reminders where id member of l_reminder_ids;
end;

for update 子句用于防止其他会话进入并在您从提醒用户中删除时弄乱这些行。 (嗯,现在我想了想,这里可能没有必要。)这假设您尝试删除的行数是“合理的”。如果您尝试删除 2000 万行,这可能不会有很好的性能。

【讨论】:

  • 我忘了说REMINDERREMINDER_USERS 表之间当然存在外键约束。您的假设是正确的,由于给定的不确定性,我不想使用ON DELETE CASCADE。其实你的解决方案对我来说真的很不错,我会试试看!
  • 当我尝试在 SQL Developer 的 PL/SQL 脚本中使用您的代码时,我收到错误“PLS-00642:SQL 语句中不允许本地集合类型”。我把type id_tt is table of reminders.id%type;改成了type id_tt is table of reminder.id%type;,但是我觉得pl/sql表还是有问题,记录不能收集到里面?
  • 糟糕,我忘记了像这样的本地类型只能在包内工作,而不是匿名块。您需要创建架构级别type。我已经编辑了答案。
  • 非常感谢!行数是“合理的”,这意味着每天最多十行。但是“每天”也意味着,我想将此脚本安排为一个运行良好的作业。当我第二次运行脚本时,我得到一个 ora 错误“名称已被现有对象使用”。这是可以理解的。将第一行调整为 create or replace type id_tt as table of number; 可以吗?还是我也必须在最后删除类型?注意:我还必须在第 2 行添加一个 / 以使代码正常工作而不会出现编译错误...
  • 是的,类型是schema对象,就像一张表一样,所以只需要创建一次。
【解决方案2】:

如果您可以使用@EJEgyed 建议的“删除级联”,那么这就是您要走的路。但是许多 DBA(包括我自己)通常不允许这样做。副作用被认为太危险了。您可能不确切知道要删除的内容,Oracle 不会告诉您,它只是删除了。如果这是您的情况,您有几个选择。

  1. 只需重复子查询。这将完成你的 需要。
  2. 在第一次删除中使用“返回...批量收集”运行脚本, 第二个是“forall ...删除”。 (见demo)。
declare 
    type rem_list_t is table of reminder_users.rem_id%type;
    ren_list rem_list_t; 
begin 
    delete 
      from reminder_users 
     where rem_id in ( select rem_id 
                         from reminders 
                        where rem_id in  
                              (select to_char(floor(dbms_random.value(1,200)))
                                             from  dual connect by level <= 4           
                              )      
                          and message not like '%Event #6' )
     returning rem_id  
         bulk collect 
         into ren_list;
         
    dbms_output.put_line( 'Rows deleted from reminder_users:' || to_char(sql%rowcount));  
         
    forall ru in 1 .. ren_list.count
       delete 
         from reminders 
         where rem_id = ren_list(ru); 
    dbms_output.put_line( 'Rows deleted from reminders:' || to_char(sql%rowcount));          

end ;

一句警告。您当前的查询格式不正确。他们要么删除 0 行,因为子选择不符合 where 条件,要么删除 All ROWS,因为至少有 1 行满足 where 条件。 EXIST 谓词在 1 个或更多行满足条件时评估为真,但对于外部条件中的每一行都为真。演示,还包含一个示例。我没有你的表定义也没有样本数据,所以演示是建立在简单的表/查询上的,但保持你的 SQL 的相同结构。

【讨论】:

  • 感谢您的回答。你说得对,因为你提到的不确定性,我不想使用ON DELETE CASCADE
  • ...也感谢您的警告!我看到您消除了删除解决方案中所有行的危险。所以我会尝试BULK COLLECT
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-24
  • 1970-01-01
相关资源
最近更新 更多