I have a db2 table t1 where one of the columns is populated by utilizing a scalar function that takes two string params.
function(string foo, string bar)
In prepare the sql code as follows:
UPDATE t1 SET col1 = function(?, 'foobar') WHERE t1.id = ?
When i now add a parameter to the prepared statement i get an error that the first parameter is of the wrong type.
for (Entry entry : entries) {
preparedStatement.setString(1, entry.getDesc());
preparedStatement.setInt(2, entry.getId());
}
I already tried wrapping the first param in single quotes but that didn't work. Did anyone else have this problem and has come to a solution?
The main problem is that i'm now sure how the db2 engine prepares the first string parameter for the scalar function so that the error is thrown in the first place.
Try function(cast(? as varchar(xxx)), 'foobar'), where xxx the corresponding length of the 1-st string parameter of the function. The function's 1-st parameter must be of varchar data type in this case. If its 1-st parameter is of another data type, then use the corresponding cast and set<Datatype>.
You need to do it, since there may be multiple functions with the same name and number of parameters, but with different data types. And Db2 must know which function to use exactly during the query compilation (prepare).
Related
I have a PL/SQL stored function which returns a table of integers:
CREATE TYPE INT_TABLE IS TABLE OF INTEGER;
CREATE FUNCTION f (someParam IN INTEGER) RETURN INT_TABLE IS ...
I wish to use JDBC to retrieve the result of this function so that I can iterate through the integers in some way, be it a ResultSet, int[], or whatever.
The function f performs modifications to the database, so it cannot be called in a query such as SELECT f(42) FROM DUAL;. The return value exists to inform the caller of the primary keys for the rows that it modified. I presumably must use a CallableStatement, but can't figure out exactly how. Taking inspiration from here, I have tried:
CallableStatement cs = conn.prepareCall("{? = call f(42)}");
cs.registerOutParameter(1, Types.ARRAY);
cs.execute();
ResultSet rs = cs.getArray(1).getResultSet();
However, this resulted in java.sql.SQLException: Invalid argument(s) in call on the registerOutParameter line. I haven't been able to determine what this error actually means, as the docs are quite general on the exceptions that may be thrown.
I've spent hours googling and am at quite a loss as to how to proceed. Is it possible to extract a table type into Java? If not, is there some other way of returning multiple integers from the PL/SQL function that can be extracted into Java?
I'm answering my own question after figuring it out.
I was very close to the solution; I'd just missed specifying the type name in the registerOutParameter call. The final solution therefore looks like:
cs = conn.prepareCall("{? = call f(42)}");
cs.registerOutParameter(1, Types.ARRAY, "INT_TABLE");
cs.execute();
ResultSet rs = cs.getArray(1).getResultSet();
This is native JDBC and does not require any of the Oracle extensions.
One other thing that tripped me up was the structure of this ResultSet. It contains two columns; the first is the index and the second is the value (documentation link). Therefore, if you want to extract the elements of the array, you need to call rs.getInt(2) for each row rather than rs.getInt(1).
If one instead wanted the result as a plain array rather than a ResultSet, the corresponding type is java.math.BigDecimal, so it would be
...
cs.execute();
BigDecimal[] array = (BigDecimal[])(cs.getArray(1).getArray());
I'm trying to pass an array of string as a parameter to my query but I get the following error
ERROR: operator does not exist: text ~~ record Dica: No operator matches the given name and argument type(s). You might need to add explicit type casts.
Here is my query
select * from table where value like any (array[?1]);
when I run it using hibernate the query is like:
select * from table where value like any (array[('%foo%', '%bar%', '%baz%')]);
There's a best way to pass my array as parameter?? I think that is important to say that my array is dynamic so i can't fiz it in my query.
use setParameterList method.
String queryStr = "select * from table where value in :valueList";
Query query = SessionFactory.getCurrentSession().createQuery(queryStr);
query.setParameterList("valueList", new Object[]{"%foo%","%bar%","%baz%"});
Please note that I used in clause and it doesn't support wildcard characters such as %.
First use createNativeQuery instead of createQuery as syntax is native to PSQL.
Second query syntax should be select * from table where value like any (array?1) as ?1 will be substituted by ['%foo%', '%bar%', '%baz%'], so your end query will match required PSQL syntax.
select * from table where value like any (array['%foo%', '%bar%', '%baz%'])
Firstly, your syntax is wrong.
Instead of:
select * from table where value like any (array[?1]);
You should use:
select * from table where value like any (:vals);
You cannot use array[?] or array[:var] to construct the variable. That is invalid syntax.
Secondly for Hibernate 5.2 up to 5.4 you can just add this dependency and you can use the most common object arrays directly. Primitive arrays are not supported and are not supposed to be.
I have a type as:
CREATE TYPE status_record AS
(
id bigint,
status boolean
);
A procedure that does some processing with an array of type as input parameter as:
CREATE OR REPLACE FUNCTION update_status(status_list status_record[])
RETURNS text AS
$BODY$
DECLARE
BEGIN
--does some processing
return 'SUCCESS';
end;$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Finally I queried the procedure as:
select *
from update_status(cast(ARRAY[(385,false),(387,false)] as status_record[]));
Everything works fine in pgadmin. Later when I tried to call the same using Hibernate native SQL Query Ka Boom!!! The following is displayed:
org.postgresql.util.PSQLException:
ERROR: array value must start with "{" or dimension information
Final question: both ARRAY[--something] and {--something} does the same job?
Use an array literal (text representation of the array), since the array constructor ARRAY[...] has to be evaluated by Postgres:
SELECT update_status('{"(1,t)","(2,f)"}'::status_record[]);
Maybe even without the explicit cast:
SELECT update_status('{"(1,t)","(2,f)"}');
There were similar cases on SO before:
Pass array from node-postgres to plpgsql function
How to pass custom type array to Postgres function
Try to put the array and type initialization into a string, maybe you can then get around the problems with the obfuscation layer (aka ORM):
select update_status(cast('{"(1,true)", "(2,false)"}' as status_record[]));
I don't know Hibernate, so I can't tell if that will work.
I have to update table using values from a data file which contains all the rows. Now I am using JDBC batches. Data files contains 100s of columns and millions of rows.
For e.g. data file contains 3 columns two rows to make it simple
1,ABC,DEF
2.GHI,JKL
PreparedStatement pstmt = connection.prepareStatement(insert);
//how to find type
pstmt.setInt(1, 2);
pstmt.setString(2, "GHI");
pstmt.setString(3, "JKL");
pstmt.addBatch();
pstmt.executeBatch();
Now my question is at run time based on the data coming from data file how do I find that I need to call setInt or setString and more importantly how many times I need to call setXXX for that addBatch(). This seems like for each table I need to have dedicated preparedStatements. More importantly I need to find how many times I should call setObject at run based on the number of columns which is in data ile? Is there anyway I can make this generic?
I am new to JDBC please guide. Thanks in advance.
You can use setObject(int index, Object obj). JDBC then determines the correct type.
The PreparedStatement has a method setObject(int, Object). The documentation states
If arbitrary parameter type conversions are required, the method setObject should be used with a target SQL type.
If you have an SQL statement like
Select * From table Where value1 = ? and value2 = ?
you have to call the setXXX methods two times. They are used to specify the wildcard values (marked ny ?) for the SQL statement that the PreparedStatement instance represents. The number of calls is therefore dependent of your SQL statement that is referenced by your variable insert. The int argument of the setXXX methods refers to the position of the variable in the SQL statement with setXXX(1, object) referring to the first wildcard and so on.
Of course, you have to repeat the same amount of calls to setXXX for each query you add the the batch that you want to execute at the end.
You can use like below snippet. Check statement.setObject documentation for further details. The "rs" in the below snippet is a resultset got by executing some query from one table. The "query" in the below snippet is some insert or update query into different table. The below example states selection from one table and insertion into some other table while dynamically identifying the column type. Note: Table column types should match else exception will be thrown.
PreparedStatement statement = connection.prepateStatement( query );
ResultSetMetaData rsmd = rs.getMetaData();
while( rs.next() )
{
for( int i = 1 ; i <= rsmd.getColumnCount() ; i++ )
{
statement.setObject( i, rs.getObject(i), rsmd.getColumnType(i) );
}
}
I use queries like
"UPDATE MAILSCH.MESSAGE "
+ "SET IDFOLDER=?, SUBJECT=?, CONTENT=?, CREATIONTIME=?, AD_FROM=?, AD_TO=?, STATUS=? "
+ "WHERE IDMESSAGE=?";
May I miss out IDFOLDER without changing query?
No you can't. You need to conditionally insert that part of the SQL into the SQL string when needed.
No, you'll have to write a second query that doesn't include the IDFOLDER column. All parameters have to be bound.
Unfortunately not. Positional parameters ('?') are exactly that, identified by their position or order of appearance in the query. If you remove the 'IDFOLDER=?', then you will assign the wrong parameters to the remainder of the query, and possibly get an exception since the number of assigned parameters do not match the number expected in the query.
I'm assuming you can't change the source code, as that's the simplest route - change the SQL and then the JDBC parameters to match. If you need to use the same number of parameters, you can write the query in a way that doesn't change the value of IDFOLDER, yet uses the first parameter.
SET IDFOLDER=CASE ISNULL(?) WHEN 0 THEN IDFOLDER ELSE IDFOLDER END
If your JDBC driver supports named parameters, that may give you a cleaner alternative.
The javadoc doesn't explicitly say that you can't, but when I tried it, I got an exception like this:
java.sql.SQLException: Parameter not set