MSSQL has a great feature called Table Valued Parameters. It allows you to pass a table of a custom data to stored procedures and functions.
I was wondering what is the equivalent in PostgreSQL, if one exists, using JDBC?
I know about the option of passing arrays as function parameters, but that seems limited to PostgreSQL data types.
Consider the following PL/pgSQL code:
CREATE TYPE number_with_time AS(
_num float,
_date timestamp
);
and this function header:
CREATE OR REPLACE FUNCTION myfunc(arr number_with_time[])
Can anyone post a Java code using JDBC driver of calling that function with an array of the user defined data type?
Assuming you want to pass values from the client. If the values exist in the database already there are other, simpler ways.
Syntax for array of composite_type
I know about the option of passing arrays as function parameters, but
that seems limited to PostgreSQL data types.
What you can pass seems to be limited by Java Types and JDBC Types, and there does not seem be provisions for array types, not to speak of arrays of composite values ...
However, you can always pass a text representation. I am building on two basic facts:
Quoting the manual:
Arrays of any built-in or user-defined base type, enum type, or
composite type can be created. Arrays of domains are not yet supported.
Bold emphasis mine. Therefore, after you have created the type number_with_time as defined in your question, or defined a table with the same columns which registers the row type in the system automatically, you can also use the array type number_with_time[].
There is a text representation for every value.
Therefore, there is also a text representation for number_with_time[]:
'{"(1,2014-04-20 20:00:00)","(2,2014-04-21 21:00:00)"}'::number_with_time[]
Function call
The actual function call depends on the return values defined in your function - which is hidden in your question.
To avoid complications from array handling in JDBC, pass the text representation. Create the function taking a text parameter.
I am not going to use the name "date" for a timestamp. Working with this slightly adjusted type definition:
CREATE TYPE number_with_time AS(
_num float
, _ts timestamp
);
Simple SQL function:
CREATE OR REPLACE FUNCTION myfunc_sql(_arr_txt text)
RETURNS integer -- example
LANGUAGE sql AS
$func$
SELECT sum(_num)::int
FROM unnest (_arr_txt::number_with_time[]) x
WHERE _ts > '2014-04-19 20:00:00';
$func$;
Call:
SELECT myfunc_sql('{"(1,2014-04-20 20:00:00)","(2,2014-04-21 21:00:00)"}');
db<>fiddle here
Old sqlfiddle
Demonstrating:
above SQL function
PL/pgSQL variant
a couple of syntax variants for the array of composite type
the function calls
Call the function like any other function taking a simple text parameter:
CallableStatement myProc = conn.prepareCall("{ ? = call myfunc_sql( ? ) }");
myProc.registerOutParameter(1, Types.VARCHAR);
// you have to escape double quotes in a Java string!
myProc.setString(2, "{\"(1,2014-04-20 20:00:00)\",\"(2,2014-04-21 21:00:00)\"}");
myProc.execute();
String mySum = myProc.getInt(1);
myProc.close();
Details in the Postgres JDBC manual here.
Example to return a whole table via JDBC:
Return rows from a PL/pgSQL function
Try something like this:
------------------ your connection
V
Array inArray = conn.createArrayOf("integer", new Integer[][] {{1,10},{2,20}});
stmt.setArray(1, inArray);
A sample method you could use to build your test:
public void testInsertMultiDimension() throws Exception {
Connection c = getConnection();
PreparedStatement stmt = c.prepareStatement("INSERT INTO sal_emp VALUES ('multi_Bill',?,?);");
Array intArray = c.createArrayOf("integer", new Integer[] {1000,1000,1000,1000});
String[][] elements = new String[2][];
elements[0] = new String[] {"meeting_m","lunch_m"};
elements[1] = new String[] {"training_m","presentation_m"};
//Note - although this is a multi-dimensional array, we still supply the base element of the array
Array multiArray = c.createArrayOf("text", elements);
stmt.setArray(1, intArray);
stmt.setArray(2, multiArray);
stmt.execute();
//Note - free is not implemented
//myArray.free();
stmt.close();
c.close();
}
Helpful links:
Binding parameter as PostgreSQL array
Postgres and multi-dimensions arrays in JDBC
Passing Array from Java to Postgres
Your problem is PostgreSQL can use table or complex type as function's parameter or "table or complex type"'s array as function's paramter?
postgresql all support. and when you create a table, it's auto create an complex type named same as tablename.
like :
digoal=# create table tbl123(id int, info text);
CREATE TABLE
digoal=# select typname from pg_type where typname='tbl123';
typname
---------
tbl123
(1 row)
and you can use this type in function direct.
for exp :
digoal=# create or replace function f_tbl123(i tbl123) returns tbl123 as $$
declare
begin
return i;
end;
$$ language plpgsql;
CREATE FUNCTION
digoal=# insert into tbl123 values (1,'test'),(2,'test2');
INSERT 0 2
digoal=# select f_tbl123(t) from tbl123 t;
f_tbl123
-----------
(1,test)
(2,test2)
(2 rows)
the array is also can used in postgresql function.
if you don't known how array construct in java, i think this exp can help you.
digoal=# select (unnest('{"(1,abc)","(2,ww)"}'::tbl123[])).*;
id | info
----+------
1 | abc
2 | ww
(2 rows)
digoal=# select '{"(1,abc)","(2,ww)"}'::tbl123[];
tbl123
----------------------
{"(1,abc)","(2,ww)"}
(1 row)
digoal=# select array['(1,abc)','(2,ww)'];
array
----------------------
{"(1,abc)","(2,ww)"}
(1 row)
digoal=# select array['(1,abc)','(2,ww)']::tbl123[];
array
----------------------
{"(1,abc)","(2,ww)"}
(1 row)
digoal=# select (unnest(array['(1,abc)','(2,ww)'])::tbl123).*;
id | info
----+------
1 | abc
2 | ww
(2 rows)
Related
I have a function with parameters of numerical type, I have done the query successfully, my problems is that I do not know how to pass several parameters within the IN, where id in(parameter).
The id is of type BIGINT
From JAVA I have the following query:
select parametro1, parametro2, parametro3 from function_detalls ("parameters");
Where parameters is a string type 1,2,3,4,5,6,7, ....
My question is how can I pass this type of parameter to the function?
Pass an array to your function and use WHERE = ANY().
For example:
CREATE FUNCTION func_test(params text[])
RETURNS SETOF test
LANGUAGE PLPGSQL
AS $$
DECLARE
BEGIN
RETURN QUERY
SELECT * FROM test WHERE txt = ANY(params);
END;
$$
To call:
SELECT * FROM func_test(ARRAY['hello', 'world']);
DBFiddle to show it in action
How can I return a set of data from PL/SQL?
I have a stored procedure that needs to run a select statement and returns the result back to the calling (Java) program.
The select statement comprises of a few joins from multiple tables, so I am not sure how to define this type in the stored procedure definition or body.
I was thinking maybe this can be done as following, but SQL Developer is giving me errors:
CREATE OR REPLACE PACKAGE my_package
AS
TYPE a_collection_records IS RECORD (
NUMBER FIRST_COL,
VARCHAR2 SECOND_COL -- a few others
);
-- Procedure API that uses a_collection_records type
END;
CREATE OR REPLACE PROCEDURE sample_procedure(
p_some_select_sql_result OUT my_package.a_collection_records
)
AS
BEGIN
-- Populate p_some_select_sql_result with some select data
END;
Unless you are particularly set on using a collection, it would be simpler to use a ref cursor:
CREATE OR REPLACE PROCEDURE sample_procedure (
p_some_select_sql_result OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN p_some_select_sql_result FOR
SELECT ...
FROM ...
JOIN ...
... etc.;
END;
/
From JDBC you can then do something like:
cStmt = conn.prepareCall('{ call sample_procedure(?) }');
cStmt.registerOutParameter(1, oracle.jdbc.OracleTypes.CURSOR);
cStmt.execute();
rSet = cStmt.getCursor(1);
and you can then iterate over the result set as you would with any other.
You could also use a function instead:
CREATE OR REPLACE FUNCTION sample_function RETURN SYS_REFCURSOR
AS
l_some_select_sql_result
BEGIN
OPEN l_some_select_sql_result FOR
SELECT ...
FROM ...
JOIN ...
... etc.;
RETURN l_some_select_sql_result;
END;
/
and
cStmt = conn.prepareCall('{ ?=call sample_function }');
cStmt.registerOutParameter(1, oracle.jdbc.OracleTypes.CURSOR);
cStmt.execute();
rSet = cStmt.getCursor(1);
Obviously you need to handle any other parameters you're passing to your real procedure/function.
Your type definition is a little bit out of order. You have the type definition before the name of the item.
TYPE a_collection_records IS RECORD (
NUMBER FIRST_COL,
VARCHAR2 SECOND_COL -- a few others
);
It should be
TYPE a_collection_records IS RECORD (
FIRST_COL NUMBER,
SECOND_COL VARCHAR2 -- a few others
);
The name of the column comes before the column's datatype. I hope this is what you are looking for. You could always do a refcursor, but if you want names that are not the actual column names on the tables you are selecting from then you will still what a record type.
To be able to create it as a custom set just declare after the close of the RECORD definition the following line of code
TYPE collection_list IS TABLE a_collection_records;
Complete example:
TYPE a_collection_records IS RECORD (
FIRST_COL NUMBER,
SECOND_COL VARCHAR2 -- a few others
);
TYPE collection_list IS TABLE OF a_collection_records;
That will give you a custom (and column masked) set of data.
I have an Oracle stored procedure that takes an array of clobs that need to be invoked from Java using JDBC. I have the data as a Set in my java code.
Tried several different approaches and nothing seems to work. Anyone has sample code to do this, please post.
Developer of the stored procedure has defined a custom data type called "CLOB_ARRAY" which is a TABLE of CLOBS.
When it is an array of VARCHAR it works fine.
I found a work around using Oracle Type and Struct. Below is a summary of the solution.
STEP 1:
Create a type as below - this has to be done at the database level using SQL Developer or SQL Plus. Not within the package
create or replace TYPE TYPE_DTAP_RECORD_STORE AS OBJECT( DATA_STORE_ID VARCHAR2(300), INDEX_RECORD CLOB);
STEP 2 :
In the package define an array of the above type
TYPE RECORD_ARRAY IS TABLE OF TYPE_DTAP_RECORD_STORE INDEX BY BINARY_INTEGER
STEP 3 : Create the stored procedure as below
procedure baseline_record_insert_bulk (i_record in record_array);
STEP 4:
In Java, write a DAO method as below to call the stored procedure
public void bulkAddToRecordStore(Map<String,String> jsonArray) throws SQLException {
List<Object>recordList = new ArrayList<>();
OracleConnection oraConnection = getConnection();
OracleCallableStatement cs =(OracleCallableStatement) getConnection().prepareCall("BEGIN SCHEMA.PACKAGE.baseline_record_insert_bulk(?, ?); END;")
for(String key :jsonArray.keySet()){
Clob clob=oraConnection.createClob();
clob.setString(1, jsonArray.get(key));
Struct rec=oraConnection.createStruct("SCHEMA.TYPE_DTAP_RECORD_STORE", new Object[]{key,clob});
recordList.add(rec);
}
Array sqlArray = oraConnection.createOracleArray("SCHEMA.PACKAGE.RECORD_ARRAY", recordList.toArray());
cs.setObject(1, sqlArray);
cs.execute();
sqlArray.free();
cs.close();
}
I am looking for suggestions on how to call a postgres function that has an argument that is an array of composite type. Similar questions have been asked but I have found a satisfactory answer.
I have the following composite type:
CREATE TYPE collect.event_record AS (
event_type integer
, event_timestamp timestamp without time zone
, event_data text
, event_import_id integer
);
I have the following function:
CREATE OR REPLACE FUNCTION collect.insert_events(
fail_on_duplicates boolean,
source_public_id text,
event_records collect.event_record[])
RETURNS integer AS
...
On the postgres side everything seems to work fine. Now I just need to invoke it from java/kotlin.
We use this version of postgres driver:
compile group: "org.postgresql", name: "postgresql", version: "9.4.1212"
On PreparedStatement there's a method that seems to be what I am looking for:
void setArray (int parameterIndex, Array x) throws SQLException;
The array type is java.sql.Array which from what I can tell can be created using the Connection object:
Array createArrayOf(String typeName, Object[] elements) throws SQLException;
However, here I am not sure what to put in. What should typeName be? I imagine that I should create a class that matches the composite type and that I serialize the fields as necessary or it's an array of java.sql.Struct.
I have been googling for examples but they seem to deal with primitive types mostly which doesn't help in my case.
An alternative is to refactor insert_events to accept several arrays of primitive types, sort of a column view of my objects.
Another alternative is to send in a JSON array which I transform into an array of collect.event_record[] inside the postgres function.
However, I would like to find a way that allowed me to keep the postgres function signature I have currently.
Any ideas are greatly appreciated.
As far as I know the Postgres jdbc driver doesn't support Struct, but there is another workaround for arrays of composite types. The documentation says that it is possible to create an instance of a composite type using the following construction: (value_of_event_type,value_of_event_timestamp,value_of_event_data,value_event_import_id). The event_record data structure can be defined in the following way:
data class EventRecord(
val eventType: Int,
val eventTimestamp: Instant,
val eventData: String,
val eventImportId: Int) {
fun toSqlRow(): String {
return ("(" + eventType + ","
+ eventTimestamp + ","
+ eventData + ","
+ eventImportId +
")")
}
}
The next step is to create PreparedStatement:
val preparedStatement = con.prepareStatement("SELECT * FROM public.insert_events(?, ?, ?)")
// ... define other parameters
preparedStatement.setArray(3, con.createArrayOf("public.event_record", records.map { e -> e.toSqlRow() }.toTypedArray()))
Created PreparedStatement can be used to query the database.
PS: The current implementation will not work for text values containing ,, ", '. Wrap the text value in quotes if it contains , and replace " with \".
I have declared package level type this way (using Oracle XE 11):
create or replace PACKAGE RM_TYPES
AS
TYPE RECPPART_ARR IS TABLE OF RM_RECEPCIONPARTIDAS%ROWTYPE;
END RM_TYPES;
I have SP like this:
create or replace PROCEDURE "RM_TRY_B" (partidas OUT RM_TYPES.RECPPART_ARR) as
begin
SELECT * BULK COLLECT INTO partidas FROM rm_recepcionpartidas;
end;
I have java code like this:
CallableStatement cstmt = conn.prepareCall("{call RM_TRY_B(?)}");
cstmt.registerOutParameter(1, OracleTypes.ARRAY, "RM_TYPES.RECPPART_ARR");
cstmt.execute();
Array a = cstmt.getArray(1);
It gives me an excepcion:
Exception in thread "main" java.sql.SQLException: invalid name pattern: RM_TYPES.RECPPART_ARR
I have already granted access to package to my user by issuing this command to oracle:
GRANT EXECUTE ON RM_TYPES TO myuser;
I used this as reference: https://docs.oracle.com/database/121/JJDBC/apxref.htm#JJDBC28913 (section named: Creating Java level objects for each row using %ROWTYPE Attribute
Where did I do wrong?
I've also try passing in this name in my java code: "RECPPART_ARR" or "MYSCHEMA.RM_TYPES.RECPPART_ARR" none of them works.
Then I read someone said this on stackoverflow: java - passing array in oracle stored procedure : "actually the problem is that any type created within a package is not visible by java. If I create the type at schema level then it works. "
Is it true?
Then maybe I should define an alias at schema level?
How? I tried "CREATE SYNONYM":
CREATE PUBLIC SYNONYM RECPPART_ARRAY FOR RM_TYPES.RECPPART_ARR;
And then (tried to modify my SP):
create or replace PROCEDURE "RM_TRY_B" (partidas OUT RECPPART_ARRAY) as
begin
SELECT * BULK COLLECT INTO partidas FROM rm_recepcionpartidas;
end;
But this time this SP wouldn't compile, with this error message in my SQLDeveloper: Error(1,36): PLS-00905: object MYSCHEMA.RECPPART_ARRAY is invalid.
Then I tried using the previous definition of my sp:
create or replace PROCEDURE "RM_TRY_B" (partidas OUT RM_TYPES.RECPPART_ARR) as
begin
SELECT * BULK COLLECT INTO partidas FROM rm_recepcionpartidas;
end;
And modified my Java code to use the synomim instead:
CallableStatement cstmt = conn.prepareCall("{call RM_TRY_B(?)}");
cstmt.registerOutParameter(1, OracleTypes.ARRAY, "RECPPART_ARRAY");
cstmt.execute();
Array a = cstmt.getArray(1);
Still, exception, with message: Fail to construct descriptor: Unable to resolve type: "MYSCHEMA.RECPPART_ARRAY"
ADDITION
Some other info I just found:
http://oracle.developer-works.com/article/5227493/%22invalid+name+pattern%22++when+trying+to+user+packaged+TYPE
Someone wrote: I had the same issue. Managed to solve it by creating public synonym and giving grants.
As you see, I did that already, but no luck for me.
ADDITION
Or ... maybe something like this in oracle (after reading this: http://docs.oracle.com/javadb/10.10.1.2/ref/rrefsqljgrant.html ):
create or replace PACKAGE RM_TYPES
AS
TYPE RECPPART_ARR IS TABLE OF RM_RECEPCIONPARTIDAS%ROWTYPE;
END RM_TYPES;
sqlplus (logged in as sys as SYSDBA)> GRANT USAGE ON TYPE RM_TYPES.RECPPART_ARR TO myuser;
CREATE PUBLIC SYNONYM RECPPART_ARRAY FOR RM_TYPES.RECPPART_ARR;
create or replace PROCEDURE "RM_TRY_B" (partidas OUT RM_TYPES.RECPPART_ARR) as
begin
SELECT * BULK COLLECT INTO partidas FROM rm_recepcionpartidas;
end;
....
I tried it..., even logged in using user "sys" as SYSDBA .... I got an error when issuing grant:
Error starting at line : 1 in command -
GRANT USAGE TYPE ON RM_TYPES.RECP_ARR TO myuser
Error report -
SQL Error: ORA-00990: missing or invalid privilege
00990. 00000 - "missing or invalid privilege"
*Cause:
*Action:
I'm running out of idea now.
JDBC Support for PL/SQL Data Types as Parameters is a new feature of Oracle 12c.
PL/SQL types look and act like regular types; they can be used in SQL and other contexts, they have a TYPE_OID and TYPECODE, and they have a data dictionary view (DBA_PLSQL_TYPES). One odd difference is that PL/SQL types do not show up in DBA_OBJECTS.
In older versions you must create a TYPE as a stand-alone object in order to use it outside of PL/SQL. Code like this can create the objects:
CREATE OR REPLACE TYPE RECPPART_REC IS OBJECT
(
--list RM_RECEPCIONPARTIDAS columns here. %ROWTYPE is not available in SQL.
);
CREATE OR REPLACE RECPPART_ARR IS TABLE OF RECPPART_REC;
You could make use of a little-known feature in PL/SQL: PIPELINED functions. An example:
create table tab (
id number(7)
);
/
insert into tab values (1);
insert into tab values (2);
create or replace package pkg
as
type typ is table of tab%rowtype;
end pkg;
/
create or replace procedure proc (param out pkg.typ) as
begin
select * bulk collect into param from tab;
end;
/
create or replace function func return pkg.typ pipelined as
begin
for rec in (select * from tab) loop
pipe row(rec);
end loop;
end;
/
select * from table(func);
The above will yield:
ID
--
1
2
So you can materialise the table type also easily from JDBC.
The reason for this is the fact that every pipelined function implicitly creates a top-level SQL type that is of the same type as your PL/SQL table type. In the above case something like:
create or replace type SYS_PLSQL_29848_13_1 as object (ID NUMBER(7));
create or replace type SYS_PLSQL_29753_9_1 as table of SYS_PLSQL_29848_13_1;
This is more of a side-note. In general, you should probably prefer the approach suggested by Jon Heller