尝试理解 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 概念
,以便我可以继续以更好的方式理解它们,并且一些小的解释会大有帮助,我也将不胜感激。
问题:
根据您的经验,上述方法是编写报告函数的通用方法吗?还是有一些最佳实践?
I am new to PLSQL
and I have this huge plsql function which am trying to understand and am having hard time understanding the flow and so I would really appreciate if anyone can run me through the big pieces so that I can understand the flow. Guidance would be highly appreciated.
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;
And would also appreciate if someone can let me know as to what are the important plsql concepts
used here so that I can go ahead and understand them in a better way and some small explanation would go long way.
Question:
Is above approach generic way of writing reporting function in your experience or there are some best practices in doing so ?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
解决此类问题的一个好方法是在调试器中单步执行代码。 Oracle 提供了一个名为 SQL Developer 的免费工具,它带有一个 dubugger,所以你可以使用它。
乍一看,这段代码看起来像是在构建一个动态 SQL 语句来获取一些数据。我所说的动态是指 SQL 语句是在运行时构建的,并且该过程基于传入的参数构建 where 子句。
最后,他们正在做:
基本上将查询结果放入引用游标中,这允许客户端获取结果数据。
顺便说一句,以这种方式使用动态 SQL 对性能来说非常糟糕,因为它会导致硬解析。您可以在 此链接。解决方案是使用上下文,因此您最终会获得绑定变量的优点并避免硬解析(这在该链接中进行了讨论)。
编辑
实际上,他们正在将结果数据通过管道传输到集合变量中。请参阅此链接,然后搜索“分配表函数的结果”。
A good way to figure stuff like this out is to step through the code in the debugger. Oracle offers a free tool called SQL Developer, which comes with a dubugger, so you could use that.
At initial glance, this code looks like it's building a dynamic SQL statement to fetch some data. By dynamic, I mean that the SQL statement is built at runtime, and the procedure is constructing the where clause based on passed in parameters.
At the end, they're doing:
Which basically puts the result from the query in a ref cursor, and this allows the client to get the result data.
By the way, using dynamic SQL in this manner is very bad for performance since it results in a hard parse. You can read more about hard parses and why they are evil at this link. The solution is to use a context, so you end up with the advantages of bind variables and avoid the hard parse (this is discussed at that link).
EDIT
Actually, they are pipelining the result data into a collection variable. See this link, and search for "Assigning the Result of a Table Function".
它看起来像一个报告功能。它构建一个包含一些条件的 SQL 语句(WHERE 中的某些元素取决于参数)。
查询本身看起来相当复杂。它使用
with
构造,让您可以在查询中定义内联视图的类型。这本身更多的是 SQL(也许是 Oracle SQL)功能,而不是 PLSQL。然后,在游标中打开查询(在字符串变量中构建)。游标可以看作是一种遍历查询结果的工具,查询是在循环中完成的。
然后,游标中的变量被放入
v_row
的属性中。v_row
被声明为记录类型
。它是一个可以代表记录的对象。该对象通过管道传送到输出,这意味着该函数实际上返回一个记录集,这意味着您可以在查询中调用它,如下所示:[编辑]
按请求添加:如何执行的示例plsql 中的查询,获取结果并返回它们,而不将查询构建为字符串。功能是从心打出来的,以原创为基础。我当然无法测试它,因为我没有正确的数据库。实际上,我现在根本没有数据库或编辑器,所以请阅读拼写错误。 ;)
It looks like a reporting function. It builds an SQL statement with some conditions in it (some elements in the WHERE depend on parameters).
The query itself looks quite complex. It uses the
with
construct that lets you define sort of an inline view inside the query. That in itself is more an SQL (maybe Oracle SQL) feature and not PLSQL.Then, the query (which is built up in a string variable) is opened in a cursor. A cursor can be seen as a tool to traverse the result of a query which is done here in a loop.
Then, variables from the cursor are put in properties of
v_row
.v_row
is declared as arecord type
. It is an object that can respresent a record. The object is piped to the output, meaning that this functions actually returns a recordset, meaning you can call it in a query, like this:[edit]
Addition on request: An example of how you can execute the query within plsql, fetch the results and return them, without building the query as a string. Function is typed from the heart, based on the original. I cannot test it ofcourse, because I don't have the correct database. Actually I don't have a database or editor at all at the moment, so please read between the typo's. ;)
您确定您已发布所有内容吗?因为按照现在的情况,它永远不会运行成功。许多变量被声明但从未使用。例如,
e_sql
正在执行,但从未分配值。我希望你不要试图通过查看这段代码来学习 PL/SQL,因为几乎每一行代码都让我感到畏缩。特别是将变量声明为 LONG(您不应该再使用它)、该记录的使用以及笨拙的日期处理。哎哟,哎哟,哎哟!最重要的是,如果有人编写这样的代码,那么有人肯定需要学会评论他正在做的事情。
更新
我重写了该函数,现在它已经完成了。我用这些辅助对象对其进行了测试:
您的函数的两个参数未使用,因此我删除了它们。所有参数的类型似乎都有错误,所以我也修复了这个问题。此外,我删除了所有不必要的变量,并使您的查询使用绑定变量。这很重要,因为 Oracle 将每个唯一的解析语句存储在共享池中以供重用。但是通过粘贴参数,您使每个语句都是唯一的,从而导致硬解析并填满共享池。
您的函数是一个管道函数,在您的情况下这似乎有点矫枉过正,因为您的结果集不会很大,因为您是按月分组的。所以你每个月只能得到一行。我把它留在原处。该查询访问了 ap_main 表六次,其中一次就足够了。您可能会通过性能提升注意到这一点。我仍然担心的是日期处理。最初的编码员无法决定是否要使用字符串或日期来处理日期。当然,您应该使用日期来处理日期。许多被调用的转换例程可能会以某种方式被跳过。无论如何...这是新函数:
并证明新函数有效:
更新 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
如果您还有其他问题,请随时提问。
问候,
抢。
Are you sure you posted everything? Because as it is now, it will never run successfully. Lot of variables are declared but never used.
e_sql
for example is being executed but is never assigned a value.I hope for you that you will not try to learn PL/SQL by looking at this code, because just about every line of code makes me cringe. Especially declaring variables as LONG (which you should never use anymore), the use of that record, and that clumsy date handling. Ouch, ouch, ouch! And above all, if someone writes code like that, that someone should definitely need to learn to comment what he's doing.
Update
I rewrote the function, now that it's complete. I tested it with these auxiliary objects:
Two parameters of your function are not used, so I removed those. All parameters have the wrong type it seems, so I fixed that as well. Furthermore, I removed all the unnecessary variables and made your query use bind variables. This is important, because Oracle stores each unique parsed statement in the shared pool for reuse. But by glueing in your parameters, you made every statement unique, causing a hard parse and filling up your shared pool.
Your function is a pipelined function, which seems like overkill in your situation, since your resultset won't be very large, because you are grouping by the month. So you'll only get one row per month. I left that in place. The query accessed your ap_main table six times, where one time is sufficient. You'll probably notice that by a performance gain. What still worries me is the date handling. The original coder couldn't makes his mind up whether he'd like to use strings or dates to handle dates. Of course you should be using dates for handling dates. A lot of the conversion routines that are called can probably be skipped somehow. Anyways ... here is the new function:
And to prove that the new functions works:
Update 2
Here are two links to the two crucial constructs used here:
OPEN FOR statement: http://download.oracle.com/docs/cd/E11882_01/appdev.112/e17126/openfor_statement.htm#sthref1703
Pipelined functions: http://download.oracle.com/docs/cd/E11882_01/appdev.112/e17126/tuning.htm#sthref1129
As you can see in the OPEN FOR statement documentation, after FOR you specify a query, which I construct dynamically. The same was being done in your original code. Differences are that I'm using native dynamic SQL, so I can use bind variables (the variables starting with ":p_"). I did it in such a way that no matter what input values I provide, all the bind variables are present in the query. Here is a good explanation of why and how: http://www.oracle.com/technetwork/issue-archive/2009/09-jul/o49asktom-090487.html
If you have some more questions, don't hesitate to ask.
Regards,
Rob.