设为首页 收藏本站
查看: 2748|回复: 1

[经验分享] Oracle实现公式解析的算法

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2013-11-26 08:42:49 | 显示全部楼层 |阅读模式
我们在开发各种管理系统时(例如:财务、人资、统计等)都会涉及到指标的数据计算,为了系统以后的可维护性,通常我们会将需要计算的指标做成可维护公式的,这样,在以后的管理中就可以很方便的通过调整公式来改变指标的计算方法。

   这样,问题同时也就来了,如何将设置的公式解析成可计算的表达式?如何设计Oracle表结构呢?前段时间UncleToo正好开发统计系统时遇到了类似的问题,逛了很多IT论坛,找了很多相关的帖子,请教了许多大神,没有得到满意的答案,最后自己写个方法实现了解析公式的功能,执行效率还不错,这里分享给大家,希望对大家有帮助,如果你还有其他更好的方法也可以与UncleToo一起讨论。

一、表结构设计

这里我们需要三张表(为了讲解方便,我把表结构都简单化了,省略了公式解析没用到的字段,大家可以根据自己的需要加上去)

表一:指标基础表(存放公式)

这张表是我们维护指标基础属性的表,存放的是指标的完整公式。表中的ACCOUNT_ORDER是一个重要的字段,用来存放指标计算等级的,等级越高,计算越靠后,相反,等级越小,计算越靠前。这个字段在遍历指标时用到,我们会在下面函数调用的时候讲解。
create table T_ITEM  
(  
  ITEM_NAME           VARCHAR2(50),  --指标名称  
  ITEM_CODE_PREFIX    VARCHAR2(50),  --指标编码  
  CALCULATION_FORMULA VARCHAR2(1000),--公式  
  ACCOUNT_ORDER       NUMBER(10)     --计算等级  
)  
表中存放数据如下:



表二:公式拆分表(暂且这么叫吧)

这张表是存放指标公式中涉及到的指标
create table T_ITEM_FMLA  
(  
  FMLA_ITEM_ID     NUMBER(10) not null, --主键ID  
  ITEM_CODE_PREFIX VARCHAR2(50),        --公式等号左边指标  
  FMLA_ITEM_CO_PR  VARCHAR2(50)         --公式等号右边指标  
)  
表中存放数据如下:



表三:数据表

这张表是存放指标数据的,这里事先已经存放了不需要计算的指标的数据,根据这些数据计算带公式的指标
create table T_ITEM_VALUE  
(  
  ITEM_CODE_PREFIX VARCHAR2(40) not null,  --指标编码  
  DATA_DATE        DATE not null,          --数据日期  
  DATA_VALUE       NUMBER(38,15)           --指标数据  
)  
表中存放数据如下:



二、编写sql脚本。

