A CallableStatement was executed with nothing returned - java

I have a Java exception when calling a function that returns nothing:
org.postgresql.util.PSQLException: A CallableStatement was executed with nothing returned.
The Java code is similar to this:
// Procedure call.
CallableStatement proc = con.prepareCall("{ ? = call doquery ( ? ) }");
proc.registerOutParameter(1, Types.Other);
proc.setInt(2, 33434);
proc.execute();
ResultSet results = (ResultSet) proc.getObject(1);
while (results.next()) {
// do something with the results...
}
results.close();
proc.close();
The query is very simple:
select * from table where idTable = 33434;
The query does not return any value because what I'm looking for in postgresql DB does not exist. A sql query is like that, not always we get something in return.
How do you deal with this situations?
PS.- The Postgresql function:
CREATE OR REPLACE FUNCTION doquery(_idTable bigint)
RETURNS TABLE(idTable bigint, name varchar) AS
$BODY$
DECLARE
searchsql text := '';
BEGIN
searchsql := 'SELECT * FROM table
WHERE idTable = ' || _idTable;
RETURN QUERY EXECUTE searchsql;
END
$BODY$
LANGUAGE plpgsql;

Don't use a CallableStatement. They are intended for stored procedures not functions.
As your function returns a resultset, you need to use a select statement:
PreparedStatement pstmt = con.prepareStatement("select * from doquery(?)");
pstmt.setInt(1, 33434);
ResultSet results = pstmt.executeQuery();
while (results.next()) {
// do something with the results...
}
results.close();
proc.close();
Note that the use of dynamic SQL or even PL/pgSQL is not needed. You should also not append parameters to queries (the same way you shouldn't do it in Java as well). Use parameter placeholders:
CREATE OR REPLACE FUNCTION doquery(_idTable bigint)
RETURNS TABLE(idTable bigint, name varchar) AS
$BODY$
BEGIN
RETURN QUERY
SELECT *
FROM table
WHERE idTable = _idTable;
END
$BODY$
LANGUAGE plpgsql;
Or even simpler as a pure SQL function:
CREATE OR REPLACE FUNCTION doquery(_idTable bigint)
RETURNS TABLE(idTable bigint, name varchar) AS
$BODY$
SELECT idtable, name
FROM table
WHERE idTable = _idTable;
$BODY$
LANGUAGE sql;
If you do need dynamic SQL then use placeholders inside the string and pass the parameters to the execute function. Do not concatenate values:
CREATE OR REPLACE FUNCTION doquery(_idTable bigint)
RETURNS TABLE(idTable bigint, name varchar) AS
$BODY$
BEGIN
RETURN QUERY EXECUTE '
SELECT *
FROM table
WHERE idTable = $1'
USING _idTable;
END
$BODY$
LANGUAGE plpgsql;

Related

How to convert a Store Proc in Postgresql to simple Java inline query

I have the below SP which I am trying to convert into simple Java inline query :
CREATE OR REPLACE PROCEDURE public.spdummytable(
par_zone_no integer,
par_fpsallocid integer,
INOUT p_refcur refcursor)
LANGUAGE 'plpgsql'
AS $BODY$
BEGIN
OPEN p_refcur FOR
SELECT
z.zone_no,
m AS monthnumber,
COALESCE(fpsallocid, par_FPSallocid) AS fpsallocid,
to_char((CAST ('2000-01-01' AS TIMESTAMP WITHOUT TIME ZONE))::TIMESTAMP + (COALESCE(aw.month, m) - 1::NUMERIC || ' MONTH')::INTERVAL, 'Month') AS monthname,
week1,
week2,
week3,
week4
FROM (SELECT par_Zone_No AS zone_no) AS z
CROSS JOIN (SELECT 1 AS m
UNION SELECT 2
UNION SELECT 3
UNION SELECT 4
UNION SELECT 5
UNION SELECT 6
UNION SELECT 7
UNION SELECT 8
UNION SELECT 9
UNION SELECT 10
UNION SELECT 11
UNION SELECT 12) AS moty
LEFT OUTER JOIN anotherTable AS aw
ON z.zone_no = aw.zone_no AND
aw.month = moty.m AND
COALESCE(fpsallocid, par_FPSallocid) = par_FPSallocid;
END;
$BODY$;
ALTER PROCEDURE public.spdummytable(integer, integer, refcursor)
OWNER TO postgres;
This will fetch some weekly values for every month from Jan to Dec.
What I am trying is below :
public List<MyResponse> result = null;
Connection conn = DatabaseConnection.connect();
PreparedStatement stmt = conn.prepareStatement("call public.spdummytable(?,?,?,,....,cast('p_refcur' as refcursor)); FETCH ALL IN \"p_refcur\";");
stmt.setString(1, "8006");
stmt.setString(2, "8049");
----
----
boolean isResultSet = stmt.execute();
if(isResultSet) {
ResultSet rs = stmt.getResultSet();
while(rs.next()) {
MyResponse myResponse = new MyResponse();
myResponse.setSomeVariable(rs.getString("columnname"));
-----
-----
result.add(myResponse)
}
rs.close();
conn.close();
}
But I am confused on the query formation part from the above SP. This seems to be a complex conversion. Can someone please help form the inline query. Appreciate your help on this.
EDIT/UPDATE
If I am unable to explain myself, I just want to say that I need to form the postgresql SELECT query from the above SP. I know the PreparedStatement is wrong above, I am trying to form a basic sql query from the above SP . Changing/Modifying the SP is not an option for me. I am planning to cut the dependency from the database and control it over Java. Please help.
I don't think getResultSet works with a stored procedure like that but I'm unsure. You're operating on a cursor with your INOUT parameter. As suggested in the comments, this would be much easier with a set returning function.
Note: stored procedures didn't exist in Postgres before Postgres 11.
If you cannot convert this to a set returning function, you'll need to handle the cursor object in a different manner. Something like this:
CallableStatement stmt = conn.prepareCall("{? = call public.spdummytable(?,?) }");
stmt.registerOutParameter(1, Types.OTHER);
stmt.setString(2, "8006");
stmt.setString(3, "8049");
stmt.execute();
ResultSet results = (ResultSet) stmt.getObject(1);
while (results.next()) {
// do something with the results.
}
Set returning function:
CREATE OR REPLACE FUNCTION public.spdummytable
( par_zone_no INTEGER
, par_fpsallocid INTEGER
)
RETURNS TABLE ( zone_no INTEGER -- I don't know the data types of these fields
, monthnumber INTEGER
, fpsallocid INTEGER
, monthname TEXT
, week1 TEXT
, week2 TEXT
, week3 TEXT
, week4 TEXT
)
AS $$
BEGIN
RETURN QUERY
SELECT z.zone_no AS zn
, moty AS mo_num
, COALESCE(fpsallocid, par_FPSallocid) AS fpsid
, to_char((CAST ('2000-01-01' AS TIMESTAMP WITHOUT TIME ZONE))::TIMESTAMP + (COALESCE(aw.month, m) - 1::NUMERIC || ' MONTH')::INTERVAL, 'Month') AS mo_name
, week1 w1
, week2 w2
, week3 w3
, week4 w4
FROM (SELECT par_Zone_No AS zone_no) AS z
CROSS JOIN generate_series(1, 12) AS moty
LEFT OUTER JOIN anotherTable AS aw ON z.zone_no = aw.zone_no
AND aw.month = moty
AND COALESCE(fpsallocid, par_FPSallocid) = par_FPSallocid
;
END;
$$ LANGUAGE PLPGSQL;
You could also define your own return type and use RETURNS SETOF your_type. Use the same function body as above.
CREATE TYPE type_dummytype AS
( zone_no INTEGER -- I don't know the data types of these fields
, monthnumber INTEGER
, fpsallocid INTEGER
, monthname TEXT
, week1 TEXT
, week2 TEXT
, week3 TEXT
, week4 TEXT
);
CREATE OR REPLACE FUNCTION public.spdummytable
( par_zone_no INTEGER
, par_fpsallocid INTEGER
)
RETURNS SETOF type_dummytype
AS $$ ... $$
Then your prepared statement becomes something like this:
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM public.spdummytable(?, ?);");
stmt.setString(1, "8006");
stmt.setString(2, "8049");
All of the other java should be good to go.

Executing multiple procedure calls at once in jdbc

FYI, None of the solutions mentioned in this answer have worked for me. I intend to execute multiple procedure call in one sql query.
The Mysql code is:
SET #SYSTEM_ID = (SELECT `id` FROM `users_admin` WHERE `username`='my_username');
SET #PAYMENT_MODE = 0;
CALL payment_mode_add(#SYSTEM_ID, 'TEST', TRUE, #PAYMENT_MODE);
CALL payment_add(#SYSTEM_ID, #PAYMENT_MODE,
'receipt/00',1000.50,#TEMP_ID);
The way it's supposed to work is, Procedure "payment_mode_add" sets out an output parameter which is supposed to be used as an input parameter by the procedure "payment_add".I know that executing multiple queries at once is not possible in Java, but the method i intend to use here works well in languages like PHP. Definition for payment_mode_add is:
# -- PAYMENT-MODE ADD
DELIMITER //
DROP PROCEDURE IF EXISTS `payment_mode_add` //
# -- remove above
CREATE PROCEDURE `payment_mode_add`(IN _author INT, IN _name VARCHAR(20), IN _active BOOLEAN, OUT _id INT)
BEGIN
# -- declare
IF NOT EXISTS (SELECT `id` FROM `users_admin` WHERE `id`=_author AND `active`=TRUE) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid Author'
ELSEIF EXISTS (SELECT `id` FROM `gym_form_hhq` WHERE `name`=_name) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '\'Payment Mode\' already exists'
ELSE
SET _active = IFNULL(_active,FALSE)
INSERT INTO `payment_mode`(`name`, `active`, `author`)
VALUES ( _name , _active , _author )
SET _id = LAST_INSERT_ID()
SELECT * FROM `payment_mode` WHERE `id`=_id
END IF
END //
DELIMITER ;
Definition for "payment_add":
# -- PAYMENT ADD
DELIMITER //
DROP PROCEDURE IF EXISTS `payment_add` //
# -- remove above
CREATE PROCEDURE `payment_add`(IN _author INT, IN _mode INT, IN _receipt VARCHAR(50), IN _amount FLOAT, OUT _id INT)
BEGIN
# -- declare
IF NOT EXISTS (SELECT `id` FROM `users_admin` WHERE `id`=_author AND `active`=TRUE) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid Author'
ELSEIF NOT EXISTS (SELECT `id` FROM `payment_mode` WHERE `id`=_mode AND `active`=TRUE) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid Payment Mode'
ELSE
SET _receipt = IFNULL(_receipt, NOW())
SET _amount = IFNULL(_amount, 0)
INSERT INTO `payment`(`mode`, `reciept`, `amount`, `author`)
VALUES (_mode , _receipt , _amount , _author)
SET _id = LAST_INSERT_ID()
SELECT * FROM `payment` WHERE `id`=_id
END IF
END
This is just a basic example for some more complex problems that i'm facing in JDBC. Is there any way that i can execute all those 4 queries at once though JDBC or any other method that can give me the output i intend to achieve?
Since this question was getting many upvotes and i had found a solution to it, i'm answering it.
So all you have to do is write a method that takes in an array of string sql queries as its parameter, executes all the queries(in the array) one by one, and returns the last ResultSet. Example:
public ResultSet runMutltipleQueries(String [] arrayOfQueries) throws SQLException{
ResultSet rs = null;
Statement stmnt= con.createStatement();
boolean rsReturned=false;
for(int i=0; i<arrayOfQueries.length;i++){
rsReturned = stmnt.execute(arrayOfQueries[i]);
}
if(rsReturned){
rs= stmnt.getResultSet();
}
return rs;
}
And while calling the method,
queries= new String [] {"SET #SYSTEM_ID = (SELECT `id` FROM `users_admin` WHERE `username`='"+model.getUserName()+"');",
"SET #TRANSACTION_ID = (select `user_data`.`transaction` from `user_data` where id= "+idText.getText()+");",
"SET #FREEZE_ID = NULL;",
// set payment id to 0 for now, update it in pay operation.
"SET #PAYMENT_ID = NULL;",
"CALL user_freeze_add(#SYSTEM_ID, #TRANSACTION_ID, '"+FreezeStartDate.getValue()+"', '"+FreezeStartDate.getValue().plusDays(Integer.parseInt(freezeAvailabletext.getText()))+"', #PAYMENT_ID, #FREEZE_ID);"
};
ResultSet rs= DatabaseHandler.getInstance().runMutltipleQueries(queries);

Retrieving MySQL stored procs OUT parameters in hibernate

I have a question regarding what is the best approach to using stored procs in mysql with hibernate. I am running mysql version 5.7.14 with hibernate 4.0.0.Final as my ORM tool. Now in mysql database, I have a stored proc defined below:
DROP PROCEDURE IF EXISTS LK_spInsertBaseUser;
DELIMITER $$
CREATE PROCEDURE `LK_spInsertBaseUser`(f_name VARCHAR(255),
l_name VARCHAR(255),
n_name VARCHAR(255),
pwd VARCHAR(255),
OUT user_id INT)
BEGIN
## Declaring exit handler
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1
#state = RETURNED_SQLSTATE,
#errno = MYSQL_ERRNO,
#message = MESSAGE_TEXT;
SET #full_error = CONCAT('ERROR ', #errno, ' (', #state, '): ', #message);
SELECT #full_error;
ROLLBACK;
END;
START TRANSACTION;
IF NOT EXISTS(SELECT first_name
FROM base_user
WHERE first_name = f_name AND last_name = l_name AND nick_name = n_name)
THEN
INSERT INTO base_user (first_name, last_name, nick_name, password)
VALUES (f_name, l_name, n_name, pwd);
SET user_id = LAST_INSERT_ID();
SELECT #user_id AS userId;
ELSE
SET #exiting_user = CONCAT('Base user already exists');
SELECT #exiting_user;
ROLLBACK;
END IF;
END $$
DELIMITER ;
As we can see from my proc above, if the insert works, the id of the new record is stored in the OUT parameter user_id and we do a select as well. However, if there is a error I print out the error. Now, here is the heart of the question. I ran into a few hiccups when trying to execute the stored proc via hibernate. I finally came up with a solution but I am not convinced it is the right solution. Let me go through the various attempts i went through.
Attempt 1:
I decided to use #NamedNativeQueries annotation for my BaseUser Entity (note: base_user sql table maps to BaseUser pojo entity). Below is the code snippet:
#SqlResultSetMapping(name="insertUserResult", columns = { #ColumnResult(name = "userId")})
#NamedNativeQueries({
#NamedNativeQuery(
name = "spInsertBaseUser",
query = "CALL LK_spInsertBaseUser(:firstName, :lastName, :nickName, :password, #user_id)",
resultSetMapping = "insertUserResult"
)
})
Now, in the Dao class I created a method to invoke the named query via the session object like so:
Query query = getSession().getNamedQuery("spInsertBaseUser")
.setParameter("firstName", user.getFirstName())
.setParameter("lastName", user.getLastName())
.setParameter("nickName", user.getNickName())
.setParameter("password", user.getEncodedPassword());
Object data = query.list();
System.out.println(data);
Now this works partially. It inserts the data into the database however the data object is null. It seems the out parameter isn't set or even retrieved. I then decided to use a different approached and use the CallableStatement object. Below is the code:
Attempt 2:
getSession().doWork((Connection connection) -> {
CallableStatement statement = connection.prepareCall("{call LK_spInsertBaseUser(?, ? , ?, ?, ?)}");
statement.setString(1, user.getFirstName());
statement.setString(2, user.getLastName());
statement.setString(3, user.getNickName());
statement.setString(4, user.getEncodedPassword());
statement.registerOutParameter(5, Types.INTEGER);
statement.executeUpdate();
System.out.println(statement.getInt(5));
});
This works and it is fairly quick however, I have read that the instantiation of the prepareCall is expensive so I guess the real question is, is this solution the acceptable standard or should I continue to figure out the NamedNativeQuery approach in the quest for better performance?

