ORACLE动态SQL 存储过程

引言

在工作中,经常会遇到拼写动态sql,虽然写法不是很优美,但却无法避免。如果在后台(java 或者C#)写非常简单,拼写完直接运行就可以了。但如果是在数据库里面拼呢?因为公司经常用到,我把它总结一下,用一个简单的例子来说明。

场景:

写一个存储过程,支持动态的条件,并根据条件输出结果。

实现

CREATE OR REPLACE PROCEDURE P_TEST(V_C1  VARCHAR2, --条件1
                                   V_C2  VARCHAR2, --条件2
                                   P_CUR OUT SYS_REFCURSOR --用于输出的索引(输出表)             
                                   ) AS
  V_SQL VARCHAR2(4000) := ' '; --用于构造要执行的动态SQL
  V_CON VARCHAR2(500) := ' where 1=1  '; --用于构造条件

BEGIN


  V_SQL := '  WITH T AS   (
SELECT ''1'' AS C1 ,''1'' AS C2 FROM DUAL UNION
SELECT ''2'' AS C1 ,''2'' AS C2 FROM DUAL UNION
SELECT ''3'' AS C1 ,''3'' AS C2 FROM DUAL UNION
SELECT ''4'' AS C1 ,''4'' AS C2 FROM DUAL  )
SELECT * FROM T ';

