Return N columns from a table function - oracle

I need to implement a table function, which I will submit a request with an unknown number of columns. It looks like:
SELECT * from TABLE (function())
where function, for example'SELECT x, y FROM z. I don't know how do this, so I'd like to hear some sort of way to solve, just as an idea.

I think what you are asking is you are getting multiple rows in the o/p when you are using
the function in select statement .
if i create a function as follows:
create or replace function get1job
(id in varchar2)
return varchar2 is
tittle jobs.JOB_TITLE%type;
begin
select job_title into tittle from jobs where job_id=id;
return tittle;
end get1job;
and use it in select statement .
i will write :
select get_job('AD_PRES') from dual;
i will get only one row
if i write :
select get_job('AD_PRES') from jobs;
the number of rows displayed will be equal to the number of rows in the table jobs.

Here is an example for a fully dynamic SQL, you can insert any SELECT statement and it prints out a corresponding HTML:
CREATE OR REPLACE PROCEDURE HtmlTable(sqlStr IN VARCHAR2) IS
cur INTEGER := DBMS_SQL.OPEN_CURSOR;
columnCount INTEGER;
describeColumns DBMS_SQL.DESC_TAB;
res INTEGER;
c INTEGER;
aCell VARCHAR2(4000);
BEGIN
DBMS_OUTPUT.PUT_LINE('<table>');
DBMS_SQL.PARSE(cur, sqlStr, DBMS_SQL.NATIVE);
DBMS_SQL.DESCRIBE_COLUMNS(cur, columnCount, describeColumns);
DBMS_OUTPUT.PUT_LINE('<thead><tr>');
FOR i IN 1..columnCount LOOP
DBMS_OUTPUT.PUT_LINE(' <td>'||describeColumns(i).COL_NAME||'</td>');
DBMS_SQL.DEFINE_COLUMN(cur, i, aCell, 4000);
END LOOP;
DBMS_OUTPUT.PUT_LINE('</tr></thead>');
res := DBMS_SQL.EXECUTE(cur);
DBMS_OUTPUT.PUT_LINE('<tbody>');
WHILE (DBMS_SQL.FETCH_ROWS(cur) > 0) LOOP
DBMS_OUTPUT.PUT_LINE('<tr>');
c := 1;
WHILE (c <= columnCount) LOOP
DBMS_SQL.COLUMN_VALUE(cur, c, aCell);
DBMS_OUTPUT.PUT_LINE(' <td>'||aCell||'</td>');
c := c + 1;
END LOOP;
DBMS_OUTPUT.PUT_LINE('</tr>');
END LOOP;
DBMS_OUTPUT.PUT_LINE('</tbody>');
DBMS_OUTPUT.PUT_LINE('</table>');
DBMS_SQL.CLOSE_CURSOR(cur);
END HtmlTable;
Use this as a base for your application. Then you can execute it like this:
BEGIN
HtmlTable('SELECT x, y FROM z');
END;

Related

Comparing data of any type without overloading

let's start with an example:
type Collection_t is table of varchar2(3);
Collection_v Collection_t := Collection_t('qwe', 'asd', 'yxc', 'rtz', 'fgh', 'vbn');
single_var varchar2(3) := 'yxc';
procedure get_position(Single_Value varchar2, Value_Set Collection_t) is
i integer := 1;
begin
while (i is not null) loop
if Single_Value = Value_Set(i)
then
dbms_output.put_line(i);
end if;
i := Value_Set.Next(i);
end loop;
end;
begin
get_position(single_var, Collection_v);
end;
now, question is: can I declare this procedure with 'anydata', and check if table (2ed argument) consists of the same type as first argument.
I'd assume declaration of a procedure would look like that:
procedure get_position(Single_Value anydata, Value_Set anydata) is ...
later I'd compare types, and honestly I couldn't figure it out, how to solve this problem.
From my limited understanding, you still have to encode/decode anydata into basic (or user-defined) PL/SQL types to do anything meaningful like comparing values, so you might not gain the benefits of dynamic languages like Python.
Here is an example, passing anydata as a parameter. You'll need to update anydata_to_varchar to handle data types you need.
Personally, overloading seems like a more straightforward approach, unless there is a better way to work with anydata values.
declare
type tab_anydata is table of anydata;
-- test as varchar2
/*
lookup_array tab_anydata := tab_anydata(
anydata.convertVarchar2('blah'),
anydata.convertVarchar2('meh'),
anydata.convertVarchar2('foo'),
anydata.convertVarchar2('bar')
);
lookup_value anydata := anydata.convertVarchar2('foo');
*/
-- test as date
lookup_array tab_anydata := tab_anydata(
anydata.convertDate(trunc(sysdate - 2)),
anydata.convertDate(trunc(sysdate - 0)),
anydata.convertDate(trunc(sysdate + 1)),
anydata.convertDate(trunc(sysdate + 2))
);
lookup_value anydata := anydata.convertDate(trunc(sysdate));
function anydata_to_varchar(p_what anydata)
return varchar2
is
value_type varchar2(30) := anydata.GetTypeName(p_what);
result varchar2(32767);
begin
select
case value_type
when 'SYS.VARCHAR2' then anydata.AccessVarchar2(p_what)
when 'SYS.DATE' then to_char(anydata.AccessDate(p_what), 'YYYY-MM-DD')
when 'SYS.NUMBER' then to_char(anydata.AccessNumber(p_what))
else null
end into result
from dual;
return result;
end;
function get_position(p_what anydata, p_where tab_anydata)
return number
is
pos number := -1;
what_value varchar2(30) := anydata_to_varchar(p_what);
curr_value varchar2(30);
begin
for i in 1..p_where.count
loop
curr_value := anydata_to_varchar(p_where(i));
if what_value = curr_value then
pos := i;
exit;
end if;
end loop;
return pos;
end;
begin
dbms_output.put_line('lookup type : '||anydata.GetTypeName(lookup_value));
dbms_output.put_line('lookup value : '||anydata_to_varchar(lookup_value));
dbms_output.put_line('found position: '||get_position(lookup_value, lookup_array));
end;
/
dbms_output:
lookup type : SYS.DATE
lookup value : 2022-07-20
found position: 2
db<>fiddle here

