试图理解PLSQL功能

时间:2010-12-29 20:29:52

标签: sql oracle plsql

我是PLSQL的新手,我有这个庞大的plsql函数,我正在努力理解并且很难理解这个流程,所以我真的很感激,如果有人能让我通过这些大片让我可以理解流程。指导将受到高度赞赏。

      FUNCTION analysis(            
        REGION_ID_P                 VARCHAR2,
        COUNTRY_ID_P            VARCHAR2 ,
        SUB_REGION_ID_P         VARCHAR2 ,
        CUSTOMER_TYPE_ID_P          VARCHAR2 ,
        RECEIVED_FROM_DATE_P        VARCHAR2 ,
        RECEIVED_TO_DATE_P          VARCHAR2,
        CUSTOMER_ID_P           VARCHAR2 ,
        PRIORITY_ID_P               VARCHAR2,
        WORK_GROUP_ID_P       VARCHAR2,
        CITY_ID_P VARCHAR2,
        USER_ID_P               VARCHAR2            
  )  RETURN ANALYSIS_REPORT_TAB_TYPE pipelined
  IS
          with_sql LONG;
          e_sql LONG;
          where_sql LONG;
          group_by_sql LONG;
          curent_date Date;
      v_row ANALYSIS_REPORT_ROW_TYPE := ANALYSIS_REPORT_ROW_TYPE(
                  NULL,
                  NULL,
                  NULL,
                  NULL,
                  NULL,
                  NULL,
                  NULL,
                  NULL
      );
       TYPE rectyp IS REF CURSOR;                                                                                                                                                                                                   -- define weak REF CURSOR type
       rrc_rectyp                    rectyp;

       TYPE recordvar IS RECORD(
    MONTHS        VARCHAR2(100),
  ORDERBY_MONTHS VARCHAR2(100),
    REQ_RECEIVED  NUMBER(9,2),
    REQ_STILL_OPEN NUMBER(9,2),
    REQ_AWAIT_ACCEPTANCE NUMBER(9,2),
    REQ_WITH_ATT NUMBER(9,2),
    REQ_CLOSED NUMBER(9,2),
    REQ_CANCELLED NUMBER(9,2)
       );
       res_rec                       recordvar;
  BEGIN

    select sysdate +substr(to_char(systimestamp, 'tzr'),3,1)/24 into curent_date from dual;
                where_sql := ' AND 1=1 ';
        IF COUNTRY_ID_P IS NOT NULL THEN
                  where_sql := where_sql ||' AND x.country_id ='|| COUNTRY_ID_P;
                END IF;
                IF SUB_REGION_ID_P IS NOT NULL THEN
                  where_sql := where_sql ||' AND x.SUB_REGION_ID ='|| SUB_REGION_ID_P;
                END IF;  
                IF CUSTOMER_TYPE_ID_P IS NOT NULL THEN
                  where_sql := where_sql ||' AND x.CUSTOMER_TYPE_ID ='|| CUSTOMER_TYPE_ID_P;
                END IF;
                IF RECEIVED_FROM_DATE_P IS NOT NULL THEN
                        where_sql := where_sql||' AND convert_time(received_date, ''GMT'', ''GMT'') >= convert_time(trunc(to_date('''||RECEIVED_FROM_DATE_P||''',''dd/mm/yyyy HH24:MI:SS'')), ''Europe/Paris'', ''GMT'')';
                END IF;
                IF RECEIVED_TO_DATE_P IS NOT NULL  THEN
                        where_sql := where_sql||' AND convert_time(received_date, ''GMT'', ''GMT'') <= convert_time(trunc(to_date('''||RECEIVED_TO_DATE_P||''',''dd/mm/yyyy HH24:MI:SS'')), ''Europe/Paris'', ''GMT'')';
                END IF;
                IF CUSTOMER_ID_P IS NOT NULL THEN
                  where_sql := where_sql||' AND x.CUSTOMER_ID in(select CUSTOMER_ID from lk_customer where upper(CUSTOMER_NAME) like upper('''||CUSTOMER_ID_P||'%''))';
                END IF;  
                IF PRIORITY_ID_P IS NOT NULL THEN
                   where_sql := where_sql ||' AND x.PRIORITY_ID ='|| PRIORITY_ID_P;
                END IF;    
                IF WORK_GROUP_ID_P IS NOT NULL THEN
                   where_sql := where_sql ||' AND x.WORKGROUP_ID ='|| WORK_GROUP_ID_P;
                END IF;                     
                IF CITY_ID_P IS NOT NULL THEN
                  where_sql := where_sql ||' AND x.CITY_ID = ' || CITY_ID_P;
                END IF;     
    group_by_sql := ' group by to_char(convert_time(received_date, ''GMT'', ''Europe/Paris''),''mm/YYYY''),to_char(convert_time(received_date, ''GMT'', ''Europe/Paris''),''yyyy/mm'')';                    

    with_sql := 'with
               b AS (select cep_work_item_no from ap_main where req_accept_date is null and  ecep_ap_utils.f_business_days(received_date,'''||curent_date||''')>30),
           e AS (select cep_work_item_no from ap_main where status_id=1 and req_accept_date is not null and stage_ID != 10 and stage_Id !=4 and ecep_ap_utils.f_business_days(received_date,'''||curent_date||''')>30),
           --f AS  (select cep_work_item_no from ap_main  where received_date is not null),
           m AS (select cep_work_item_no from ap_main  where received_date is not null and status_id=1), 
         n AS (select cep_work_item_no from ap_main  where  status_id=2),
         o AS (select cep_work_item_no from ap_main  where  status_id=3)';