I can't read in java returned array from plpgsql function

Postgres plpgsql function :
CREATE OR REPLACE FUNCTION usersList()
RETURNS TABLE(at varchar,name varchar,surname varchar) AS $$
BEGIN
RETURN QUERY SELECT * FROM users;
END;
$$ LANGUAGE plpgsql;
And java code
result = Pstatement.executeQuery("Select usersList() ");
while(result.next()) {
System.out.println(result.getString(("at")));
System.out.println(result.getString(("name")));
System.out.println(result.getString(("surname")));
}
Java error sql exception message :
Message: The column name at was not found in this ResultSet.
SQLState: 42703
ErrorCode: 0
How can i return all table columns from a function and then print them in java ?
Postgres plpgsql function :
CREATE OR REPLACE FUNCTION usersList()
RETURNS TABLE(at varchar,name varchar,surname varchar) AS $$
BEGIN
RETURN QUERY SELECT * FROM users;
END;
$$ LANGUAGE plpgsql;
And java code
result = Pstatement.executeQuery("SELECT * FROM usersList() ");
while(result.next()) {
System.out.println(result.getString(("at")));
System.out.println(result.getString(("name")));
System.out.println(result.getString(("surname")));
}
Credits to RealSkeptic && Nick Barnes !!!
You can use result.getMetaData().getColumnCount() to see how many columns you retrieved and then you can call result.getMetaData().getColumnName(x) to check the name of the column (replace x with the number of column).
So technically you should be able to write your code block like so:
result = Pstatement.executeQuery("Select usersList() ");
String mesites[];
while(result.next()) {
int columnCount = result.getMetaData().getColumnCount();
System.out.println("Found:"+columnCount+" columns.");
for(int i=1; i<=columnCount; i++){
System.out.println(result.getString(result.getMetaData().getColumnName(i)));
}
}
Which should then print out any columns retrieved in that result set regardless of names (if any).