Oracle - Get function parameter value in cursor

I have a package where I have created one function like this
create or replace package pk_server_control
is
function fn_get_employees_by_consultant(consultant_id number) return number;
end;
-----------------------------------------------------------------
create or replace package body pk_server_control
is
**function fn_get_employees_by_consultant(consultant_id number)
return number
is
cursor employees is select c.CST_NAME, a.NO_OF_EMPLOYEES from NISHAN_LDS_ACCOUNT a join NISHAN_LDS_CONSULTANT c
on c.CONSULTANT_ID = a.FK1_CONSULTANT_ID where c.CONSULTANT_ID =consultant_id ;
total number := 0; **
begin
for data in employees
loop
total := total + data.NO_OF_EMPLOYEES;
end loop;
return total;
end;
end;
begin
dbms_output.put_line(pk_server_control.fn_get_employees_by_consultant(1));
end;
I need to get value from the parameter "consultant_id number" of function "fn_get_employees_by_consultant" into "consultant_id" of the cursor "". While running, it doesn't give an error also it doesn't pass the value. Please help me to get through this :)
Try this
create or replace package pk_server_control
is
function fn_get_employees_by_consultant(consultant_id number) return number;
end;
-----------------------------------------------------------------
create or replace package body pk_server_control
is
function fn_get_employees_by_consultant(consultant_id number)
return number
is
val number := consultant_id;
cursor employees is select c.CST_NAME, a.NO_OF_EMPLOYEES from NISHAN_LDS_ACCOUNT a join NISHAN_LDS_CONSULTANT c
on c.CONSULTANT_ID = a.FK1_CONSULTANT_ID where c.CONSULTANT_ID =val;
total number := 0;
begin
for data in employees
loop
total := total + data.NO_OF_EMPLOYEES;
end loop;
return total;
end;
end;
begin
dbms_output.put_line(pk_server_control.fn_get_employees_by_consultant(3));
end;

PLSQL : FORALL insert when data is NOT from the type