--e_sql := ' SELECT MONTHS, REQ_RECEIVED,REQ_STILL_OPEN, REQ_AWAIT_ACCEPTANCE, REQ_WITH_ATT from (';
--e_sql := with_sql;
    e_sql := with_sql||' select   to_char(convert_time(received_date, ''GMT'', ''Europe/Paris''),''mm/YYYY'') MONTHS, to_char(convert_time(received_date, ''GMT'', ''Europe/Paris''),''yyyy/mm'') ORDERBY_MONTHS,
        count(x.cep_work_item_no)  REQ_RECEIVED,
        count(m.cep_work_item_no) REQ_STILL_OPEN,count(b.cep_work_item_no) REQ_AWAIT_ACCEPTANCE,count(e.cep_work_item_no) REQ_WITH_ATT,
  count(n.cep_work_item_no) REQ_CLOSED, count(o.cep_work_item_no) REQ_CANCELLED
        from ap_main x,m,b,e,n,o where  x.cep_work_item_no=m.cep_work_item_no(+)
        and x.cep_work_item_no = b.cep_work_item_no(+) and x.cep_work_item_no=e.cep_work_item_no(+) and 
  x.cep_work_item_no=n.cep_work_item_no(+) and x.cep_work_item_no=o.cep_work_item_no(+)
        and x.received_date is not null';
e_sql := e_sql|| where_sql||group_by_sql;

           OPEN rrc_rectyp FOR e_sql;
              LOOP
                 FETCH rrc_rectyp INTO  res_rec;
                 EXIT WHEN rrc_rectyp%NOTFOUND;
                      v_row.MONTHS      := res_rec.MONTHS ;
                      v_row.ORDERBY_MONTHS      := res_rec.ORDERBY_MONTHS ;
                      v_row.REQ_RECEIVED        := res_rec.REQ_RECEIVED;
                      v_row.REQ_STILL_OPEN      := res_rec.REQ_STILL_OPEN;
                      v_row.REQ_AWAIT_ACCEPTANCE        := res_rec.REQ_AWAIT_ACCEPTANCE;
                      v_row.REQ_WITH_ATT        := res_rec.REQ_WITH_ATT;
                      v_row.REQ_CLOSED      := res_rec.REQ_CLOSED;
                      v_row.REQ_CANCELLED       := res_rec.REQ_CANCELLED;
                  pipe ROW(v_row);

              END LOOP;
              RETURN;                    
  END analysis;

如果有人能让我知道这里使用的重要plsql concepts是什么让我能够继续以更好的方式理解它们并且一些小的解释会有很长的路要走。

