Is there a way to calculate the optimal block size when using array binding to insert large amounts of data?
I do it like this: When my application runs a command the first time I call this function:
Public Function GetRowSize(ByVal cmd As OracleCommand) As Integer
Dim dr As OracleDataReader
dr = cmd.ExecuteReader()
Return CInt(dr.GetType.GetField("m_rowSize", Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic).GetValue(dr))
End Function
The result I store in a class variable for further use.
It is executed like this:
Dim cmd As OracleCommand
Dim da As OracleDataAdapter, dt As New DataTable
Dim expectedNumberOfRows As Integer
cmd = New OracleCommand("BEGIN :res := GetData(:expectedRows); END;"), server.con)
cmd.Parameters.Add("res", OracleDbType.RefCursor, ParameterDirection.ReturnValue)
cmd.Parameters.Add("expectedRows", OracleDbType.Int32, ParameterDirection.Output)
cmd.Parameters("expectedRows").DbType = DbType.Int32
If rowSize = 0 Then rowSizes = GetRowSize(cmd)
cmd.ExecuteNonQuery()
expectedNumberOfRows = CInt(cmd.Parameters("expectedRows").Value)
cmd.FetchSize = rowSize * expectedNumberOfRows
da = New OracleDataAdapter(cmd)
da.Fill(dt)
In my PL/SQL I run EXPLAIN PLAN in order to get the estimated number of rows, it looks like this:
FUNCTION GetData(estimatedRows OUT NUMBER) RETURN SYS_REFCURSOR IS
res SYS_REFCURSOR;
BEGIN
OPEN res FOR SELECT * FROM MY_TABLE;
DELETE FROM PLAN_TABLE;
EXECUTE IMMEDIATE 'EXPLAIN PLAN FOR SELECT * FROM MY_TABLE';
SELECT CARDINALITY
INTO estimatedRows
FROM PLAN_TABLE
WHERE ID = 0;
RETURN res;
END;
Related
I want to return query results from a function:
SELECT Lec.univ_id,Count(Lec.lecturer_id) FROM D8_SUBJECT Sub
JOIN D8_SUBJ_LECT SubLect ON
Sub.subj_id = SubLect.subj_id
JOIN D8_LECTURER Lec ON
SubLect.LECTURER_ID = Lec.LECTURER_ID
WHERE Sub.subj_name = 'ИНФОРМАТИКА' AND univ_id BETWEEN 1 AND 50
Group BY Lec.univ_id;
Where is the result of this query of a row of the form (id Integer, count Integer).
I tried something like that
CREATE OR REPLACE FUNCTION GetMaximum
(first_univer IN Integer,second_univer IN Integer,subj_name IN NVARCHAR(30))
RETURN user_tables.num_rows%TYPE
AS
rf_cur sys_refcursor;
BEGIN
OPEN rf_cur for
SELECT Lec.univ_id,Count(Lec.lecturer_id)
FROM D8_SUBJECT Sub
JOIN D8_SUBJ_LECT SubLect
ON Sub.subj_id = SubLect.subj_id
JOIN D8_LECTURER Lec
ON SubLect.LECTURER_ID = Lec.LECTURER_ID
WHERE Sub.subj_name = subj_name AND univ_id BETWEEN first_univer AND second_univer
Group BY Lec.univ_id;
return rf_cur;
END GetMaximum;
/
but it don't compile. What type do I need to use for the return value
If you're returning refcursor, then function declaration has to support it:
CREATE OR REPLACE FUNCTION GetMaximum
(first_univer IN Integer,second_univer IN Integer,subj_name IN NVARCHAR(30))
RETURN sys_refcursor --> this
AS
rf_cur sys_refcursor; --> this
BEGIN
OPEN rf_cur for
SELECT Lec.univ_id,Count(Lec.lecturer_id) cnt
FROM D8_SUBJECT Sub
<snip>
Group BY Lec.univ_id;
return rf_cur; --> this
END GetMaximum;
/
I am trying to call the following pl/sql procedure from JDBC.
create or replace PACKAGE test AS
type testrec_r is record (
val1 number,
val2 varchar2(100)
);
type testarr_t is table of testrec_r index by binary_integer;
function test_func(i_data in testarr_t, o_sum out number, o_totallength out number) return number;
END test;
This is how I tried to invoke it, but without success:
StructDescriptor recDescriptor = StructDescriptor.createDescriptor("test.testrec_r", conn);
STRUCT[] RECORDS_ARRAY = new STRUCT[2];
for (int i = 0; i < 2; i++) {
STRUCT oracle_record = new STRUCT(recDescriptor, conn, new
Object[] {i, "test"});
RECORDS_ARRAY[i] = oracle_record;
}
CallableStatement stmt = conn.prepareCall("{ call TEST.TEST_FUNC(?, ?, ?) }");
ArrayDescriptor arrayDescriptor = ArrayDescriptor.createDescriptor("TEST.TESTARR_T", conn);
ARRAY oracle_array = new ARRAY(arrayDescriptor, conn, RECORDS_ARRAY);
// Bind the input record
stmt.setArray(1, oracle_array);
stmt.registerOutParameter(2, Types.NUMERIC);
stmt.registerOutParameter(3, Types.NUMERIC);
stmt.executeUpdate();
double out1 = stmt.getDouble(2);
double out2 = stmt.getDouble(3);
return new Object[] { out1, out2 };
I just have read that oracle jdbc does not support pl/sql struct types. So, this fails with "invalid name pattern: test.testrec_r"
How can I call this procedure from Java? Ideally would be to only use a java libray/API, but as this seems almost imposible, which is the best workaround to wrap the pl/sql package in simple sql call and to invoke it?
P.S I am using Spring JDBCTemplate for database connection.
You cannot use PL/SQL types because they known to PL/SQL alone (since 12c this is no more strictly true - see UPD). Also any type created within a package is not visible by java directly.
You should create a SQL type at schema level. SQL types are visible to all and usable by all.
create or replace and compile java source named "ArrayOfRecTest" as
import java.io.*;
import java.sql.*;
import oracle.sql.*;
import oracle.jdbc.driver.*;
public class ArrayOfRecTest
{
public static void passArrayOfRec() throws SQLException
{
Connection conn = new OracleDriver().defaultConnection();
StructDescriptor sd = StructDescriptor.createDescriptor("MYREC_TYPE", conn);
ArrayDescriptor ad = ArrayDescriptor.createDescriptor("MYRECARR_TYPE", conn);
STRUCT[] recarr = new STRUCT[2];
for (int i = 0; i < 2; i++) { recarr[i] = new STRUCT(sd, conn, new Object[] {i+1, "value " + (i+1)}); }
ARRAY oracle_array = new ARRAY(ad, conn, recarr);
CallableStatement stmt = conn.prepareCall("{ ? = call testpkg.showArrOfRec(?, ?, ?) }");
stmt.registerOutParameter(1, Types.INTEGER);
stmt.setObject(2, oracle_array);
stmt.registerOutParameter(3, Types.INTEGER);
stmt.registerOutParameter(4, Types.INTEGER);
stmt.execute();
int sizeofArr = stmt.getInt(1);
int total = stmt.getInt(3);
int totalLength = stmt.getInt(4);
System.out.println("passArrayOfRec(total,len)=(" + total + "," + totalLength + ") " + sizeofArr + " records were shown");
}
}
/
create or replace force type myrec_type as object( id number, value varchar2(100));
/
create or replace type myrecarr_type as table of myrec_type;
/
create or replace package testpkg as
procedure passArrayOfRec as language java name 'ArrayOfRecTest.passArrayOfRec()' ;
function showArrOfRec(ra myrecarr_type, total out number, totallength out number) return number;
end testpkg;
/
create or replace package body testpkg as
--OP stuff
type testrec_r is record (val1 number, val2 varchar2(100));
type testarr_t is table of testrec_r index by binary_integer;
function test_func(data in testarr_t, total out number, totallength out number) return number is
begin
<<for_each>> for i in data.first..data.last loop
dbms_output.put_line('data(' || i || ')[val1,val2]=[' || data(i).val1 || ',' || data(i).val2 || ']');
total := nvl(total,0) + data(i).val1;
totallength := nvl(totallength,0) + length(data(i).val2);
end loop for_each;
return data.count;
end test_func;
--end OP stuff
function showArrOfRec(ra myrecarr_type, total out number, totallength out number) return number is
data testarr_t;
begin
for i in ra.first..ra.last loop data(i).val1 := ra(i).id; data(i).val2 := ra(i).value; end loop;
return test_func(data, total, totalLength);
end showArrOfRec;
end testpkg;
/
exec testpkg.passArrayOfRec;
Output:
data(1)[val1,val2]=[1,value 1]
data(2)[val1,val2]=[2,value 2]
passArrayOfRec(total,len)=(3,14) 2 records were shown
UPD New in 12cR1: Using PL/SQL Types
When I do this:
select sum(m.mot)
from rmtq mq
join rmo m on mq.id = m.id
where mq.another = 138;
return value = 2, which is correct. But when I put this code inside a function:
create or replace function get(another in number) return number
is ret number := 0;
begin
select sum(m.mot)
into ret
from rmtq mq
join rmo m on mq.id = m.id
where mq.another = another
return(ret);
end;
and I call:
exec dbms_output.put_line(get(138));
return value = 39, which is incorrect. What is that 39?
The comments were right to question; lest anyone waste time on this you have to write the names of the parameters correctly, for example:
create or replace function get(p_another in number) return number
is ret number := 0;
begin
select sum(m.mot)
into ret
from rmtq mq
join rmo m on mq.id = m.id
where mq.another = p_another
return(ret);
end;
I have a function which is having three different queries. First query is returning a single record in a plsql record type.Second query is returning another single value and third is also returning a different single value. Now I want to append first record with those two values and return a table of that new record from my function. How can I achieve that.
create or replace function test(p_actBillDat date) return <what should I return> as
type tab_accountNum is table of account.account_num%type;
var_accountNum tab_accountNum;
type query1Record is record(
accountNum account.account_num%type,
customerRef customer.customer_ref%type,
internalCreditScore CUSTOMERATTRIBUTES.Internal_Credit_Score%type);
var_query1Rec query1Record;
var_nsfDat date;
var_writeOffdat date;
cursor cur_accountNum is
select ACCOUNT_NUM
from BILLSUMMARY
where trunc(ACTUAL_BILL_DTM) = p_actBillDat
and CANCELLATION_REQUEST_DAT is null;
begin
open cur_accountNum;
Loop
fetch cur_accountNum bulk collect
into var_accountNum limit 100;
close cur_accountNum;
for i in 1 .. var_accountNum.count
loop
select A.ACCOUNT_NUM, A.CUSTOMER_REF, CA.INTERNAL_CREDIT_SCORE
into var_query1Rec
from ACCOUNT A, CUSTOMERATTRIBUTES CA, CONTACTDETAILS CD, CONTACT CNT
where A.ACCOUNT_NUM = var_accountNum(i) and
A.CUSTOMER_REF = CA.CUSTOMER_REF(+) and
A.CUSTOMER_REF = CD.CUSTOMER_REF and
CNT.CUSTOMER_REF = A.CUSTOMER_REF and
CD.CONTACT_SEQ = CNT.CONTACT_SEQ and
CD.CONTACT_SEQ = 1 and
CD.START_DAT = (select min(CNTD.START_DAT)
from CONTACTDETAILS CNTD
where CNTD.CONTACT_SEQ = CD.CONTACT_SEQ and
CNTD.CUSTOMER_REF = A.CUSTOMER_REF);
select max(AP.ACCOUNT_PAYMENT_DAT) into var_writeOffdat
from ACCOUNT A, ACCOUNTPAYMENT AP
where A.ACCOUNT_NUM = AP.ACCOUNT_NUM and
A.ACCOUNT_NUM = var_accountNum(i) AND
A.TOTAL_WRITEOFF_TOT <> 0 and
(AP.PAYMENT_ORIGIN_ID = 2 or AP.PAYMENT_ORIGIN_ID = 3) and
AP.CANCELLED_DTM is null and
AP.FAILED_DTM is null;
select max(PP.FAILED_DTM) into var_nsfDat
from ACCOUNTPAYMENT AP, PHYSICALPAYMENT PP
where AP.ACCOUNT_NUM = var_accountNum(i) and
AP.ACCOUNT_PAYMENT_STATUS = 3 and
AP.PHYSICAL_PAYMENT_SEQ = PP.PHYSICAL_PAYMENT_SEQ and
AP.CUSTOMER_REF = PP.CUSTOMER_REF and
PP.PHYSICAL_PAYMENT_STATUS = 3 and
PP.FAILURE_CODE_ID in (select PFC.FAILURE_CODE
from CGPAYMENTFAILURECONFIG PFC
where PFC.FAILURE_TYPE = 'Decline NSF') ;
<how to appned var_query1Rec with var_writeOffdat and var_writeOffdat>
<how to make a PLSQl table of that record and return from function>
end loop;
end loop;
end;
If this function is not part of a package - why wouldn't it be? then you have no other choice but to declare a SQL Object type like this example:
CREATE TYPE person_typ AS OBJECT (
idno NUMBER,
first_name VARCHAR2(20)
);
Declare the variables at the top of your function to access the type created.
type t_arr is table of person_typ ;
l_arr t_arr := t_arr();
Then assign them in your code:
l_arr.extend;
l_arr(i).idno := xxx;
l_arr(i).first_name := yyyy;
The create function returns the object:
create or replace function test(p_actBillDat date) return person_typ as
.....
return(l_arr);
end;
But I would have this function in a package then in the package body header or spec you could do this:
type t_rec is
record(x number
,y varchar2(100)
);
type t_tbl is table of t_rec index by binary_integer;
Then declare in your function:
l_tbl t_tbl;
Then assign them in the function:
l_tbl(i).x := xxx;
l_tbl(i).y := yyy;
And finally just return the type in your function like this:
create or replace function test(p_actBillDat date) return t_tbl as
......
l_tbl t_tbl;
begin
.......
for i in 1..counter loop
.. SQL statements
l_tbl(i).x := xxx;
l_tbl(i).y := yyy;
end loop;
return l_tbl;
end;
The title is pretty-self-explanatory: from a C# application, using ODP.NET, I'm trying to call a PL/SQL function that returns not a simple value, but a record.
Unfortunately, I'm not authorized to add or change the PL/SQL code, so attempting to wrap the function in another function that returns a different type is not an option for me.
Here is a simplified example...
PL/SQL:
CREATE OR REPLACE PACKAGE FOO_PACKAGE AS
TYPE FOO_RECORD IS RECORD (
BAR VARCHAR2(50),
BAZ VARCHAR2(50)
);
FUNCTION FOO_FUNCTION RETURN FOO_RECORD;
END;
/
CREATE OR REPLACE PACKAGE BODY FOO_PACKAGE AS
FUNCTION FOO_FUNCTION RETURN FOO_RECORD AS
R FOO_RECORD;
BEGIN
R.BAR := 'Hello bar!';
R.BAZ := 'Hello baz!';
RETURN R;
END;
END;
/
C#:
The first thing I tried was the most direct approach, but I'm at a loss how to bind the return parameter...
using (var conn = new OracleConnection(connection_string)) {
conn.Open();
using (var tran = conn.BeginTransaction()) {
using (var cmd = conn.CreateCommand()) {
cmd.CommandText = "FOO_PACKAGE.FOO_FUNCTION";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(
null,
/* What to use here? */,
ParameterDirection.ReturnValue
);
cmd.ExecuteNonQuery();
}
}
}
I have also tried some "roundabout" approaches, but they all throw exceptions:
cmd.CommandText = "SELECT * FROM FOO_PACKAGE.FOO_FUNCTION";
cmd.ExecuteReader(); // ORA-00942: table or view does not exist
cmd.CommandText = "SELECT FOO_PACKAGE.FOO_FUNCTION FROM DUAL";
cmd.ExecuteReader(); // ORA-00902: invalid datatype
cmd.CommandText = "SELECT BAR, BAZ FROM (SELECT FOO_PACKAGE.FOO_FUNCTION FROM DUAL)";
cmd.ExecuteReader(); // ORA-00904: "BAZ": invalid identifier
You need anonymous PL/SQL block to convert function result to another representation:
declare
vFooRes FOO_PACKAGE.FOO_RECORD;
vRes sys_refcursor;
begin
vFooRes := FOO_PACKAGE.FOO_FUNCTION;
open vRes for select vFooRes.BAR, vFooRes.BAZ from dual;
--:result := vRes;
end;
SQLfiddle
And execute it instead of calling stored procedure:
cmd.CommandText = "declare\n" +
" vFooRes FOO_PACKAGE.FOO_RECORD;\n" +
"begin\n" +
" vFooRes := FOO_PACKAGE.FOO_FUNCTION;\n" +
" open :result for select vFooRes.BAR, vFooRes.BAZ from dual;\n" +
"end;";
OracleParameter p = cmd.Parameters.Add(
"result",
OracleDbType.RefCursor,
DBNull.Value,
ParameterDirection.Output
);
cmd.ExecuteNonQuery();
After executing cmd you get cursor in result parameter which can be used to fill dataset:
var adapter = new OracleDataAdapter(cmd);
var data = new DataSet("FooDataSet");
adapter.Fill(data, "result", (OracleRefCursor)(p.Value));