I'm newbie in Java and PLSQL. I've build this procedure in PLSQL:
PROCEDURE getLogs (
p_idcontract IN NUMBER,
p_iduser IN NUMBER,
o_logs OUT VARCHAR2
)
IS
BEGIN
SELECT logData INTO o_logs
FROM SERVICELOG
WHERE IDCONTRACT= p_idcontract
AND IDUSER= p_iduser;
END getLogs;
If script detect several rows, return to the Java class who launch it this message:
ORA-01422: exact fetch returns more than requested number of rows
Please, how I can return an hashmap or similar, ready to be outputted to Java? Thanks.
I would use ref cursor as an output parameter then build map inside java program. https://oracle-base.com/articles/misc/using-ref-cursors-to-return-recordsets
You can also define a user type in oracle that will be something like Java Array.
PROCEDURE getLogs (
p_idcontract IN NUMBER,
p_iduser IN NUMBER,
p_recordset OUT SYS_REFCURSOR
)
IS
BEGIN
OPEN p_recordset FOR
SELECT logData
FROM SERVICELOG
WHERE IDCONTRACT= p_idcontract
AND IDUSER= p_iduser;
END getLogs;
Java code:
CallableStatement stmt = conn.prepareCall("BEGIN getLogs(?, ?, ?); END;");
stmt.setInt(1, 0);
stmt.setInt(2, 0);
stmt.registerOutParameter(3, OracleTypes.CURSOR);
stmt.execute();
ResultSet rs = ((OracleCallableStatement)stmt).getCursor(3);
while (rs.next()) {
;
//here build your Map, list or whatever you want
}
Related
I am calling postgreSQL function from Java using SimpleJdbcCall and extracting the output in ResultSet, while the function returns data when run in postgre but in Java it is giving null result.
Here is my postgre function that I am calling:
CREATE OR REPLACE FUNCTION package.getdemojob(create_user_id character varying)
RETURNS TABLE(batch_id bigint, content_id character varying, start_date timestamp without time zone,
end_date timestamp without time zone)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN query
SELECT MY_TABLE.BATCH_ID, MY_TABLE.CONTENT_ID, MY_TABLE.START_DATE, MY_TABLE.END_DATE
FROM MY_TABLE
WHERE MY_TABLE.CREATE_USER = create_user_id
ORDER BY BATCH_ID;
END;
$function$
;
Java code that I am using to call this function is:
this.demoGetJob = new SimpleJdbcCall(jdbcTemplate)
.withCatalogName("package")
.withProcedureName("getdemojob")
.withoutProcedureColumnMetaDataAccess()
.useInParameterNames("create_user_id")
.declareParameters(
new SqlParameter("create_user_id", Types.VARCHAR)
);
SqlParameterSource in = new MapSqlParameterSource().addValue("create_user_id", userId);
ResultSet rs = demoGetJob.executeFunction(ResultSet.class, in);
while(rs.next()){
System.out.println(rs.getInt("batch_id"));
}
On line rs.next() I am getting nullPointerException though there is data present in MY_TABLE with the given create_user_id
What wrong am I doing?
Thanks in advance.
I want to use procedure multiple times to get many table select from oracle database
My Oracle procedure
PROCEDURE getInfo(
Status IN VARCHAR2,
P_CUR OUT REFCURSOR)
AS
BEGIN
OPEN P_CUR FOR
SELECT *
FROM TABLE
WHERE TABLE.STATUS = Status
END;
Here is my Java call the the procedure. It doesn't work, I can not set registerOutParameter for PreparedStatement to get the cursor data.
PreparedStatement pstmt = null;
pstmt = cnn.prepareCall("{call " + schemaName + ".LOC_EXCHANGE.getInfo(?,?)}");
for (Entity entity : ListEntity) {
int i = 1;
pstmt.setString(i++, entity.getTxnId());
pstmt.registerOutParameter(i, OracleTypes.CURSOR);
pstmt.addBatch();
}
pstmt.executeBatch();
cnn.commit();
rs = (ResultSet) pstmt.getObject(i);
Fetching the data in bulk rather than in batch
I don't think you can batch those calls. You could probably do an anonymous block, instead:
BEGIN
LOC_EXCHANGE.getInfo(?, ?);
LOC_EXCHANGE.getInfo(?, ?);
LOC_EXCHANGE.getInfo(?, ?);
...
END;
Avoiding N+1
Personally, I think you're producing an N+1 problem here. rather than fetching data for each individual entity.getTxnId(), how about refactoring your getInfo() procedure to allow for returning data for a list of txnids? Do this:
CREATE TYPE txnids AS TABLE OF VARCHAR2(4000);
And then declare:
PROCEDURE getInfo(
Status IN txnids ,
P_CUR OUT REFCURSOR)
AS
BEGIN
OPEN P_CUR FOR
SELECT *
FROM TABLE
WHERE TABLE.STATUS IN (
SELECT * FROM TABLE (Status)
)
ORDER BY TABLE.STATUS
END;
Since you're projecting *, you'll still be able to access the STATUS column in order to group data in the client, after fetching it all.
I am trying to get 3 fields from 1 table using a function and I am getting errors:
CREATE OR REPLACE FUNCTION sp_search_test_ui_test_prog_revision(dev TEXT)
RETURNS table (dev_op_test_id BIGINT, test_program_name TEXT, test_program_revision TEXT)
AS $$
BEGIN
RETURN QUERY
SELECT dev_op_test_id, test_program_name, test_program_revision
FROM dev_op_test
WHERE device = dev
ORDER BY dev_op_test_id;
END;
$$ LANGUAGE plpgsql;
The JAVA code is below: (device is a String passed to this method) Is this where the error is?
// Get unique devices from dev_op_test
String sql = " SELECT sp_search_test_ui_test_prog_revision(" + device + ") ";
PreparedStatement statement = pgConn.prepareStatement(sql);
ResultSet rs = statement.executeQuery();
// Clear from previous run
cboTestProgDev.getItems().clear();
while (rs.next()) {
TestProgRev tpr = new TestProgRev();
tpr.setDevOpTestId(rs.getLong(1));
tpr.setTestProgramName(rs.getString(2));
tpr.setTestProgramRevision(rs.getString(3));
testProgs.add(tpr);
cboTestProgDev.getItems().add(tpr.toString());
}
And this is the error I keep getting. Notice it's telling me the column doesn't exist, which is true because that is a value not a column name. Any ideas??? I know it may be something simple, I just can't seem to ding the anwser.
The query you are sending is
SELECT sp_search_test_ui_test_prog_revision(mnf0306aa)
Do you notice the missing single quotes around the string? That is why PostgreSQL interprets it as a column name and complains that the column does not exist.
Composing queries with string concatenation is dangerous, it exposes you to the dangers of SQL injection. If device contains a single quote, your statement would cause an error or worse – a skilled attacker could do anything with your database.
Use the power of prepared statements to avoid that danger:
java.sql.PreparedStatement statement =
pgConn.prepareStatement("SELECT * FROM sp_search_test_ui_test_prog_revision(?)";
statement.setString(1, device);
java.sql.ResultSet rs = statement.executeQuery();
I have a stored procedure that calls another stored procedure. The inner stored procedure returns a result set. After using a CallableStatement to execute the calling stored procedure I am unable to get the result set returned by called stored procedure.
I tried both execute and executeQuery for execution of callable statement. When I execute the calling stored procedure from SQL Server I am getting proper results.
Calling procedure:-
ALTER PROC [User].[Get_Data]
(#UserID NVARCHAR(20))
AS
BEGIN
Select 'User Data'
Exec [Order].[Get_Order] #UserID
END
Called procedure:-
ALTER PROC [Order].[Get_Order]
(#UserID NVARCHAR(20))
AS
BEGIN
Select * from orders where userId=#UserID
END
Your outer stored procedure is returning two result sets:
The results from Select 'User Data'
The results from Exec [Order].[Get_Order] #UserID
You need to call .getMoreResults() in order to retrieve the second result set, e.g.,
CallableStatement cs = connection.prepareCall("{CALL Get_Data (?)}");
cs.setString(1, "gord");
ResultSet rs = cs.executeQuery();
System.out.println("[First result set]");
while (rs.next()) {
System.out.printf("(No column name): %s%n", rs.getString(1));
}
cs.getMoreResults();
rs = cs.getResultSet();
System.out.println();
System.out.println("[Second result set]");
while (rs.next()) {
System.out.printf("userId: %s, orderId: %s%n",
rs.getString("userId"), rs.getString("orderId"));
}
producing
[First result set]
(No column name): User Data
[Second result set]
userId: gord, orderId: order1
userId: gord, orderId: order2
(Tested using mssql-jdbc-6.2.1.jre8.jar connecting to SQL Server 2014.)
For more details, see
How to get *everything* back from a stored procedure using JDBC
You cannot select the results of a stored procedure directly within SQL Server itself. You need to first insert the result into a temp table as per example below.
Example use:
-- Create a tempoary table to store the results.
CREATE TABLE #UserOrderDetail
(
UserData NVARCHAR(50) -- Your columns here
)
-- Insert result into temp table.
-- Note that the columns returned from procedure has to match columns in your temp table.
INSERT INTO #UserOrderDetail
EXEC [Order].[Get_Order] #UserID
-- Select the results out of the temp table.
SELECT *
FROM #UserOrderDetail
If the intent is to simply return one or more result sets to a client application, you should ensure that the SET NOCOUNT ON statement is added to the top of your stored procedures, this will prevent SQL Server from sending the DONE_IN_PROC messages to the client for each statement in the stored procedure. Database libraries like ODBC, JDBC and OLEDB can get confused by the row counts returned by the various insert and update statements executed within a SQL Server stored procedures. Your original procedure will look as follow:
ALTER PROC [User].[Get_Data]
(
#UserID NVARCHAR(20)
)
AS
BEGIN
SET NOCOUNT ON
SELECT 'User Data'
EXEC [Order].[Get_Order] #UserID
END
The correct way to do this with JDBC
Getting this right with JDBC is quite hard. The accepted answer by Gord Thompson might work, but it doesn't follow the JDBC spec to the word, so there might be edge cases where it fails, e.g. when there are interleaved update counts (known or accidental), or exceptions / messages.
I've blogged about the correct approach in detail here. The Oracle version is even more tricky, in case you need it. So here it goes:
// If you're daring, use an infinite loop. But you never know...
fetchLoop:
for (int i = 0, updateCount = 0; i < 256; i++) {
// Use execute(), instead of executeQuery() to handle
// leading update counts or exceptions
boolean result = (i == 0)
? s.execute()
: s.getMoreResults();
// Warnings here
SQLWarning w = s.getWarnings();
for (int j = 0; j < 255 && w != null; j++) {
System.out.println("Warning : " + w.getMessage());
w = w.getNextWarning();
}
// Don't forget this
s.clearWarnings();
if (result)
try (ResultSet rs = s.getResultSet()) {
System.out.println("Result :");
while (rs.next())
System.out.println(" " + rs.getString(1));
}
else if ((updateCount = s.getUpdateCount()) != -1)
System.out.println("Update Count: " + updateCount);
else
break fetchLoop;
}
Using jOOQ
Note that in case you're using jOOQ, you could leverage code generation for your stored procedures and call the simplified API to do this in a few lines only:
GetDatap = new GetData();
p.setUserId("gord");
p.execute(configuration);
Results results = p.getResults();
for (Result<?> result : results)
for (Record record : result)
System.out.println(record);
Disclaimer: I work for the company behind jOOQ
I have a procedure I'm calling using a JDBC callable statement. The first parameter, v_type, has a default value I want to use. The proc/code is simplified.
PROCEDURE select_type(v_type IN VARCHAR2 DEFAULT 'All',
cv_1 OUT SYS_REFCURSOR)
AS
BEGIN
IF (v_type = 'All')
THEN
OPEN cv_1 FOR
SELECT DISTINCT TYPE,
FROM type_source;
ELSE
IF (v_type = 'NotAll')
THEN
OPEN cv_1 FOR
SELECT DISTINCT type, desc,
FROM OTHER_TYPE_SOURCE;
END IF;
END;
This is the code I attempted, assuming null would trigger the default.
callablestatement = connection.prepareCall("{ CALL select_type(?,?) } ") ;
callablestatement.setString(1, null);
callablestatement.registerOutParameter(2, OracleTypes.CURSOR);
callablestatement.execute();
resultset = (ResultSet)callablestatement.getObject(2);
The last line .getObject() throws a cursor closed exception, presumably, due to the null parameter not matching the IF structure in the procedure and the cursor never being opened.