--构造条件
  V_CON := V_CON || ' AND C1=''' || V_C1 || ''' ';
  V_CON := V_CON || ' AND C2=''' || V_C2 || ''' ';

--构造动态SQL
  V_SQL := V_SQL || V_CON;
  DBMS_OUTPUT.put_line(V_SQL); ----输出,方便测试动态生成的SQL

  OPEN P_CUR FOR V_SQL;//关键,执行并输出结果

END;

以上实现已经完成,我们在PL SQL运行一下看看结果。
在这里插入图片描述
在这里插入图片描述

实例分享

下面分再享个复杂一些的,以及写的写的过程。代码如下:

CREATE OR REPLACE PROCEDURE P_QMS_GET_MIS_RPT(MIS_TYPE        VARCHAR2, --1:3MIS 2:6MIS
                                              V_DATE          DATE, --时间条件
                                              OTHER_CONDITION VARCHAR2, --其它条件
                                              DATE_TYPE       NVARCHAR2, -- 1:年 2:月  3:周
                                              GROUP_BY        VARCHAR2, --维度 BRAND
                                              P_CUR           OUT SYS_REFCURSOR
                                              
                                              ) AS
  V_SQL        VARCHAR2(4000);
  V_CON   VARCHAR2(500):='';
  V_STRAT_DATE date;
  V_END_DATE   date;
  V_MIS_MONTH  varchar2(50);
  V_START_DATE_STR VARCHAR2(100);
  V_END_DATE_STR VARCHAR2(100);
BEGIN


  V_STRAT_DATE := Trunc(add_months(V_DATE, -3 * MIS_TYPE ), 'mm');
  V_END_DATE   := Last_Day(add_months(V_DATE, -1));
  V_MIS_MONTH  := to_char(Trunc(add_months(V_DATE, -1), 'mm'), 'yyyymm');
  
  V_START_DATE_STR:= ' to_date('''|| to_char( V_STRAT_DATE,'yyyy-MM-dd') ||''',''yyyy-MM-dd'') ';
  V_END_DATE_STR := ' to_date('''|| to_char( V_END_DATE,'yyyy-MM-dd') ||''',''yyyy-MM-dd'') ';
  
  V_CON:=V_CON || ' and ACTIVE_START>=  ' || V_START_DATE_STR ;
  V_CON:=V_CON || '  and ACTIVE_START < '|| V_END_DATE_STR || '+1';
  V_CON:=V_CON|| ' ';

   V_SQL := ' with B as --市场不良数
   (SELECT '|| GROUP_BY ||', sum(QTY) AS sc_qty
      FROM T_QMS_DB_COMMON_CACULATE cc
     where cc.CACULATE_TYPE = ''10''
       '|| V_CON ||'
     group by '|| GROUP_BY ||'),
  --销售数量
  C as
   (SELECT '|| GROUP_BY ||', sum(QTY) AS s_qty
      FROM T_QMS_DB_COMMON_CACULATE cc
     where cc.CACULATE_TYPE = ''40''
       '|| V_CON ||'
     group by '|| GROUP_BY ||' ),
  --生产数量
  D as
   (SELECT '|| GROUP_BY ||', sum(QTY) AS pro_qty
      FROM T_QMS_DB_COMMON_CACULATE cc
     where cc.CACULATE_TYPE = ''30''
         '|| V_CON ||'
     group by '|| GROUP_BY ||' ),
  --销售比率
  E as
   (select d.'|| GROUP_BY ||',
           round(least(sum(c.s_qty) / sum(d.pro_qty), 1), 8) as rate,
           sum(c.s_qty) sale_qty,
           sum(d.pro_qty) pro_qty
      from D, c
     where 1 = 1 and
        d.'|| GROUP_BY ||' = c.'|| GROUP_BY ||'
     group by d.'|| GROUP_BY ||'),
  --交货数量
  F as
   (SELECT '|| GROUP_BY ||', sum(QTY) AS part_qty
      FROM T_QMS_DB_COMMON_CACULATE cc
     where cc.CACULATE_TYPE = ''20''
         '|| V_CON ||'
     group by '|| GROUP_BY ||' ),
  --交货数量*销售比率获取 =索赔零件总数
  G as
   (select F.BRAND, sum(F.part_qty * E.rate) as instrorage_qty
      from F, E
     where F.'|| GROUP_BY ||' = E.'|| GROUP_BY ||'
     group by F.'|| GROUP_BY ||'),
  --计算实绩值
  H AS
   (select G.'|| GROUP_BY ||',
           B.sc_qty,
           instrorage_qty,
           round(B.sc_qty / instrorage_qty * 1000000, 1) as rate,
           round(B.sc_qty / instrorage_qty * 1000000, 10) rate_dec
      from G
      left join B
        on G.'|| GROUP_BY ||' = B.'|| GROUP_BY ||'),
    V AS
   (SELECT TARGET_VAL, BRAND AS TARGET_TYPE_NAME
      FROM T_QMS_DB_TARGET_VAL T
     WHERE
         T.BUSI_TYPE = ''10''
       AND T.TARGET_DATE= '''||V_MIS_MONTH||''')
  
  SELECT sysdate,
         '|| GROUP_BY ||' as SUMARY_NAME,
         rate_dec,
         '''||V_MIS_MONTH||''' as MIS_MONTH,
         nvl((select target_val from V where H.'|| GROUP_BY ||' = V.target_type_name and rownum=1), 0) as target_val
         
    FROM H';
    
    
    /* with B as --市场不良数
   (SELECT BRAND, sum(QTY) AS sc_qty
      FROM T_QMS_DB_COMMON_CACULATE cc
     where cc.CACULATE_TYPE = '10'
       and ACTIVE_START>= V_STRAT_DATE and ACTIVE_START < V_END_DATE+1
     group by BRAND),
  --销售数量
  C as
   (SELECT BRAND, sum(QTY) AS s_qty
      FROM T_QMS_DB_COMMON_CACULATE cc
     where cc.CACULATE_TYPE = '40'
       and ACTIVE_START>= V_STRAT_DATE and ACTIVE_START <  V_END_DATE+1
     group by BRAND),
  --生产数量
  D as
   (SELECT BRAND, sum(QTY) AS pro_qty
      FROM T_QMS_DB_COMMON_CACULATE cc
     where cc.CACULATE_TYPE = '30'
       and ACTIVE_START>= V_STRAT_DATE and ACTIVE_START <  V_END_DATE+1
     group by BRAND),
  --销售比率
  E as
   (select d.BRAND,
           round(least(sum(c.s_qty) / sum(d.pro_qty), 1), 8) as rate,
           sum(c.s_qty) sale_qty,
           sum(d.pro_qty) pro_qty
      from D, c
     where 1 = 1
       and d.BRAND = c.BRAND
     group by d.BRAND),
  --交货数量
  F as
   (SELECT BRAND, sum(QTY) AS part_qty
      FROM T_QMS_DB_COMMON_CACULATE cc
     where cc.CACULATE_TYPE = '20'
       and ACTIVE_START>= V_STRAT_DATE and ACTIVE_START <  V_END_DATE+1
     group by BRAND),
  --交货数量*销售比率获取 =索赔零件总数
  G as
   (select F.BRAND, sum(F.part_qty * E.rate) as instrorage_qty
      from F, E
     where F.BRAND = E.BRAND
     group by F.BRAND),
  --计算实绩值
  H AS
   (select G.BRAND,
           B.sc_qty,
           instrorage_qty,
           round(B.sc_qty / instrorage_qty * 1000000, 1) as rate,
           round(B.sc_qty / instrorage_qty * 1000000, 10) rate_dec
      from G
      left join B
        on G.BRAND = B.BRAND),
    V AS
   (SELECT TARGET_VAL, BRAND AS TARGET_TYPE_NAME
      FROM T_QMS_DB_TARGET_VAL T
     WHERE
         T.BUSI_TYPE = '10'
       AND T.TARGET_DATE= V_MIS_MONTH)
  
  SELECT sysdate,
         '10' AS CACULATE_TYPE,
         '统计因子,品牌3MIS' AS REMARK,
         BRAND,
         BRAND,
         rate_dec,
         V_MIS_MONTH,
         nvl((select target_val from V where H.BRAND = V.target_type_name and rownum=1), 0) as target_val,
         V_ASYNC_ID
    FROM H
    */
   
  DBMS_OUTPUT.put_line(V_SQL); 
    

  OPEN P_CUR FOR V_SQL;

END;

经验分享

上面这个实例其实也不算复杂,但做这样的活,却是一件很头痛的事情。过程非常不好调整,特别是业务调整的时候,你想死的心都有。当然,实际使用能避免就避免吧。经过了多次经维护与编写,我总结了以下步骤。
1:整理好逻辑,最好以文字的方式把它实现的逻辑写出来贴在备注里。因为SQL本身可读性就差。下次维护连自己都可能不认识了。
2:根据第1步整理好的逻辑,写代SQL,变量部分先写死。保证能运行通过。
3:整理并定义变量。
4:拼写SQL主体,条件。用变量替换写死的代码。
5:执行,输出。一般这个过程需要输出多次,先解决语法问题,再解决数据正确性问题。
发布了5 篇原创文章 · 获赞 9 · 访问量 2783

猜你喜欢

转载自blog.csdn.net/richyliu44/article/details/104319947
今日推荐