问题:

上述方法是根据您的经验编写报告功能的通用方法,还是有一些最佳实践?

3 个答案:

答案 0 :(得分:5)

它看起来像一个报告功能。它构建了一个SQL语句,其中包含一些条件(WHERE中的某些元素依赖于参数)。

查询本身看起来很复杂。它使用with构造,允许您在查询中定义内联视图的排序。这本身就是一个SQL(可能是Oracle SQL)功能,而不是PLSQL。

然后,在游标中打开查询(在字符串变量中构建)。可以将游标视为遍历查询结果的工具,该查询在循环中完成。

然后,光标中的变量将放在v_row的属性中。 v_row被声明为record type。它是一个可以重新呈现记录的对象。对象是管道到输出,这意味着这个函数实际上返回一个记录集,这意味着你可以在查询中调用它,如下所示:

select * from table(monthly_analysis(<parameters>))

[编辑]

根据请求添加:如何在plsql中执行查询,获取结果并返回结果,而不将查询构建为字符串。功能是根据原始内容从心脏输入的。我无法测试它,因为我没有正确的数据库。其实我现在根本没有数据库或编辑器,所以请在拼写错误之间阅读。 ;)

create function Analysis2(
  REGION_ID_P            VARCHAR2,
  COUNTRY_ID_P           VARCHAR2,
  SUB_REGION_ID_P        VARCHAR2,
  CUSTOMER_TYPE_ID_P     VARCHAR2,
  RECEIVED_FROM_DATE_P   VARCHAR2,
  RECEIVED_TO_DATE_P     VARCHAR2,
  CUSTOMER_ID_P          VARCHAR2,
  PRIORITY_ID_P          VARCHAR2,
  WORK_GROUP_ID_P        VARCHAR2,
  CITY_ID_P              VARCHAR2,
  USER_ID_P              VARCHAR2)
return
  ANALYSIS_REPORT_TAB_TYPE
is
  V_RESULTSET ANALYSIS_REPORT_TAB_TYPE;