I want to use FORALL to insert data into a table. But, in my below code I will not be able to
get l_final_amt and l_reference_number variables outside the FOR loop of l_tbl_table_test_retrieve.
How to use FORALL to insert data into a table when values are not in the given type?
CREATE OR REPLACE PACKAGE test_FORALL AS
PROCEDURE pr_test_FORALL;
END test_FORALL;
CREATE OR REPLACE PACKAGE BODY test_FORALL AS
PROCEDURE pr_test_FORALL IS
TYPE ty_tbl_table_test IS TABLE OF table_test%ROWTYPE INDEX BY BINARY_INTEGER;
l_tbl_table_test_retrieve ty_tbl_table_test;
l_tbl_table_test ty_tbl_table_test;
l_final_amt INTEGER;
l_reference_number VARCHAR2(100);
BEGIN
SELECT * BULK COLLECT
INTO l_tbl_table_test_retrieve
FROM table_test t1;
FOR i IN 1 .. l_tbl_table_test_retrieve.COUNT
LOOP
l_tbl_table_test(l_tbl_table_test.COUNT + 1) := l_tbl_table_test_retrieve(i);
l_final_amt := l_final_amt + 10;
l_reference_number := SYSDATE + l_tbl_table_test_retrieve(i).ID;
insert into some_other_table(fname, address,final_amt,ref_number)
values(l_tbl_table_test_retrieve(i).fname, l_tbl_table_test_retrieve(i).address,l_final_amt,l_reference_number);
END LOOP;
--I want to insert into some_other_table using FORALL. But,l_final_amt and l_reference_number variables
-- are not available in l_tbl_table_test_retrieve.
EXCEPTION
DBMS_OUTPUT.put_line('EXCEPTION occurred');
END;
END pr_test_FORALL;
END test_FORALL;
Use a cursor and add the fields into the rows returned by the cursor:
PROCEDURE pr_test_FORALL IS
DECLARE csrData AS CURSOR FOR
SELECT t1.*,
NULL AS COUNT_VAL,
NULL AS FINAL_AMT,
NULL AS REFERENCE_NUMBER
FROM TABLE_TEST t1;
TYPE ty_tbl_table_test IS
TABLE OF csrData%ROWTYPE -- Note: csrData%ROWTYPE
INDEX BY BINARY_INTEGER;
l_tbl ty_tbl_table_test;
l_final_amt INTEGER := 0;
l_reference_number VARCHAR2(100);
BEGIN
OPEN csrData
FETCH csrData
BULK COLLECT INTO l_tbl;
CLOSE csrData;
FOR i IN 1 .. l_tbl.COUNT LOOP
l_final_amt := l_final_amt + 10;
l_tbl(i).FINAL_AMT := l_final_amt;
l_tbl(i).REFERENCE_NUMBER := SYSDATE + l_tbl(i).ID;
END LOOP;
FORALL i IN l_tbl.FIRST..l_tbl.LAST
INSERT INTO SOME_OTHER_TABLE
(FNAME, ADDRESS, FINAL_AMT, REF_NUMBER)
VALUES
(l_tbl(i).FNAME,
l_tbl(i).ADDRESS,
l_tbl(i).FINAL_AMT,
l_tbl(i).REFERENCE_NUMBER);
EXCEPTION
DBMS_OUTPUT.put_line('EXCEPTION occurred');
END pr_test_FORALL;
You could convert the whole thing into two inserts of the below form into the required tables.
I see that in your code l_reference_number is defined as a VARCHAR2 variable but it sounds like a number. ( SYSDATE + some_number ) will yield a date type. It will be implicitly converted into a string based on your NLS_ settings when you assign it to a varchar2. I'm not sure what do you want to store in there as a "REFERENCE_NUMBER".
INSERT INTO some_other_table (
fname,
address,
final_amt,
ref_number
)
SELECT fname,
address,
10 * ROWNUM AS final_amt,
SYSDATE + id as reference_number
FROM table_test;

Wrong number or TYPES of arguments, error in PL/SQL

I have to create a list of RECORD and I need to send it to a procedure.
There is my header.
CREATE OR REPLACE PACKAGE tema4 IS
TYPE obj IS RECORD(id INTEGER := 0,percent INTEGER := 0);
TYPE listObj IS TABLE OF obj INDEX BY PLS_INTEGER;
PROCEDURE ex1 (p_listObj IN listObj);
END tema4;
My body.
create or replace PACKAGE BODY tema4 IS
PROCEDURE ex1 (p_listObj IN listObj) IS
BEGIN
DBMS_OUTPUT.PUT_LINE('Cant reach');
END ex1;
END tema4;
And my code that calls procedure ex1.
DECLARE
TYPE obj IS RECORD(id INTEGER := 0,percent INTEGER := 0);
TYPE listObj IS TABLE OF obj INDEX BY PLS_INTEGER;
v_obj obj;
v_listObj listObj;
BEGIN
FOR v_i IN (SELECT ID,BURSA FROM STUDENTI ORDER BY ID) LOOP
v_obj.id := v_i.id;
v_obj.percent := 50;
v_listObj(v_i.id) := v_obj;
END LOOP;
FOR v_i IN v_listObj.FIRST..v_listObj.LAST LOOP
DBMS_OUTPUT.PUT_LINE(v_listObj(v_i).id || ' - ' ||
v_listObj(v_i).percent);
END LOOP;
tema4.ex1(v_listObj); --this line is with problems
END;
PLS-00306: wrong number or types of arguments in call to 'EX1'
Can someone explain me what is wrong in my code? I also tried to create my type as global, but it won't let me because of 'RECORD' keyword.
Don't declare the types again (new types), use the types already declared in the package spec:
DECLARE
v_obj tema4.obj;
v_listObj tema4.listObj;
BEGIN
FOR v_i IN (SELECT ID,BURSA FROM STUDENTI ORDER BY ID) LOOP
v_obj.id := v_i.id;
v_obj.percent := 50;
v_listObj(v_i.id) := v_obj;
END LOOP;
FOR v_i IN v_listObj.FIRST..v_listObj.LAST LOOP
DBMS_OUTPUT.PUT_LINE(v_listObj(v_i).id || ' - ' ||
v_listObj(v_i).percent);
END LOOP;
tema4.ex1(v_listObj); --this line is with problems
END;

