I'm trying to generate an Interactive-Report in Apex with a SQL-Statement which lies in a page item (:P10_SQL), for example: select 1 from dual.
declare
l_sql VARCHAR2(4000 CHAR);
Begin
select V('P10_SQL') into l_sql from dual;
return l_sql;
End;
The Report has the type PL/SQL Function Body returning SQL Query.
Apex shows an error
ORA-20999: WWV_FLOW_EXEC.NULL_QUERY_RETURNED_BY_FUNCTION
Somebody gets an idea?
Apex acts kind of crazy as it doesn't always want to accept code I write (which is perfectly valid), saying that function doesn't return anything. Oh well.
Therefore, my function that returns SQL query was written in stages. The following bullets represents attempts I made; all of them are OK. The final version - the one that runs right now is as simple as return :P77_SQL; (I'm on page 77)
return 'select dummy from dual'
declare
l_str varchar2(4000);
begin
l_str := case when :P77_SQL is null then 'select dummy from dual'
else :P77_SQL
end;
return l_str;
end;
return :P77_SQL;
However: you can't use interactive report, has to be a classic report because not all tables you'll have as a source will have the same column names (unless you use aliases and match number and datatypes, which is rather complicated).
But, if you use a classic report, then turn "Use generic column names" on and set desired number of generic columns (I set it to 10; you'll know what to do in your case).
My P77_SQL text item submits the page when I press ENTER, which causes the report to refresh.
A few examples:
Related
I am trying to call procedure in oracle apex but I am facing problem in displaying the output of procedure while passing the parameter as emp_id to that procedure from oracle apex. Can anyone help me?
procedure that I have written in SQL developer tool.
create or replace PROCEDURE TEST_PROC(EMP_ID1 IN Number)
As
RESULT TIMESHEET_EMPLOYEES%ROWTYPE;
BEGIN
SELECT * INTO RESULT.EMP_ID,RESULT.NAME,RESULT.LOCATION,RESULT.CITY,RESULT.COUNTRY,RESULT.EMPLOYMENT_TYPE,RESULT.EMAIL_ID,RESULT.PHONE_NUMBER,RESULT.CREATED_BY,RESULT.CREATED_ON,RESULT.UPDATED_ON,
RESULT.UPDATED_BY,RESULT.DATE_OF_JOINING,RESULT.ROLE_ID,RESULT.SUPERVISOR_ID FROM TIMESHEET_EMPLOYEES
WHERE EMP_ID=EMP_ID1;
DBMS_OUTPUT.PUT_LINE('EMP_ID:'||RESULT.EMP_ID||' '||'NAME:'||RESULT.NAME||' '||'LOCATION:'||RESULT.LOCATION||' '||'CITY:'||RESULT.CITY ||' '||'COUNTRY:'||RESULT.COUNTRY||' '||'EMPLOYMENT_TYPE:'||
RESULT.EMPLOYMENT_TYPE||' '||'EMAIL_ID:'||RESULT.EMAIL_ID||' '||'PHONE_NUMBER:'||RESULT.PHONE_NUMBER||' '||'CREATED_BY:'||RESULT.CREATED_BY||' '||'CREATED_ON:'||RESULT.CREATED_ON||' '||'UPDATED_ON:'||
RESULT.UPDATED_ON||' '||'UPDATED_BY:'||RESULT.UPDATED_BY||' '||'DATE_OF_JOINING:'||RESULT.DATE_OF_JOINING||' '||'ROLE_ID:'||RESULT.ROLE_ID||' '||'SUPERVISOR_ID:'||RESULT.SUPERVISOR_ID);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('SQLCODE'||' '||SQLCODE);
DBMS_OUTPUT.PUT_LINE('SQLERRM'||' '||SQLERRM);
END;
herein oracle apex in PLSQL code I am trying to call that procedure but I am not able to see any output.
BEGIN
TEST_PROC(:P1_EMPID);
END;
Can someone help me as I am new to APEX???
Apex can't display result of DBMS_OUTPUT.PUT_LINE.
Two simple options you might use:
add another, OUT parameter(s) to your current procedure, or
convert this procedure to a function which returns the result as a concatenated string
Both of them should return the result into an item on the page.
For example, if it were a procedure:
create or replace PROCEDURE TEST_PROC(EMP_ID1 IN Number,
par_result out varchar2
)
is
-- ... your current code goes here; I presume it does what you wanted
-- at the end, return the result as
par_result := 'EMP_ID:'||RESULT.EMP_ID||' '||'NAME:'||RESULT.NAME || ...
end;
Call it in a process as
TEST_PROC(:P1_EMPID, :P1_RESULT);
As of code you wrote: if you declared result as %rowtype, it is simpler to insert directly into it, not into every separate piece of it. For example:
select *
into result --> this
from timesheet_employees
where emp_id = emp_id1;
I have made a data block in oracle forms using Data Block Wizzard, however query does not populate the form. Even though cursor returns values and enters the loop in query procedure:
Here is the code of query procedure:
PROCEDURE PD_PDT_SCHEDULE_TYPES_QUERY(par_pd_pdt_schedule_types_tbl IN OUT gt_pd_pdt_schedule_types_tbl) IS
lc_err_msg VARCHAR2(2000);
lc_add_rec VARCHAR2(1);
lc_search_ok VARCHAR2(1);
CURSOR c_pd_pdt_schedule_types IS
SELECT pst_code,
pst_prty,
pst_mnemo,
pst_name,
pst_crt_mandatory,
pst_pdt_mnemo,
pst_type,
pst_purpose,
pst_purpose_det,
pst_ref_mnemo,
pst_hidden,
pst_ref_show,
pst_payment_show
FROM s_pd_pdt_schedule_types where pst_pdt_mnemo = 'SOME_PRODUCT';
ln_idx NUMBER := 1;
BEGIN
FOR i IN c_pd_pdt_schedule_types
LOOP
par_pd_pdt_schedule_types_tbl(ln_idx) := i;
ln_idx := ln_idx + 1;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
lc_err_msg := 'FRL_184.PD_PDT_SCHEDULE_TYPES_QUERY error: ' || SQLERRM;
RAISE_APPLICATION_ERROR(-20555, SUBSTR(lc_err_msg, 1, 2000));
END PD_PDT_SCHEDULE_TYPES_QUERY;
Here is the code of form trigger Query-Procedure:
DECLARE
bk_data FRL_184.GT_PD_PDT_SCHEDULE_TYPES_TBL;
BEGIN
frl_184.PD_PDT_SCHEDULE_TYPES_QUERY(bk_data);
PLSQL_TABLE.POPULATE_BLOCK(bk_data, 'S_PD_PDT_SCHEDULE_TYPES');
END;
First of all, make sure that PD_PDT_SCHEDULE_TYPES_QUERY actually does something - test it in SQL*Plus (or SQL Developer or any other tool you use).
QUERY-PROCEDURE trigger is created by the Wizard; it is as is, there's nothing you should do about it. Forms says that you shouldn't modify it anyway.
In order to make it work, you should edit data block's properties - go to the Palette, navigate to the "Database" section and open Query data source columns - in there, you should enter ALL columns returned by the procedure, i.e. pst_code, pst_prty, etc., along with their datatypes, length, precision ... depending on the datatype itself.
Also, modify Query data source arguments property. As your procedure doesn't accept any IN parameters, it would be just one argument (TABLE type, write its name, mode is IN OUT). If you passed some parameters to the procedure, you'd put them in here as well.
That would be it, I think.
Is it possible to select the parameters for calling a procedure from the select statement?
EXECUTE PROCEDURE_NAME(para1,para2,para3,para4);
commit;
Is it possible to select para1,para2,para3,para4 from a select query?
EXECUTE PROCEDURE_NAME((SELECT PARA1,PARA2,PARA3,PARA4 FROM TABLEA))
COMMIT;
I do not have access to modify the procedure.
As a slight variation on what #vc74 suggested, you could just replace your EXECUTE command (which, assuming this is SQL*Plus or SQL Developer anyway, is just a wrapper for an anonymous block anyway) with an explicit anonymous block:
begin
for r in (SELECT PARA1,PARA2,PARA3,PARA4 FROM TABLEA) loop
PROCEDURE_NAME(r.PARA1,r.PARA2,r.PARA3,r.PARA4);
end loop;
end;
/
(I've left the bits from your original call uppercase and the new bits lower case mostly to distinguish them.)
Using a loop just means you don't need to declare local variables and select into those. It would also allow you to process multiple rows from the table, though I see form a comment you only expect one row. However, the flip side of that is it won't complain if there are no rows, or if there is more than one row, as the variable approach would do.
You could also use a record type to avoid declaring all the parameters separately:
declare
l_row tablea%rowtype;
begin
SELECT * into l_row FROM TABLEA;
PROCEDURE_NAME(l_row.PARA1,l_row.PARA2,l_row.PARA3,l_row.PARA4);
end;
/
This now does expect exactly one row to be found in the table.
You can call the functions in sql. So if you are able to create a function in your schema then you can do the following:
create a function function_name in your schema that calls the procedure procedure_name and returns some dummy result
use this function in sql query: select function_name(para1,para2,para3,para4) from tablea
example of function:
create or replace function function_name(
p1 varchar2,
p2 varchra2,
p3 varchar2,
p4 varchar2
) return number
is
begin
procedure_name(p1,p2,p3,p4); -- here you execute the procedure
return null;
end;
I have a code in oracle pl sql, want to really want to understand how much context switching is there
If x=0 then
curserx= select a from mytable1;
Else
curserx=select a from mytable1 where id=:x;
End;
Loop
Fetch on cursorx
Select c from mytable2 where a=curserx.a;
End loop;
This is just a sample code so please pardon any text casing and logic error.
I converted your pseudo code into PL/SQL and include comments indicating where I believe you will have a context switch from the PL/SQL engine to the SQL engine.
Note that if you are querying a non-trivial number of rows, you could use FETCH BULK COLLECT INTO and retrieve multiple rows with each fetch, greatly reducing context switches.
DECLARE
l_x_value INTEGER;
l_cursor SYS_REFCURSOR;
l_fetched mytble1.a%TYPE;
BEGIN
/* context switch to open */
IF x = 0
THEN
OPEN l_cursor FOR SELECT a FROM mytable1;
ELSE
OPEN l_cursor FOR
SELECT a
FROM mytable1
WHERE id = l_x_value;
END IF;
LOOP
/* context switch per fetch */
FETCH l_cursor INTO l_fetched;
EXIT WHEN l_cursor%NOTFOUND;
/* context switch for implicit cursor */
SELECT c
INTO l_fetched
FROM mytable2
WHERE a = curserx.a;
END LOOP;
/* context switch to close */
CLOSE l_cursor;
END;
But that's not all! Remember that the context switch works both ways: SQL -> PL/SQL and PL/SQL -> SQL. You can reduce the overhead of going from SQL to PL/SQL by declaring your function with the UDF pragma (12c+) or defining it with the WITH FUNCTION clause (also 12c+). There is still a context switch but some of the work is done at compile time instead of run time.
So in the code below, for each invocation of the function from within the SELECT, there is a switch.
CREATE OR REPLACE FUNCTION full_name (first_in IN VARCHAR2,
last_in IN VARCHAR2)
RETURN VARCHAR2
IS
BEGIN
RETURN first_in || ' ' || last_in;
END;
/
DECLARE
l_name VARCHAR2 (32767);
BEGIN
SELECT full_name (first_name, last_name) INTO l_name
FROM employees
WHERE employee_id = 100;
DBMS_OUTPUT.PUT_LINE (l_name);
END;
/
Finally a cautionary note: you should do everything you can to avoid executing SQL inside a function that is then called from SQL. The standard read consistency model that works for your SQL statement will not be "carried in" to the function's SQL. In other words, if you "outer" SELECT starts running at 10:00 and runs for an hour, and at 10:05, someone deletes rows from a table that is used in both the outer query and the query in the function (and commits), those two queries will be working with different states of those tables.
I have many cursors that all return rows with the same fields: a numeric ID field and an XMLType field. Every time I access one of these cursors (each cursor has now got its own function for access), I go through the same pattern:
--query behind cursor is designed to no more than one row.
for rec in c_someCursor(in_searchKey => local_search_key_value) loop
v_id := rec.ID
v_someXMLVar := rec.XMLDataField
end loop;
if v_someXMLVar is null then
/* A bunch of mostly-standard error handling and logging goes here */
end if;
exception
/* all cursor access functions have the same error-handling */
end;
As the pattern became more obvious, it made sense to centralize it in a single function:
function fn_standardCursorAccess(in_cursor in t_xmlCursorType, in_alt in XMLType) return XMLType is
v_XMLData XMLType;
begin
dbms_application_info.set_module(module_name => $$PLSQL_UNIT, action_name => 'fn_standardCursorAccess');
loop
fetch in_cursor
into v_XMLData;
exit when in_cursor%notfound;
end loop;
/*some additional standard processing goes here*/
return v_XML;
exception
/*standard exception handling happens here*/
end;
The problem I've run into is in calling this function. I now have to call it like this:
open v_curs for select /*blah blah blah*/ where key_field = x and /*...*/;
v_data := fn_standardCursorAccess(v_curs,alt);
close v_curs;
What I'd like to do is call it like this:
open v_curs for c_getSomeData(x);
v_data := fn_standardCursorAccess(v_curs,alt);
close v_curs;
...reason being to minimize the amount of changes to my code (I don't want to have to cut/paste all these cursors to the functions that depend on them, and in the case where multiple functions depend on the same cursor, I'll have to wrap that in a new function).
Unfortunately, this does not work, Oracle returns an error saying
Error: PLS-00222: no function with name 'C_GETSOMEDATA' exists in this scope
Is what I'm trying to do even possible?
(Oracle version is 10.2)
EDIT:
I think a better way to describe what I'm doing is pass a reference to an explicit cursor to a function that will perform some common routines on the data returned by the cursor.
It appears that I cannot use an open-for statement with an explcit cursor, is there any other way to get a reference to an explicit cursor so I can pass that reference to a function? Maybe there is some other way I could approach this problem?
EDIT:
Copying and pasting from my earlier reply to R Van Rijn's reply:
I tried declaring the cursor in the package specification, and referencing it with the package name: open v_curs for PKG.c_getSomeData(x);... This gives me a new error, saying that PKG.c_getSomeData must be a function or array to be used that way.
UPDATE:
I spoke to our DBA here, he says it is not possible to have a ref cursor point to an explicit cursor. It looks like I can't do this after all. Bummer. :(
concerning the Error PLS-00222:
An identifier being referenced as a function 'c_getSomeData' was not declared or actually represents another object (for example, it might have been declared as a procedure).
Check the spelling and declaration of the identifier. Also confirm that the declaration is placed correctly in the block structure
This means you must create a function that actually returns some value(s).
Does this test script and output represent what you are trying to do? Instead of open v_curs for c_getSomeData(x); I'm setting the cursor variable = to the output from the function.
Our Test Data:
set serveroutput on
--create demo table
drop table company;
create table company
(
id number not null,
name varchar2(40)
);
insert into company (id, name) values (1, 'Test 1 Company');
insert into company (id, name) values (2, 'Test 2 Company');
insert into company (id, name) values (3, 'Test 3 Company');
commit;
Create Packages
create or replace package test_pkg as
type cursor_type is ref cursor;
function c_getSomeData(v_companyID number) return cursor_type;
end test_pkg;
/
create or replace package body test_pkg as
function c_getSomeData(v_companyID number) return cursor_type
is
v_cursor cursor_type;
begin
open v_cursor for
select id,
name
from company
where id = v_companyID;
return v_cursor;
end c_getSomeData;
end test_pkg;
/
Run Our Procedure
declare
c test_pkg.cursor_type;
v_id company.id%type;
v_name company.name%type;
begin
c := test_pkg.c_getSomeData(1);
loop
fetch c
into v_id, v_name;
exit when c%notfound;
dbms_output.put_line(v_id || ' | ' || v_name);
end loop;
close c;
end;
/
1 | Test 1 Company
PL/SQL procedure successfully completed.
I confess to finding your requirements a trifle hard to divine. You have posted a lot of code, but as I suggested in my comment, not the parts which would illuminate the problem. So possibly the following is way off-beam. But it is an interesting issue.
The following code shows how we can define a common, geneneric REF CURSOR, populate it with specific data from different queries, and then process them in a standardised fashion. Again, I apologise if this does not fit your business logic; if such is the case, please edit your question to explain where I have made a bloomer..
Here is the generic ref cursor. ...
create or replace package type_def is
type xml_rec is record (id number, payload xmltype);
type xml_cur is ref cursor return xml_rec;
end type_def;
/
and here is the standatd processor
create or replace procedure print_xml_cur
( p_cur in type_def.xml_cur )
is
lrec type_def.xml_rec;
begin
loop
fetch p_cur into lrec;
exit when p_cur%notfound;
dbms_output.put_line('ID='||lrec.id);
dbms_output.put_line('xml='||lrec.payload.getClobVal());
end loop;
close p_cur;
end print_xml_cur;
/
Two procedures which return the standard cursor with different data....
create or replace function get_emp_xml
( p_id in emp.deptno%type )
return type_def.xml_cur
is
return_value type_def.xml_cur;
begin
open return_value for
select deptno
, sys_xmlagg(sys_xmlgen(ename))
from emp
where deptno = p_id
group by deptno;
return return_value;
end get_emp_xml;
/
create or replace function get_dept_xml
( p_id in dept.deptno%type )
return type_def.xml_cur
is
return_value type_def.xml_cur;
begin
open return_value for
select deptno
, sys_xmlagg(sys_xmlgen(dname))
from dept
where deptno = p_id
group by deptno;
return return_value;
end get_dept_xml;
/
Now let's put it all together ....
SQL> set serveroutput on size unlimited
SQL>
SQL> exec print_xml_cur(get_emp_xml(40))
ID=40
xml=<?xml
version="1.0"?>
<ROWSET>
<ENAME>GADGET</ENAME>
<ENAME>KISHORE</ENAME>
</ROWSET>
PL/SQL procedure successfully completed.
SQL> exec print_xml_cur(get_dept_xml(20))
ID=20
xml=<?xml version="1.0"?>
<ROWSET>
<DNAME>RESEARCH</DNAME>
</ROWSET>
PL/SQL procedure successfully completed.
SQL>
OK, so the short answer from Oracle is: "can't be done!"
The short answer from me is: "Yeah - like Oracle is gonna stop me! So yes you can....but you need to be sneaky ... oh yes, and there is a 'but' or two....in fact...ugh!"
So, how can you pass your explicit cursor by reference? By nesting it into another cursor using the CURSOR() construct!
e.g.)
CREATE OR REPLACE package CFSDBA_APP.test_Cursor
as
function get_cursor(ed_id number) return sys_refcursor;
end;
/
CREATE OR REPLACE package body CFSDBA_APP.test_Cursor
as
function get_cursor(ed_id number) return sys_refcursor
is
test_Cur sys_refcursor;
cursor gettest is
select CURSOR( -pass our actual query back as a nested CURSOR type
select ELCTRL_EVNT_ELCTRL_DISTRCT_ID,
ELECTORAL_DISTRICT_ID,
ELECTORAL_EVENT_ID
from ELCTRL_EVNT_ELCTRL_DISTRCT
where electoral_District_id = ed_id)
from dual;
begin
open gettest;
fetch gettest into test_Cur;
return test_Cur;
end;
end;
/
So what is the problem with this solution? It has a leak! The outer gettest cursor is never closed, because we don't close it and the client will only close the reference to the nested cursor that was selected for them - not the main cursor. And we can't close it automatically because closign the parent would force closing the nested cursor that you have returned by reference - and it is entirely likely that the client has not done using it.
So we have to leave a cursor open in order to return the nested cursor.
And if the user tried calling get_Cursor again with a new value of ed_id, they would discover that the session persistence in the package meant that the cursor handle is still in use and an error would be raised.
Now, we could fix that by first checking and closing the explicit cursor:
if gettest%isopen then
close gettest;
end if;
open gettest;
fetch gettest into test_Cur;
return test_Cur;
But still - what if the user never calls this again? How long 'til Oracle garbage-collects the cursor? And how many users running how many sessions calling how many functions that use this construct will be leaving cursors open after they are done with them? Better count on a huuuuuge overhead to leave all those open cursors layin' about!
No, you would need to have the users do a callback to explicitely close it or you would be clogging the database. But doing this would require changing the scope of the explicit cursor so that both functions can access it: So we need to make it at package scope, not function scope
CREATE OR REPLACE package CFSDBA_APP.test_Cursor
as
function get_cursor(ed_id number) return sys_refcursor;
function close_cursor return sys_refcursor;
end;
/
CREATE OR REPLACE package body CFSDBA_APP.test_Cursor
as
cursor l_gettest(p_ed_id in number) is
select CURSOR(
select ELCTRL_EVNT_ELCTRL_DISTRCT_ID, ELECTORAL_DISTRICT_ID, ELECTORAL_EVENT_ID
from ELCTRL_EVNT_ELCTRL_DISTRCT
where electoral_District_id = p_ed_id)
from dual;
function get_cursor(ed_id number) return sys_refcursor
is
l_get_Cursor sys_refcursor;
begin
open l_gettest (ed_id);
fetch l_gettest into l_get_Cursor;
return l_get_cursor;
end;
function close_cursor return sys_refcursor
is
begin
if l_gettest%isopen then
close l_gettest;
end if;
return pkg_common.generic_success_cursor;
end;
end;
/
OK, plugged the leak. Except it cost us a network round trip instead of the hard parse,...oh wait - and also except embedding a bind variable into an explicit cursor declared at this level is probably going to cause scoping issues of its own which was the reason we wanted to do this in the first place!
Oh, and in a session-pooling environment can two users step on each other's cursors? IF they aren't very carefull about doing an open-fetch-close before returning the session to the pool - we could wind up with some really interesting (and impossible to debug) results!
And how much do you trust the maintainers of the client code to be extra-diligent on this? YEah - me too.
So the short answer is: Yes, with a bit of sneakiness it could be done despite Oracle saying it can't.
The better answer is: But please don't! The extra round trip and potential for memory leaks and client code errors causing data problems makes this a very scary proposition.
It appears that what I wanted to do (have an open-for statement reference an existing explicit cursor) is simply not allowed in Oracle. :(