存储过程中的 Oracle For 循环不循环

Posted

技术标签:

【中文标题】存储过程中的 Oracle For 循环不循环【英文标题】:Oracle For Loop in Stored Procedure Not Looping 【发布时间】:2012-11-29 18:00:38 【问题描述】:

在尝试在我们的一个系统中提高 SP 的可维护性时,我决定使用循环比硬编码一组值(在本例中为表名)更好,并尝试相应地重构代码这样在系统中添加或删除表不需要编辑数组。暂且不说循环的原因和原因(我非常清楚反对它们的论点),谁能解释发生了什么?

想象两个用户 SourceUser 和 DestUser 都在同一个数据库中,每个用户的表都在同一个表空间中。 SourceUser 中的一堆存储过程将 SourceUser 中的数据填充到 DestUser 中以用于报告目的。作为其中的一部分,要运行的第一个过程会删除 DestUser 中的所有表并重新创建它们。同样,这里不讨论这样做的相对优点。

SourceUser 对 DestUser 具有删除任何表和创建任何表的权限。我们要保留 DestUser 中的一张表。所以,我在过程中构造的 SQL 如下所示:

Begin
  For T In (SELECT TABLE_NAME FROM all_tables WHERE TABLE_NAME != 'MIDBLOG' AND OWNER = sTarget_DB) Loop
    Begin
      Execute Immediate('Drop Table ' || sTarget_DB || '.' || T.TABLE_NAME);

    Exception
      When Others Then
        --Don't care if we get an exception here as most likely the table wasn't there to be dropped in the first place.
        NULL;
    End;
  End Loop;
End;

在这种情况下,sTarget_DB 设置为 DestUser,并且此代码正在针对 SourceUser 运行。

当程序运行时,我发现没有表被删除(我确认有几十个表,包括一个在开始之前名为 MIDBLOG 的表)。我在 SQL Developer 调试模式下运行它,执行甚至从未进入循环内部,因为它似乎认为它没有要处理的行,但我确定 select 语句将返回几十个表名。

接下来我将其修改为:

Begin
  For T In (SELECT TABLE_NAME FROM all_tables WHERE OWNER = sTarget_DB) Loop
    Begin
      If T.TABLE_NAME != 'MIDBLOG' THEN
        Execute Immediate('Drop Table ' || sTarget_DB || '.' || T.TABLE_NAME);
      End If;
    Exception
      When Others Then
        --Don't care if we get an exception here as most likely the table wasn't there to be dropped in the first place.
        NULL;
    End;
  End Loop;
End;

运行此之后,WAS 删除的唯一表就是我不想删除的那个!更奇怪的是循环只执行了一次,就好像选择查询只返回一行一样。如果我在 SQL Developer 3.2 的调试中运行该过程,我可以看到它正在发生。我们在同事的 PC 上的 SQL Developer(可能是 3.1)上做了同样的事情,循环只执行了一次,但这次它正确地决定不删除表 MIDBLOG 并再次留下其他所有内容。

如果我在 SQL Developer 中将上述示例中的任何一个作为匿名块运行,它就会完全按照我的预期运行。我尝试了更详细的显式游标声明,结果与以前相同。我从来没有得到任何例外。

所有这些都在 Oracle 10g 企业版版本 10.2.0.4.0(64 位)上。只要在 Oracle 11g Enterprise Edition Release 11.2.0.1.0 (64bit) 上尝试过,它就可以正常工作。为什么这样一个基本要求会在两个版本中表现出如此截然不同的行为?它可以在两个版本中使用相同的代码按我想要的方式工作吗?

【问题讨论】:

关于 others 异常及其附带的注释 - 除了表不存在之外,drop table 命令命令可能失败的原因有很多。最好明确处理 -942 异常,让其他异常正常失败。 确实如此,但据我所知,实际的例外情况对手头的场景并不感兴趣 - 现在是 3 1/2 年前! 【参考方案1】:

我的猜测是问题与权限有关,而不是 Oracle 的版本。 DestUser 的权限是否通过一个数据库中的角色和另一个数据库中的直接授予授予SrcUser

在运行匿名 PL/SQL 块之前,如果先禁用角色会发生什么?

set role none;
<<run the anonymous PL/SQL block>>

如果您在代码中添加检测,针对all_tables 的查询是否会返回您期望的表集?我的猜测是,当代码失败时,它位于定义者权限存储过程中,其中过程的所有者可以通过角色访问DestUser 表。由于通过角色授予的权限在定义者的权限存储过程中不可见,这将导致循环中的 SELECT 语句返回 0 行(尽管以交互方式运行相同的查询将返回您期望的行)。另一方面,如果直接授予DestUser 表上的权限,则相同的定义者权限存储过程将成功运行。它可以在匿名 PL/SQL 块中工作。

【讨论】:

我会检查任何角色等,但在所有情况下,授权都是直接的,而不是按角色。令我困惑的是为什么一个人在 10g 服务器上获得 0 行而另一个人获得 1 行。两者都使用相同的登录名,因此具有相同的权限。我会尝试禁用角色并报告。针对 all_tables 的查询完全返回了我孤立地期望的结果,这就是为什么我不明白它在计算机和服务器之间的差异。 好的,正如你所建议的,我已经尝试在两台服务器上运行匿名块之前禁用所有角色,果然结果也是你所建议的:set roles none =>匿名块什么都不做,自行运行选择不会产生任何结果。我不得不得出结论,我们在两台服务器之间的设置存在不一致,所以如果我想坚持这一点,我需要调查我们的角色如何在我们的(和我们的客户)服务器上工作。耻辱,比一组表名更简洁的解决方案! 由于开发计划的压力,这将不得不继续作为学术撇开,但令人担忧的是,我们的用户(SourceUser 和 DestUser)被授予 DBA 角色,我认为这已经足够了,但也许 SP 是在其他系统用户的权限下执行。我不是 Oracle DBA,此时我有点不知所措,但会继续阅读它。非常感谢您的帮助。 @StevePettifer - 从安全角度来看,授予任何用户DBA 角色是一个坏主意。但是,由于 所有 角色在定义者权限存储过程中是不可见的,因此定义者权限存储过程不能使用授予DBA 角色或任何其他角色的权限。它只能使用直接授予用户的权限。 我完全同意 - 当我发现我很惊讶并且有点担心时。作为附录,我发现通过在 SP 声明中添加 authid current_user 可以对问题进行排序并确保存储过程确实继承了正确的权限以实现我想要的。这里很好地解释了这样做的目的和理论:docs.oracle.com/cd/B19306_01/appdev.102/b14261/…

以上是关于存储过程中的 Oracle For 循环不循环的主要内容,如果未能解决你的问题,请参考以下文章

Oracle存储过程游标for循环怎么写

存储过程中For循环怎么写啊?

oracle存储过程中循环for in是如何使用的

oracle存储过程for in loop的问题

oracle存储过程中循环for in是如何使用的

Oracle存储过程游标for循环怎么写