PL/SQL 函数返回错误结果

Posted

技术标签:

【中文标题】PL/SQL 函数返回错误结果【英文标题】:PL/SQL Function returning wrong result 【发布时间】:2016-09-01 07:21:48 【问题描述】:

下面有一个 PL/SQL 函数,它在 SQL Navigator 和 SQL Developer 中返回错误结果,但在 SQL Plus 中返回正确答案。

我们有一个正在运行的脚本正在执行它并返回错误的答案,因此尝试修复它。任何人都可以看到它的任何问题吗?它对大多数人来说都很好,但我有几个人在 SQL Navigator 和 Developer 中进入并返回 null/nothing。它不会为他们填充 l_end_date,因此不会填充学分。

由于某种原因,在 SQL Plus 中工作正常。

    create or replace function mis_get_mem_lcr_credits(p_mem_no in number) RETURN  number is
    --
       v_lcr_credit   number;
       l_mem_no       number;
       l_start_date   date;
       l_end_date     date;
       l_dob          date;
       l_18th_date    date;
    --
       cursor c1 is
       select mem_no, ind_birth_dt
       from cd_individual
       where mem_no = l_mem_no
       and pkg_mem_utils.get_member_age(mem_no,ind_birth_dt) >= 18
       and nvl(ind_student_flag,'N') = 'N'
       order by mem_no, ind_birth_dt;
    --
       cursor c2 is
       select distinct m_effdt,
              m_termdt
       from cd$v_member_contracts9 cd1,
            cd_member_product_link cd2
       where cd1.mem_no = l_mem_no
       and cd1.policy_no = cd2.policy_no
       and cd1.m_effdt = cd2.mem_product_eff_dt --.2
       and (l_18th_date between cd1.m_effdt and cd1.m_termdt OR cd1.m_effdt > l_18th_date)--.3 18 at time of contract effective date
       and nvl(cd1.lapsed_to_start,'N') = 'N'
       and cd2.product_id not in (14,41,31) -- Exclude No Cover, DentalProtect and HealthProtect
       and cd2.product_id NOT IN (select distinct product_id
                                  from cd_product_options
                                  where nvl(allowed_for_lcr,'Y') = 'N')
       order by cd1.m_effdt ASC;
    --
    begin
    --
       l_mem_no       := p_mem_no;
       v_lcr_credit   := 0;
       l_dob          := null;
    --
       for crec in c1 loop            
       --
          l_dob := crec.ind_birth_dt;
       --
        --  l_18th_date := substr(to_char(l_dob,'DD/MM/YYYY'),0,6)||(substr(to_char(l_dob,'DD/MM/YYYY'),7,4)+18);

           if to_char(l_dob) like '29-02%' then
                    l_18th_date :=    add_months(to_date(l_dob+1),216 );
            else
                    l_18th_date :=   add_months(to_date(l_dob), 216);
            end if;

       --       

          for crec2 in c2 loop
          --



             if crec2.m_termdt > sysdate then
             --
                l_end_date := sysdate;            
             --         
             else
             --
                l_end_date := crec2.m_termdt;
             --
             end if;
          --
             if v_lcr_credit = 0 then --earliest contract
             --
                if l_18th_date between crec2.m_effdt and crec2.m_termdt then
                --
                   v_lcr_credit := v_lcr_credit + months_between(l_end_date,l_18th_date);
                --
                else
                --
                   v_lcr_credit := v_lcr_credit + months_between(l_end_date,crec2.m_effdt);
                --
                end if;
             --
             else
             --
                v_lcr_credit := v_lcr_credit + months_between(l_end_date,crec2.m_effdt);
             --
             end if;
          --
          end loop;
       --
       end loop;
    --
       return round(nvl(v_lcr_credit,0));

    --
    end mis_get_mem_lcr_credits;
    /
    show errors

    spool off

    exit

【问题讨论】:

【参考方案1】:

永远不要在 DATE 值上使用 to_date()

to_date()varchar 转换为 date

如果您使用 DATE 调用它,则日期值将转换为 varchar,然后再转换回它开始的日期 - 并在该过程中两次受到邪恶的隐式数据类型转换。

变量l_dob被定义为DATE所以你必须改变

add_months(to_date(l_dob+1),216 );
...
add_months(to_date(l_dob), 216);

add_months(l_dob+1,216);
...
add_months(l_dob, 216);

【讨论】:

@Horse..一个问题出于好奇,那么为什么它会在 SQL Plus 中返回正确答案??? @Raj_Te:因为那里邪恶的隐式数据类型转换恰好按您的预期工作(请参阅 Plirkee 的回答)。 谢谢,这成功了。甚至没有考虑到这一点,以为错误出在 c2 光标中,并花了几个小时查看它。 @horse..所以这意味着隐式数据类型转换只发生在 SQL PLUS 中,不会发生在 SQL Navigator 和 SQL Developer 中。对吗? @Raj_Te:不-两者都发生。但在 SQL*Plus 中,它恰好可以正常工作,而在其他工具中,它恰好将其转换为不返回正确值的某个日期。【参考方案2】:

可能是因为不同的值

NLS_TERRITORYNLS_DATE_FORMAT等在不同的环境中。 所以我建议在你的脚本中明确设置这些值。例如类似EXECUTE IMMEDIATE 'ALTER SESSION SET NLS_TERRITORY=''AMERICA''';

一些参考资料:

NLS_DATE_FORMAT

NLS_TERRITORY

【讨论】:

以上是关于PL/SQL 函数返回错误结果的主要内容,如果未能解决你的问题,请参考以下文章

如何通过 JDBC 调用返回 UDO 并解释该结果的 PL/SQL 函数?

在 pl/sql 函数中选择子句返回错误值

ORA-06504: PL/SQL: 执行时返回结果集变量的类型

返回表查询的 PL/SQL 封装函数

在 Java 中调用 Oracle PL/SQL 中的过程或函数。返回结果集 false

PL/SQL 函数在执行时抛出进程内存错误