Java call User Defined Table Function DB2 with prepared statement

I'm developing a web app with Java 6 EE and DB2. I created a table function that receives 3 parameters and returns a table.
CREATE FUNCTION MY_FUNCTION (PARAM1 VARCHAR(5), PARAM2 VARCHAR(10), PARAM3 INTEGER)
RETURNS TABLE (
FIELD1 VARHCHAR(5),
FIELD2 VARCHAR(10),
FIELD3 INTEGER
)
RETURN
SELECT FIELD1, FIELD2, FIELD3
FROM TABLE_1 WHERE FIELD1 = PARAM1 || '_MAIN'
AND FIELD2 = PARAM2 || '_MAIL' AND FIELD3 = PARAM3 + 47
I'm trying to execute a function in Java with prepared statement as follows (using wildcards):
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM TABLE(MY_FUNCTION(?, ?, ?)) AS TABLE");
But when I run my code, I get an SQLSyntaxErrorException in the prepared statement:
java.sql.SQLSyntaxErrorException: [SQL0418] A statement contains a use of a parameter marker that is not valid
at com.ibm.as400.access.JDError.createSQLExceptionSubClass(JDError.java:828)
at com.ibm.as400.access.JDError.throwSQLException(JDError.java:699)
at com.ibm.as400.access.JDError.throwSQLException(JDError.java:669)
at com.ibm.as400.access.AS400JDBCStatement.commonPrepare(AS400JDBCStatement.java:1660)
at com.ibm.as400.access.AS400JDBCPreparedStatement.<init>(AS400JDBCPreparedStatement.java:248)
at com.ibm.as400.access.AS400JDBCCallableStatement.<init>(AS400JDBCCallableStatement.java:120)
at com.ibm.as400.access.AS400JDBCConnection.prepareCall(AS400JDBCConnection.java:1840)
at com.ibm.as400.access.AS400JDBCConnection.prepareCall(AS400JDBCConnection.java:1741)
Note: If I hardcode the parameters like this (without wilcards) works:
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM TABLE(MY_FUNCTION('" + var1 + "', '" + var2 + "', '" + var3 + "')) AS TABLE");
What I want to achieve is to call the functions with the wildcards to improve the processing of the function.
Thanks in advance
Solution with #user384842 answer
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM TABLE(MY_FUNCTION(cast(? as VARCHAR(5)), cast(? as VARCHAR(10)), cast(? as INTEGER))) AS TABLE");
After hunting a bit on google, looks like maybe you need to cast them to the appropriate type? I found this documentation:
requiresCastingOfParametersInSelectClause()
DB2 in fact does require that parameters appearing in the select clause be wrapped in cast() calls to tell the DB parser the type of the select value.
here:
https://docs.jboss.org/hibernate/orm/3.5/api/org/hibernate/dialect/DB2Dialect.html
Not sure if it's relevant, but might be worth a go? I guess it would look something like cast(? as varchar(30))
Link on casting here http://www.dbatodba.com/db2/how-to-do/how-to-convert-data-types-on-db2/

Categories

Resources