How to loop through a delimited list in Oracle PLSQL

I am working on an Oracle procedure that calls another procedure within it. One of my parameters (parm1) can contain one or more values in a comma separated list. How can I loop through these values to pass them one at a time to another procedure?
Here is an example of what I would like it to do:
When Parm1 = 123,312
callProcedure2(123)
callProcedure2(321)
-or-
When Parm1 123
callProcedure2(123)
I think this can be accomplished using a loop but I can't figure out how to get it to use each value as a separated call within the loop.
Any help would be appreciated!
Thanks!
CURSOR V_CUR IS
select regexp_substr(Parm1 ,'[^,]+', 1, level) As str from dual
connect by regexp_substr(Parm1, '[^,]+', 1, level) is not null;
This curor will give you result like this
123
321
Now iterate the cursor and call the procedure in loop.
For i IN V_CUR
LOOP
callProdcedure2(i.str);
END LOOP;
Just loop through substrings:
declare
parm1 varchar2(1000) := '123,234,345,456,567,789,890';
vStartIdx binary_integer;
vEndIdx binary_integer;
vCurValue varchar2(1000);
begin
vStartIdx := 0;
vEndIdx := instr(parm1, ',');
while(vEndIdx > 0) loop
vCurValue := substr(parm1, vStartIdx+1, vEndIdx - vStartIdx - 1);
-- call proc here
dbms_output.put_line('->'||vCurValue||'<-');
vStartIdx := vEndIdx;
vEndIdx := instr(parm1, ',', vStartIdx + 1);
end loop;
-- Call proc here for last part (or in case of single element)
vCurValue := substr(parm1, vStartIdx+1);
dbms_output.put_line('->'||vCurValue||'<-');
end;
There is a utility procedure COMMA_TO_TABLE and array type DBMS_UTILITY.UNCL_ARRAY dedicated for this task. Since Oracle 10g.
It is well document here.
Here is a sample solution:
SET SERVEROUTPUT ON
DECLARE
csvListElm VARCHAR2(4000) := 'elm1, elm2,elm3 ,elm4 , elm5';
csvListTable DBMS_UTILITY.UNCL_ARRAY;
csvListLen BINARY_INTEGER;
currTableName VARCHAR2(222);
BEGIN
DBMS_UTILITY.COMMA_TO_TABLE(csvListElm, csvListLen, csvListTable);
FOR csvElm IN 1..(csvListTable.COUNT - 1) LOOP
dbms_output.put_line('-- CSV element : <'||csvListTable(csvElm)||'>');
dbms_output.put_line('-- Trimmed CSV element: <'||trim(csvListTable(csvElm))||'>');
END LOOP;
END;
/
Sample output:
-- CSV element : <elm1>;
-- Trimmed CSV element: <elm1>;
-- CSV element : < elm2>;
-- Trimmed CSV element: <elm2>;
-- CSV element : <elm3 >;
-- Trimmed CSV element: <elm3>;
-- CSV element : <elm4 >;
-- Trimmed CSV element: <elm4>;
-- CSV element : < elm5>;
-- Trimmed CSV element: <elm5>;
It is possible to use a function that you can use in a for loop (without regexp for ThinkJet):
Create a type and function
CREATE OR REPLACE TYPE t_my_list AS TABLE OF VARCHAR2(100);
CREATE OR REPLACE
FUNCTION cto_table(p_sep in Varchar2, p_list IN VARCHAR2)
RETURN t_my_list
AS
l_string VARCHAR2(32767) := p_list || p_sep;
l_sep_index PLS_INTEGER;
l_index PLS_INTEGER := 1;
l_tab t_my_list := t_my_list();
BEGIN
LOOP
l_sep_index := INSTR(l_string, p_sep, l_index);
EXIT
WHEN l_sep_index = 0;
l_tab.EXTEND;
l_tab(l_tab.COUNT) := TRIM(SUBSTR(l_string,l_index,l_sep_index - l_index));
l_index := l_sep_index + 1;
END LOOP;
RETURN l_tab;
END cto_table;
/
Then how to call it in the for loop:
DECLARE
parm1 varchar2(4000) := '123,234,345,456,567,789,890';
BEGIN
FOR x IN (select * from (table(cto_table(',', parm1)) ) )
LOOP
dbms_output.put_line('callProdcedure2 called with ' || x.COLUMN_VALUE);
callProdcedure2(x.COLUMN_VALUE);
END LOOP;
END;
/
Notice the default name COLUMN_VALUE given by Oracle, which is necessary for the use I want to make of the result.
Result as expected:
callProdcedure2 called with 123
callProdcedure2 called with 234
...

Resources