这里我们采用Oracle自定义函数来写,方便在其他地方调用。sql中每个重点的地方都有标注,这里就不一一说明了,直接上代码:
FUNCTION get_value(itemCodePrefix     VARCHAR2,  
                   calculationFormula VARCHAR2,  
                   valueDate         date) RETURN NUMBER AS  
    n_value  NUMBER;  
    v_string VARCHAR2(4000);  
    v_sql    VARCHAR2(4000);  
    v_r      VARCHAR2(1000);  
    cursor cur1 is  
      select fm.fmla_item_co_pr, nvl(rt.data_value, 0) data_value  
        from T_ITEM_FMLA fm, T_ITEM_VALUE rt  
       where fm.fmla_item_co_pr = rt.item_code_prefix(+)  
         and rt.data_date = valueDate  
         and fm.item_code_prefix = itemCodePrefix;  
  begin  

      if calculationFormula is null then  
        --如果公式为空,这指标数据默认为0  
        n_value := 0;  
      else  
        v_string := '''' || calculationFormula || '''';  
        --循环公式指标,将指标替换成对应数据  
        for c in cur1 loop  
          v_string := 'replace(' || v_string || ',''[' || c.FMLA_ITEM_CO_PR ||  
                      ']'',''' || c.data_value || ''')';  
        end loop;  
        --将替换后的表达式字符串赋值  
        v_sql := 'select ' || v_string || ' from dual';  
        execute immediate v_sql into v_r;  
        commit;  

        --将字符串里的--替换成+,要不然会出错  
        v_r := replace(v_r,'--','+');  
        --这里最后判断一下最后的表达式里是否还存在没有替换的指标  
        if instr(v_r, '[') > 0 or instr(v_r, ']') > 0 then  
          n_value := 0;  
        else  
          --执行表达式  
          v_sql := 'select ' || v_r || ' from dual';  
          execute immediate v_sql into n_value;  
          commit;  
        end if;  
      end if;  
    --返回数据  
    return n_value;  

    --异常处理  
    exception  
      when ZERO_DIVIDE  then  
        return 0;  
  END get_value;  
参数介绍:

itemCodePrefix:需要计算的指标,即公式等号左边的指标

calculationFormula:计算公式,即公式等号右边的指标

valueDate:数据日期

三、函数调用

遍历T_ITEM表,根据计算等级排序,找到所有带公式的指标,将公式传给指标、公式、日期传给get_value函数即可得到该指标的计算值。

调用示例:
for c in (select t.* from T_ITEM t  
      where t.CALCULATION_FORMULA is not null  
          order by t.account_order asc) loop  
    n_value := get_value(c.item_code_prefix,c.calculation_formula,d_date);  

    --下面是其他操作,省略...  
end loop;  
到此,公式解析已经写好了。

当时在写这个功能的时候,还写了一个判断公式是否有除数为0的情况,后来嫌调用麻烦就没用上,直接用Oracle的exception给处理了,但是有时候我们在程序中确实需要这样的功能,所以这里UncleToo也拿出来一起分享给大家。

代码:

[sql] view plaincopy
FUNCTION fun_exist_zero(calculationFormula VARCHAR2) RETURN NUMBER AS  
    n_count    number;  
    n_rntValue number := 0;  
    n_i        number := 1;  
    n_op       number;  
    n_tmp      number;  
    v_div      VARCHAR2(100);  
    v_tmp      VARCHAR2(1000);  
    v_sql      VARCHAR2(1000);  
    n_while    number;  
  begin  
    select lengthb(calculationFormula) -  
           lengthb(replace(calculationFormula, '/', ''))  
      into n_count  
      from dual;  

    if n_count = 0 then  
      n_rntValue := 0;  
    else  
      v_tmp := calculationFormula;  
      while n_i <= n_count loop  
        n_op := instr(v_tmp, '/');  
        if substr(v_tmp, n_op + 1, 1) = '0' or instr(v_tmp, '/0')>0 then  
          n_rntValue := 1;  
          exit;  
        elsif substr(v_tmp, n_op + 1, 1) = '(' then  
          select substr(v_tmp,  
                        n_op + 2,  
                        instr(v_tmp, ')', n_op + 1) - n_op - 2)  
            into v_div  
            from dual;  

          n_tmp := lengthb(v_div) - lengthb(replace(v_div, '(', ''));  
          if n_tmp = 0 then  
            v_div := v_div;  
            v_sql := 'select ' || v_div || ' from dual';  
            execute immediate v_sql  
              into n_tmp;  
            commit;  
            if n_tmp = 0 then  
              n_rntValue := 1;  
              exit;  
            end if;  
          else  
            v_div := substr(v_tmp,  
                            n_op + 2,  
                            instr(v_tmp,  
                                  ')',  
                                  n_op + lengthb(v_div) + 2,  
                                  n_tmp) - (n_op + 1));  
            if instr(v_div, '/') > 0 then  
              n_rntValue := fun_exist_zero(v_div);  
            else  
              v_sql := 'select ' || v_div || ' from dual';  
              execute immediate v_sql  
                into n_while;  
              if n_while = 0 then  
                n_rntValue := 1;  
                exit;  
              else  
                n_rntValue := 0;  
              end if;  
            end if;  
          end if;  
        else  
          v_tmp := substr(v_tmp, n_op + 1);  
        end if;  
        n_i := n_i + 1;  
      end loop;  

    end if;  

    return n_rntValue;  
  END fun_exist_zero;  

调用时,直接将公式传进来,返回1,表示存在除数为0的情况,返回0,表示不存在,这里就不多解释了,函数只用到了循环和字符串处理函数等基本知识,有兴趣的同学可以研究研究。这个函数还不太完善,如果公式比较复杂,带有多层括号嵌套,会有问题,大家在使用的时候根据自己的实际情况继续完善完善。



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.iyunv.com/thread-10677-1-1.html 上篇帖子: oracle数据库的导入(imp)与导出(exp) 下篇帖子: oracle 热备脚本 Oracle

尚未签到

发表于 2013-12-29 18:07:30 | 显示全部楼层
爱那么短,遗忘却那么长。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表