I'm trying to recover a bunch of data in the form of a table
I created the type as follows
create or replace type Structure_type AS OBJECT
(
SWID Integer,
structure_TYPESid Integer,
MAIN_BRANCHid Integer,
Organizational_structureid Integer,
Administrative_structureid Integer,
structure_tree NVarchar2(1000),
structure_NO NVarchar2(500),
Structure_level Integer
)
and create table of type
create or replace type Structure_table_type as table of Structure_type;
Then I created a data retrieval function like the following
create or replace function Get_Who_Runs_Stores
(OoA char) return Structure_table_type is
FunctionResult Structure_table_type;
begin
FunctionResult :=Structure_table_type();
CASE UPPER(OoA)
WHEN 'A' then
FOR x_Structure IN (select b.administrative_tree
from warehouse w join branches_costcenter b on (w.w_branch=b.swid))
LOOP
select
b.swid SWID,
b.costcenter_type_id structure_TYPESid ,
b.main_branch_id MAIN_BRANCHid,
b.direct_branch_cost_center_id Organizational_structureid,
b.direct_adminstration_parent_id Administrative_structureid,
b.administrative_tree structure_tree,
b.administrative_no structure_NO,
b.administrative_level Structure_level
into FunctionResult
from branches_costcenter b
where
x_Structure.Administrative_Tree like '%-' || b.swid || '-%';
end loop;
WHEN 'O' then
FOR x_Structure IN (select b.organizationa_tree
from warehouse w join branches_costcenter b on (w.w_branch=b.swid))
LOOP
select b.swid SWID,b.costcenter_type_id structure_TYPESid ,
b.main_branch_id MAIN_BRANCHid, b.direct_branch_cost_center_id Organizational_structureid,
b.direct_adminstration_parent_id Administrative_structureid,b.organizationa_tree structure_tree,
b.organizationa_no structure_NO ,b.organizationa_level Structure_level into FunctionResult
from branches_costcenter b
where
x_Structure.Organizationa_Tree like '%-' || b.swid || '-%';
end loop;
else
return null;
end case ;
return(FunctionResult);
end Get_Who_Runs_Stores;
Error: PL/SQL: ORA-00947: not enough values
Line: 45
Text: from branches_costcenter b
I tried the SQL sentence and it works correctly
select
b.swid SWID,
b.costcenter_type_id structure_TYPESid ,
b.main_branch_id MAIN_BRANCHid,
b.direct_branch_cost_center_id Organizational_structureid,
b.direct_adminstration_parent_id Administrative_structureid,
b.administrative_tree structure_tree,
b.administrative_no structure_NO,
b.administrative_level Structure_level
from branches_costcenter b
where
'-1-2-3-12-24-' like '%-' || b.swid || '-%';
How can I get the required data and what is the best way to do so?
Do I have to define all that I want to retrieve data from the function in the form of a table?
Sorry, my English is bad
Your function will be horrible slow. Apart from that the issue is following: If you run
FOR x_Structure IN (select ...) LOOP
SELECT ...
INTO FunctionResult
FROM ...;
END LOOP;
RETURN FunctionResult;
then you insert row by row into FunctionResult. At the end you return only the last row from your select, all other rows have been overwritten.
Try
SELECT ...
BULK COLLECT INTO FunctionResult
FROM ...;
You could also insert row by row, but this is much slower:
FOR x_Structure IN (select ...) LOOP
FunctionResult.Extend();
SELECT ...
INTO FunctionResult(FunctionResult.LAST)
FROM ...;
END LOOP;
You cannot select into FunctionResult directly. The statement must be like this:
SELECT Structure_type(b.swid, b.costcenter_type_id, ..., b.administrative_level)
BULK COLLECT INTO FunctionResult
FROM ...
Update
Depending on your client it might be easier and better to return a RefCursor. Would be like this:
create or replace function Get_Who_Runs_Stores(OoA char) return SYS_REFCURSOR is
res SYS_REFCURSOR;
begin
OPEN res FOR
SELECT b.swid SWID,b.costcenter_type_id structure_TYPESid, ...
FROM ...
WHERE ...;
RETURN res;
end;
You don't have to create any TABLE or OBJECT type, just return the RefCursor.
Related
I have the following plsql block
declare
TYPE t_mds_ids IS TABLE OF mds.id%TYPE;
l_mds_ids t_mds_ids;
l_mds_parents t_mds_parents;
begin
SELECT id BULK COLLECT INTO l_mds_ids FROM mds;
FORALL indx IN l_mds_ids.FIRST .. l_mds_ids.LAST
select l_mds_ids(indx), ch.id_employee_parent
into l_mds_parents
FROM hierarchy_all ch
CONNECT BY ch.id_employee = prior ch.id_employee_parent
START WITH ch.id_employee = l_mds_ids(indx);
EXECUTE IMMEDIATE 'truncate table mds_hierarchy_all';
insert into mds_hierarchy_all
select * from l_mds_parents;
end;
t_mds_parents declared as
create or replace type r_mds_parents as object (
id_mds number(5,0),
id_employee number(5,0)
);
/
create or replace type t_mds_parents as table of r_mds_parents;
/
And I get an exception ORA-00947: not enough values
I really need to put the resultset of multiple rows into variable of TABLE TYPE on each iteration of FORALL loop. I can't use BULK COLLECT into l_mds_parents as it's restricted inside of FORALL.
Is there only solution to use temporary table instead of table variable?
I don't think you can do this with forall. You could use nested loops:
declare
TYPE t_mds_ids IS TABLE OF mds.id%TYPE;
l_mds_ids t_mds_ids;
l_mds_parents t_mds_parents;
begin
SELECT id BULK COLLECT INTO l_mds_ids FROM mds;
l_mds_parents := NEW t_mds_parents();
FOR indx IN l_mds_ids.FIRST .. l_mds_ids.LAST LOOP
FOR rec IN (
select l_mds_ids(indx) as id_employee, ch.id_employee_parent
FROM hierarchy_all ch
CONNECT BY ch.id_employee = prior ch.id_employee_parent
START WITH ch.id_employee = l_mds_ids(indx)
) LOOP
l_mds_parents.extend();
l_mds_parents(l_mds_parents.COUNT)
:= NEW r_mds_parents (rec.id_employee, rec.id_employee_parent);
END LOOP;
END LOOP;
EXECUTE IMMEDIATE 'truncate table mds_hierarchy_all';
insert into mds_hierarchy_all
select * from table(l_mds_parents);
end;
/
But you don't need to use PL/SQL at all; use a single hierarchical query, or probably more simply here, recursive subquery factoring:
insert into mds_hierarchy_all /* (id_mds, id_employee) -- better to list columns */
with rcte (id_mds, id_employee) as (
select m.id, ha.id_employee_parent
from mds m
join hierarchy_all ha on ha.id_employee = m.id
union all
select r.id_mds, ha.id_employee_parent
from rcte r
join hierarchy_all ha on ha.id_employee = r.id_employee
)
select * from rcte;
db<>fiddle with some made-up data.
My Question is "How can we return multiple records from pl/sql stored procedure without taking OUT parameter".I got this doubt because if we are using cursors or refcursor in out parameter it may degrade performance.So what is the solution??
As OldProgrammer wrote, i think the performance of a cursor wouldn't be you problem. But here a Solution anyway:
You can return custom types like Table of number. If it's only a list of numbers you could return a table of numbers. If you Want to return rows from a table you could return table of 'tablename'%ROWTYPE. But i guess you want to create some custom types.
CREATE OR REPLACE TYPE PUWB_INT.MyOrderType AS OBJECT
(
OrderId NUMBER,
OrderName VARCHAR2 (255)
)
/
CREATE OR REPLACE TYPE PUWB_INT.MyOrderListType AS TABLE OF MYORDERtype
/
Now we can use them similar to a return myNumberVariable;
Let's build a function (procedures don't have return values):
CREATE OR REPLACE FUNCTION PUWB_INT.MyFunction (SomeInput VARCHAR2)
RETURN MyOrderListType
IS
myOrderList MyOrderListType := MyOrderListType ();
BEGIN
FOR o IN (SELECT 1 AS Id, 'One' AS Name FROM DUAL
UNION ALL
SELECT 2 AS Id, 'Two' AS Name FROM DUAL)
LOOP
myOrderList.EXTEND ();
myOrderList (myOrderList.COUNT) := MyOrderType (o.Id, o.Name || '(' || SomeInput || ')');
END LOOP;
RETURN myOrderList;
END MyFunction;
/
Now we can call the function and get a table of our custom-type:
DECLARE
myOrderList MyOrderListType;
myOrder MyOrderType;
BEGIN
myOrderList := MyFunction ('test');
FOR o IN myOrderList.FIRST .. myOrderList.LAST
LOOP
myOrder := myOrderList (o);
DBMS_OUTPUT.put_line ('Id: ' || myOrder.OrderId || ', Name: ' || myOrder.OrderName);
END LOOP;
END;
Be aware, that the calling schema, has to know the type.
I'm trying to use the rownum to simulate a column autonumbered as I need to use it as an ID. Since it is an ID, I look at the final table if no record with MAX (ID).
The problem I have is when I want to do arithmetic operations within the cursor or when you invoke, or when you want to use a function. The ROWNUM (v_id) field is empty me when I want to print with DBMS_OUTPUT . Anyone have any idea how to solve it without using sequences ?
Here put the sample code.
declare
max_id number;
CURSOR INSRT(w_max number) IS
SELECT f_max_fact_sap(to_number(V_ID),w_max) AS V_ID,Seriei,serief
FROM (SELECT To_Char(ROWNUM) AS V_ID, A.*
FROM (SELECT DISTINCT a.matnr, a.seriei, a.serief,a.xblnr,a.fecha_sap, ((SERIEF-SERIEI)+1) AS rango
FROM SOPFUN.TT_ZMOVIMI_FACTURADAS a
WHERE 0 =(SELECT COUNT(1)
FROM PA_ZMOVIMI_FACTURADAS B
WHERE A.SERIEI = B.SERIEI
AND A.SERIEF = B.SERIEF
AND A.MATNR = B.MATNR
AND A.FECHA_SAP=B.FECHA_SAP)
AND A.FECHA_SAP IS NOT NULL) A);
TYPE T_INSRT IS TABLE OF INSRT%ROWTYPE INDEX BY PLS_INTEGER;
V_INSRT T_INSRT;
begin
SELECT Max(Nvl(ID,10000)) INTO MAX_ID-- To Proof because the table is empty
FROM PA_ZMOVIMI_FACTURADAS;
OPEN INSRT(MAX_ID);
LOOP
FETCH INSRT BULK COLLECT INTO V_INSRT LIMIT 1000;
FOR I IN 1 .. V_INSRT.Count loop
DBMS_OUTPUT.PUT_LINE('ID: ' ||V_INSRT(I).V_ID||' SI: '||V_INSRT(I).SERIEI||' SI: '||V_INSRT(I).SERIEF||' OPERACION: '||to_char(f_max_fact_sap(V_INSRT(I).V_ID,MAX_ID)));
end loop;
EXIT WHEN INSRT%NOTFOUND;
END LOOP;
end;
Image I have the following queries in a package:
SELECT col1 FROM table WHERE id=5;
SELECT otherCol FROM otherTable WHERE id=78;
I want to return one record with two columns containing the values 'col1' and 'otherCol'.
In my case, I'd have lots of subqueries and DECODE statements so for readability I want to split it up into different queries something ala:
var result = {}; -- Unfortunately PL/SQL doesn't cope very well with JavaScript.
SELECT col1 INTO someVar FROM table WHERE id=5;
IF someVar = 1 THEN
result.X = SomeQuery();
ELSE
result.X = SomeEntirelyDifferentQuery();
END IF;
SELECT col INTO result.Z FROM tab1;
SELECT coz INTO result.Q FROM tab2;
IF result.Z IS NULL THEN
result.B = Query1();
ELSE
result.B = Query2();
END IF;
... and so on.
RETURN result;
So basically I want to create an "empty record" and then based on some conditions assign different values to columns in that record
This could be solved with DECODE but the resulting query is both not very readable nor very performant.
You have to define an object type the function can return.
CREATE OR REPLACE TYPE fancy_function_result_row as object
(
X number,
Y number,
Q date,
Z varchar2(30)
);
create or replace function fancy_function(...)
return fancy_function_result_row
as
my_record fancy_function_result_row := fancy_function_result_row();
begin
my_record.X := 1;
etc...
end;
If you want to insert the record into a table, it might be a lot easier simply defining a rowtype of that table.
declare
row_my_table my_table%rowtype;
begin
row_my_table.X := etc..
insert into my_table values row_my_table;
end;
--To concat the values 'col1' and 'otherCol':
with r1 as (select col1 from table where id=5),
r2 as (select otherCol from otherTable WHERE id=78),
select r1.col1 || ' concat with ' || r2.othercol from r1, r2
--To do this condition using DECODE:
SELECT DECODE (col1,
1,
(SELECT 1 FROM query1),
(SELECT 1 FROM DifferentQuery)
)
FROM TABLE
declare
cursor lc is
select *
from (select a.lin, a.pr,
b.name, sum(a.up) as u,
sum (a.d) as d
from li_dy_4 a,
p_list b
where a.pr=b.id
and b.parent_id != 0
and a.partitionid <= 308
and a.partitionid >= 302
and a.pr in (91,80)
GROUP BY a.pr, b.name, a.lin
order by d desc) ;
rec lc%ROWTYPE;
BEGIN
open lc;
loop
FETCH lc into rec;
dbms_output.put_line(rec.pr);
exit when lc%NOTFOUND;
end loop;
close lc;
END;
the above statement works fine for me. What I am not capable of finding anything hopeful is changing the value after the "in" statement which is a.pr in (91,80)
I have listed the values here manually, but I want to pass it to the cursor as an array of numbers for a.pr column. In short I want to do a.pr = idlist wher idlist is an array. Please anyone tell me if my idea is possible.
Just want to remind you, the IN clause supports 1000 items only. And that could be the primary reason ,there's nothing called BULK BINDING for SELECT Queries. We have FORALL INSERT/UPDATE, which is like BULK BINDING. Unfortunately select has none.
But still you can achieve your requirement in a different fashion.
You can try a global temporary table(GTT) which a temporary table with "scope of the data inserted" is only to that session.
You can FORALL INSERT all your data for IN clause into that table, and join the TABLE to your Query.
Else you can have a nested table (if oracle 10g) or a simple pl/sql type itself (if oracle 11g), with all your IN class items as records and join it to your Query.
Example: Using NESTED TABLE , effective for less number(<10000) of items
CREATE TYPE pr AS OBJECT
(pr NUMBER);
/
CREATE TYPE prList AS TABLE OF pr;
/
declare
myPrList prList := prList ();
cursor lc is
select *
from (select a.lin, a.pr,
b.name, sum(a.up) as u,
sum (a.d) as d
from li_dy_4 a,
p_list b,
TABLE(CAST(myPrList as prList)) my_list
where a.pr=b.id
and b.parent_id != 0
and a.partitionid <= 308
and a.partitionid >= 302
and a.pr = my_list.pr
GROUP BY a.pr, b.name, a.lin
order by d desc) ;
rec lc%ROWTYPE;
BEGIN
/*Populate the Nested Table, with whatever collection you have */
myPrList := prList ( pr(91),
pr(80));
/*
Sample code: for populating from your TABLE OF NUMBER type
FOR I IN 1..your_input_array.COUNT
LOOP
myPrList.EXTEND;
myPrList(I) := pr(your_input_array(I));
END LOOP;
*/
open lc;
loop
FETCH lc into rec;
exit when lc%NOTFOUND; -- Your Exit WHEN condition should be checked afte FETCH iyself!
dbms_output.put_line(rec.pr);
end loop;
close lc;
END;
/
I don't know the exact structure of your global table but you can use the collection in cursor like this
declare
cursor c1 is
select last_name ls from empc;
type x is table of employees.last_name%type;
x1 x := x();
cnt integer :=0;
begin
for z in c1 loop
cnt := cnt +1;
x1.extend;
x1(cnt) := z.ls;
if x1(cnt) is NULL then-----------
DBMS_OUTPUT.PUT_LINE('ASHISH');
end if;
dbms_output.put_line(cnt || ' '|| x1(cnt));
end loop;
end;