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
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.
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);
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?
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).
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/