begin
  -- I hope the 'with' construct is supported within PLSQL. I don't have it here on my home laptop so I can't test it.
  with
    b AS (select cep_work_item_no from ap_main where req_accept_date is null and  ecep_ap_utils.f_business_days(received_date,''''||curent_date||'''')>30),
    e AS (select cep_work_item_no from ap_main where status_id=1 and req_accept_date is not null and stage_ID != 10 and stage_Id !=4 and
    ecep_ap_utils.f_business_days(received_date,''''||curent_date||'''')>30),
    --f AS  (select cep_work_item_no from ap_main  where received_date is not null),
    m AS (select cep_work_item_no from ap_main  where received_date is not null and status_id=1), 
    n AS (select cep_work_item_no from ap_main  where  status_id=2),
    o AS (select cep_work_item_no from ap_main  where  status_id=3)
  select
    -- You can actually use the record type constructor here to return 
    -- a specific record type instead of a bunch of loose fields
    ANALYSIS_REPORT_REC_TYPE(
      to_char(convert_time(received_date, 'GMT', 'Europe/Paris'),'mm/YYYY') MONTHS, 
      to_char(convert_time(received_date, 'GMT', 'Europe/Paris'),'yyyy/mm') ORDERBY_MONTHS,
      count(x.cep_work_item_no) REQ_RECEIVED,
      count(m.cep_work_item_no) REQ_STILL_OPEN,
      count(b.cep_work_item_no) REQ_AWAIT_ACCEPTANCE,
      count(e.cep_work_item_no) REQ_WITH_ATT,
      count(n.cep_work_item_no) REQ_CLOSED, 
      count(o.cep_work_item_no) REQ_CANCELLED)
  bulk collect into
    V_RESULTSET
  from 
    ap_main x,m,b,e,n,o 
  where 
    x.cep_work_item_no=m.cep_work_item_no(+)
    and x.cep_work_item_no = b.cep_work_item_no(+) and x.cep_work_item_no=e.cep_work_item_no(+) and 
    x.cep_work_item_no=n.cep_work_item_no(+) and x.cep_work_item_no=o.cep_work_item_no(+)
    and x.received_date is not null
    /* Additional where, based on input goes below. I did two, but you get the point */
    AND (COUNTRY_ID_P is null or x.country_id = COUNTRY_ID_P)
    AND (SUB_REGION_ID_P is null or x.SUB_REGION_ID = SUB_REGION_ID_P)
    -- and etc
  group by 
    to_char(convert_time(received_date, 'GMT', 'Europe/Paris'),'mm/YYYY'),
    to_char(convert_time(received_date, 'GMT', 'Europe/Paris'),'yyyy/mm');

  -- The entire resultset of the query is now stored in V_RESULTSET
  -- It can actually be looped using a loop like this:
  -- for i in V_RESULTSET.first..V_RESULTSET.last loop
  --   DBMS_OUTPUT.PUT_LINE(V_RESULTSET(i).Whateverfield);
  -- end loop;

  -- But its not needed. The actual query is all this function does, so return its result

  return V_RESULTSET;
end;

答案 1 :(得分:3)

你确定你发布了一切吗?因为现在,它永远不会成功运行。声明了许多变量但从未使用过。例如,e_sql正在执行,但从未分配过值。

我希望你不要试图通过查看这段代码来学习PL / SQL,因为几乎每一行代码都让我感到畏缩。特别是将变量声明为LONG(您不应再使用它),使用该记录,以及笨拙的日期处理。哎哟,哎哟!最重要的是,如果有人写这样的代码,那么某人肯定需要学会评论他正在做的事情。


更新

我重写了这个功能,现在已经完成了。我用这些辅助对象测试了它:

SQL> create table ap_main
  2  ( cep_work_item_no number
  3  , received_date    date
  4  , req_accept_date  date
  5  , status_id        number
  6  , stage_id         number
  7  , country_id       number
  8  , sub_region_id    number
  9  , customer_type_id number
 10  , customer_id      number
 11  , priority_id      number
 12  , workgroup_id     number
 13  , city_id          number
 14  )
 15  /

Table created.

SQL> insert into ap_main
  2  select 1,sysdate,sysdate,1,4,1,1,1,1,1,1,1 from dual union all
  3  select 2,sysdate,sysdate,1,4,1,1,1,1,1,1,1 from dual union all
  4  select 3,sysdate,sysdate,1,5,1,1,1,1,1,1,1 from dual union all
  5  select 4,sysdate,sysdate,1,5,1,1,1,1,1,1,1 from dual union all
  6  select 5,sysdate,sysdate,2,5,1,1,1,1,1,1,1 from dual union all
  7  select 6,sysdate-31,sysdate-31,1,5,1,1,1,1,1,1,1 from dual union all
  8  select 7,sysdate-31,sysdate-31,1,5,1,1,1,1,1,1,1 from dual union all
  9  select 8,sysdate-31,sysdate-31,3,5,1,1,1,1,1,1,1 from dual
 10  /

8 rows created.

SQL> create table lk_customer (customer_id,customer_name)
  2  as
  3  select 1, 'Anna' from dual union all
  4  select 2, 'Bob' from dual
  5  /

Table created.

SQL> create type analysis_report_row_type as object
  2  ( months               varchar2(7)
  3  , orderby_months       varchar2(7)
  4  , req_received         number
  5  , req_still_open       number
  6  , req_await_acceptance number
  7  , req_with_att         number
  8  , req_closed           number
  9  , req_cancelled        number
 10  )
 11  /

Type created.

SQL> create type analysis_report_tab_type as table of analysis_report_row_type
  2  /

Type created.

SQL> create function convert_time
  2  ( p1 in date
  3  , p2 in varchar2
  4  , p3 in varchar2
  5  ) return date
  6  is
  7  begin
  8    return p1;
  9  end;
 10  /

Function created.

SQL> create package ecep_ap_utils
  2  as
  3    function f_business_days(p1 in date,p2 in date) return number;
  4  end ecep_ap_utils;
  5  /

Package created.

SQL> create package body ecep_ap_utils
  2  as
  3    function f_business_days(p1 in date,p2 in date) return number
  4    is
  5    begin
  6          return p2 - p1;
  7    end f_business_days;
  8  end ecep_ap_utils;
  9  /

Package body created.

未使用函数的两个参数,因此我删除了这些参数。所有参数看起来都是错误的类型,所以我也修复了它。此外,我删除了所有不必要的变量,并使您的查询使用绑定变量。这很重要,因为Oracle将每个唯一的解析语句存储在共享池中以供重用。但是通过粘贴参数,您可以使每个语句都是唯一的,从而导致难以解析并填满您的共享池。

你的函数是一个流水线函数,在你的情况下看起来有些过分,因为你的结果集不会很大,因为你按月分组。所以你每个月只能获得一排。我把它留在了原地。该查询访问了您的ap_main表六次,其中一次就足够了。你可能会注意到性能提升。让我担心的是日期处理。最初的编码员无法确定他是否想使用字符串或日期来处理日期。当然你应该使用日期来处理日期。可能会以某种方式跳过很多被调用的转换例程。无论如何......这是新功能:

SQL> create function analysis
  2  ( country_id_p         in number
  3  , sub_region_id_p      in number
  4  , customer_type_id_p   in number
  5  , received_from_date_p in date
  6  , received_to_date_p   in date
  7  , customer_id_p        in number
  8  , priority_id_p        in number
  9  , work_group_id_p      in number
 10  , city_id_p            in number
 11  ) return analysis_report_tab_type pipelined
 12  is
 13    l_current_date        date;
 14    l_refcursor           sys_refcursor;
 15    l_analysis_report_row analysis_report_row_type := analysis_report_row_type(null,null,null,null,null,null,null,null);
 16  begin
 17    select sysdate + to_number(to_char(systimestamp, 'tzh')) / 24
 18      into l_current_date
 19      from dual
 20    ;
 21    open l_refcursor for
 22      'select analysis_report_row_type
 23              ( to_char(convert_time(received_date, ''GMT'', ''Europe/Paris''),''mm/yyyy'')
 24              , to_char(convert_time(received_date, ''GMT'', ''Europe/Paris''),''yyyy/mm'')
 25              , count(cep_work_item_no)
 26              , count(case when received_date is not null and status_id=1 then 1 end)
 27              , count(case when req_accept_date is null and ecep_ap_utils.f_business_days(received_date,:p_current_date)>30 then 1 end)
 28              , count(case when req_accept_date is not null and status_id = 1 and stage_ID not in (4,10) and ecep_ap_utils.f_business_days(received_date,:p_current_date)>30 then 1 end)
 29              , count(case when status_id = 2 then 1 end)
 30              , count(case when status_id = 3 then 1 end)
 31              )
 32         from ap_main
 33        where received_date is not null ' ||
 34      case
 35      when country_id_p is null then
 36        ' and (1=1 or :p_country_id is null)'
 37      else
 38        ' and country_id = :p_country_id'
 39      end ||
 40      case
 41      when sub_region_id_p is null then
 42        ' and (1=1 or :p_sub_region_id is null)'
 43      else
 44        ' and sub_region_id = :p_sub_region_id'
 45      end ||
 46      case
 47      when customer_type_id_p is null then
 48        ' and (1=1 or :p_customer_type_id is null)'
 49      else
 50        ' and customer_type_id = :p_customer_type_id'
 51      end ||
 52      case
 53      when received_from_date_p is null then
 54        ' and (1=1 or :p_received_from_date is null)'
 55      else
 56        ' and convert_time(received_date, ''GMT'', ''GMT'') >= convert_time(trunc(:p_received_from_date), ''Europe/Paris'', ''GMT'')'
 57      end ||
 58      case
 59      when received_to_date_p is null then
 60        ' and (1=1 or :p_received_to_date is null)'
 61      else
 62        ' and convert_time(received_date, ''GMT'', ''GMT'') <= convert_time(trunc(:p_received_to_date), ''Europe/Paris'', ''GMT'')'
 63      end ||
 64      case
 65      when customer_id_p is null then
 66        ' and (1=1 or :p_customer_id is null)'
 67      else
 68        ' and customer_id in (select customer_id from lk_customer where upper(customer_name) like upper(:p_customer_id || ''%''))'
 69      end ||
 70      case
 71      when priority_id_p is null then
 72        ' and (1=1 or :p_priority_id is null)'
 73      else
 74        ' and priority_id = :p_priority_id'
 75      end ||
 76      case
 77      when work_group_id_p is null then
 78        ' and (1=1 or :p_workgroup_id is null)'
 79      else
 80        ' and workgroup_id = :p_workgroup_id'
 81      end ||
 82      case
 83      when city_id_p is null then
 84        ' and (1=1 or :p_city_id is null)'
 85      else
 86        ' and city_id = :p_city_id'
 87      end ||
 88      ' group by to_char(convert_time(received_date, ''GMT'', ''Europe/Paris''),''mm/yyyy'')
 89            , to_char(convert_time(received_date, ''GMT'', ''Europe/Paris''),''yyyy/mm'')'
 90    using l_current_date
 91    ,     l_current_date
 92    ,     country_id_p
 93    ,     sub_region_id_p
 94    ,     customer_type_id_p
 95    ,     received_from_date_p
 96    ,     received_to_date_p
 97    ,     customer_id_p
 98    ,     priority_id_p
 99    ,     work_group_id_p
100    ,     city_id_p
101    ;
102    loop
103      fetch l_refcursor into l_analysis_report_row;
104      exit when l_refcursor%notfound;
105      pipe row (l_analysis_report_row);
106    end loop;
107    return;
108  end analysis;
109  /

Function created.

并证明新功能有效:

SQL> select * from table(analysis(1,1,1,null,null,1,1,1,1))
  2  /

no rows selected

SQL> select * from table(analysis(null,null,null,null,null,null,null,null,null))
  2  /

MONTHS  ORDERBY REQ_RECEIVED REQ_STILL_OPEN REQ_AWAIT_ACCEPTANCE REQ_WITH_ATT REQ_CLOSED REQ_CANCELLED
------- ------- ------------ -------------- -------------------- ------------ ---------- -------------
12/2010 2010/12            5              4                    0            0          1             0
11/2010 2010/11            3              2                    0            2          0             1

2 rows selected.

更新2

以下是这里使用的两个关键结构的两个链接:

OPEN FOR声明:http://download.oracle.com/docs/cd/E11882_01/appdev.112/e17126/openfor_statement.htm#sthref1703

流水线功能:http://download.oracle.com/docs/cd/E11882_01/appdev.112/e17126/tuning.htm#sthref1129

正如您在OPEN FOR语句文档中所看到的,在FOR之后指定了一个动态构造的查询。您的原始代码也是如此。不同之处在于我使用的是原生动态SQL,所以我可以使用绑定变量(以“:p_”开头的变量)。我这样做是为了不管我提供什么输入值,所有绑定变量都出现在查询中。以下是对原因和方式的一个很好的解释:http://www.oracle.com/technetwork/issue-archive/2009/09-jul/o49asktom-090487.html

如果您还有其他问题,请不要犹豫。

此致 罗布。

答案 2 :(得分:2)

计算这样的东西的好方法是逐步调试调试器中的代码。 Oracle提供了一个名为SQL Developer的免费工具,它附带了一个dubugger,所以你可以使用它。

初看起来,这段代码看起来正在构建一个动态SQL语句来获取一些数据。通过动态,我的意思是SQL语句是在运行时构建的,并且该过程基于传入的参数构造where子句。

最后,他们正在做:

  

OPEN rrc_rectyp FOR e_sql

这基本上将查询的结果放在引用游标中,这允许客户端获取结果数据。

顺便说一句,以这种方式使用动态SQL对性能非常不利,因为它会导致硬分析。您可以在this链接上阅读有关硬解析以及它们为何是邪恶的更多信息。解决方案是使用上下文,因此您最终获得绑定变量的优点并避免硬解析(这在该链接中讨论)。

修改 实际上,他们正在将结果数据流水线化为集合变量。请参阅this链接,然后搜索“分配表函数的结果”。