为啥我不能在存储函数中使用 Like?

Posted

技术标签:

【中文标题】为啥我不能在存储函数中使用 Like?【英文标题】:why can not I use Like in stored functions?为什么我不能在存储函数中使用 Like? 【发布时间】:2013-04-25 06:58:54 【问题描述】:

我正在 Oracle 中编写一些存储函数。其中一个是一个非常基本的函数,它以字符串作为参数并返回另一个字符串。这是我的功能:

CREATE OR REPLACE 
FUNCTION get_mail_custcode (
    custcodeParam IN customer_table.custcode%TYPE)
    RETURN VARCHAR2
IS
    mail_rc   contact_table.email%TYPE;
BEGIN
    SELECT   cc.email
      INTO   mail_rc
      FROM   contact_table cc, customer_table cu
     WHERE       cu.customer_id = cc.customer_id
             AND cu.custcode like custcodeParam ;

    RETURN mail_rc ;
END;

所以它不工作..该功能似乎运行良好,但执行没有任何结束..该功能是工作时间和时间,我在 2 或 3 分钟后手动取消操作(此查询通常会立即给出结果)。 在一次又一次地编写查询后,我终于(随机)将cu.custcode like custcodeParam 更改为cu.custcode = custcodeParam,它正在工作!!

所以我的问题是为什么?为什么我不能在存储函数中使用like 比较器?为什么这不会出错,但函数会无限期运行。

谢谢。

【问题讨论】:

custcode 上有索引吗?您所做的是有效的,但也许 Oracle 正在为函数中的 like 版本(过滤器位于绑定变量上)选择不同的执行计划,以独立运行(未绑定并且可以决定索引访问会更快)。但除非桌子很大,否则我不会期望看到那么大的差异。确实,您需要同时跟踪两者以查看发生了什么。另外,你真的在​​parm中传递通配符吗?如果不是,like 是没有意义的,如果你是,你是不是冒着多次匹配的风险,这会导致 ORA-02112? @AlexPoole 我没有在参数中传递通配符,所以我会让相等,但我只是对这种差异感到好奇。我查看了两个版本的执行计划,但没有区别。 custcode 列上有一个索引,两种情况都用他 【参考方案1】:

在 Oracle 中,所有光标都被同等对待。函数中的查询将被视为与您通过 SQL*Plus 手动输入的查询完全相同。

但是,您的示例中可能有所不同的是 Oracle 如何处理变量。以下两个查询与优化器根本不同:

SELECT * FROM tab WHERE code LIKE 'FOO%';

variable v_code VARCHAR2(4)
EXEC :v_code := 'FOO%';
SELECT * FROM tab WHERE code LIKE :v_code;

在第一种情况下,优化器查看常量FOO% 并可以立即判断code 上的索引非常适合通过索引RANGE SCAN 快速检索行。

在第二种情况下,优化器必须考虑:V_CODE 不是常数。优化器的目的是确定一个查询的计划,该计划将由同一查询的连续执行共享(因为计算计划的成本很高)。

优化器的行为将取决于您的 Oracle 版本:

在旧的 Oracle 版本(9i 及之前)中,变量的值被忽略以构建计划。实际上,无论传递给它的价值如何,甲骨文都必须制定一个有效的计划。在您的情况下,这可能会导致完全扫描,因为 Oracle 必须采取风险最小的选择,并认为 FOO%%FOO 一样可能是一个值(后者无法通过索引范围扫描有效访问) . Oracle 在 10g 中引入了bind peeking:现在优化器可以访问变量的值并生成合适的计划。主要问题是,在大多数情况下,一个查询只能有一个计划,这意味着曾经传递给函数的第一个变量的值将强制执行所有后续执行的执行计划。如果要传递的第一个值是 %FOO,则可能会选择 FULL SCAN。 在 11g 中,Oracle 有“intelligent cursor sharing”:单个查询可以共享多个计划,在上面的示例中,值FOO% 将使用范围扫描,而%FOO 可能使用全扫描。

您使用的是什么版本的 Oracle?


更新

在 10g 和之前的版本中,如果这个函数经常在没有通配符的情况下使用,你应该重写它以确认优化器的行为:

BEGIN
   IF instr(custcodeParam, '%') > 0 OR instr(custcodeParam, '_') > 0 THEN
      SELECT cc.email
        INTO mail_rc
        FROM contact_table cc, customer_table cu
       WHERE cu.customer_id = cc.customer_id
         AND cu.custcode LIKE custcodeParam;
   ELSE
      SELECT cc.email
        INTO mail_rc
        FROM contact_table cc, customer_table cu
       WHERE cu.customer_id = cc.customer_id
         AND cu.custcode = custcodeParam;
   END IF;

   RETURN mail_rc;
END;

【讨论】:

感谢您的回复!我使用的是 Oracle 10g,所以它可以作为解释。优化器对合适的执行计划进行全面扫描?我理解正确吗? 我不认为 10g 可以构建两个不同的计划(一个用于通配符 %,一个没有),所以我认为当您的变量不需要时使用不同的查询是谨慎的LIKE 运算符(请参阅我的更新答案)。

以上是关于为啥我不能在存储函数中使用 Like?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我们不能在 SQL Server 中的函数内执行存储过程

为啥mysql创建存储函数,会是这样报错

下列函数试图求链式存储的线性表的表长,为啥是错的?

绝不能对非动态分配存储块使用free,也不能对同一块内存区同时用free释放两次,为啥?free函数原理是?

为啥这个函数序言中没有“sub rsp”指令,为啥函数参数存储在负 rbp 偏移处?

为啥这个函数序言中没有“sub rsp”指令,为啥函数参数存储在负 rbp 偏移量?