I've written a PL/SQL procedure that I'm attempting to call from a Java application. The problem is that when I call the procedure in sqlplus, it runs exactly as expected, but when it's run from the Java application, produces the following error:
SQLException Message:ORA-01453: SET TRANSACTION must be first statement of transaction
ORA-06512: at "JTRIBUNA.ASSIGN_PILOT2", line 24
ORA-06512: at line 1
The Java method is as follows:
private static boolean assignFlight() throws SQLException {
CallableStatement cstmt = null;
try {
// Prepare to call the stored procedure
System.out.println("Assigning flights...");
cstmt = connection.prepareCall("{call assign_pilot2()}");
cstmt.execute();
} catch (SQLException e) {
System.out.println("Problem with assignFlight ");
throw (e); // let caller handle after above output
} finally {
//closeCallableStatement doesn't compile
//closeCallableStatement(cstmt);
cstmt.close();
}
return true;
}
And this is the script to create the procedure:
create or replace procedure assign_pilot2 as
--cursor through flights
CURSOR fl_cur IS
SELECT * FROM flights
ORDER BY departs;
--createSP1
CURSOR pl_cur IS
SELECT e.eid, ename, a.aid, cruisingrange
FROM employees1 e
INNER JOIN certified1 c
ON e.eid=c.eid
INNER JOIN aircraft a
ON c.aid=a.aid
ORDER BY cruisingrange ASC;
pl_row pl_cur%ROWTYPE;
fl_row fl_cur%ROWTYPE;
is_assigned number;
need_delay number;
BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
dbms_output.put_line('hi from SP assign_pilot2');
--loop through flights
FOR fl_row IN fl_cur LOOP
DBMS_OUTPUT.PUT_LINE('processing flight '|| fl_row.flno);
--loop cursor through pilots
FOR pl_row IN pl_cur LOOP
--see if the range is long enough
IF (pl_row.cruisingrange >= fl_row.distance) THEN
--see if pilot is available (not present in flight_assignments)
SELECT COUNT(*)
INTO is_assigned
FROM flight_assignments FA
WHERE FA.eid = pl_row.eid;
--if pilot is available
IF(is_assigned = 0) THEN
--add fight to flight_assignments;
INSERT INTO flight_assignments
VALUES(fl_row.flno, pl_row.aid, pl_row.eid);
EXIT;
END IF;
END IF;
END LOOP;
--if flight isn't present in flight_assignments, delay it
SELECT COUNT(*)
INTO need_delay
FROM flight_assignments FA
WHERE FA.flno = fl_row.flno;
IF(need_delay = 0) THEN
INSERT INTO delayed_flights
VALUES(fl_row.flno);
END IF;
END LOOP;
COMMIT;
END;
/
show errors;
The part that's really confusing me is that the error is a syntax one. As I mentioned before, I can call this procedure from sqlplus normally, and it does exactly what I want it to.
I'm new to working with transactional SQL, so any help will be greatly appreciated!
Related
I am trying to call several oracle stored procedures from my web application. There are no in or out parameters in any of the procedures. Everything works perfectly when I run my web app locally, but when I deploy the app to our test server the stored procedure calls stop working. I get no error messages and the procedure does not run.
I would think it has something to do with the JDBC driver or connection, but I'm at a loss? Any help would be greatly appreciated!
Here is the code that works perfectly when running locally and debugging:
public boolean deleteFromTempTables(String id)
{
String msg = "";
CallableStatement stmt1 = null;
try
{
conn = DBConnection.getConnection();
DbmsOutput dbmsOutput = new DbmsOutput( conn );
dbmsOutput.enable( 1000000 );
stmt1 = conn.prepareCall("{call delete_from_temp_employee()}");
stmt1.execute();
stmt1.close();
dbmsOutput.show(id);
dbmsOutput.close();
}
catch(Exception e)
{
e.printStackTrace();
return true;
}
finally
{
try
{
stmt1.close();
conn.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
return false;
}
Here is the stored procedure:
create or replace procedure delete_from_temp_employee as
temp_empe_before NUMBER;
temp_empe_after NUMBER;
begin
select count(*) into temp_empe_before from temp_employee;
delete from temp_employee;
select count(*) into temp_empe_after from temp_employee;
DBMS_OUTPUT.PUT_LINE ('Procedure DELETE_FROM_TEMP_EMPLOYEE Employee Before: ' || temp_empe_before);
DBMS_OUTPUT.PUT_LINE ('Procedure DELETE_FROM_TEMP_EMPLOYEE Employee After: ' || temp_empe_after);
end delete_from_temp_employee;
Call in JSP page:
<%
boolean isDeleteError = false;
isDeleteError = memberupload.deleteFromTempTables(userid);
%>
you need to execute:
set serveroutput ON;
this command makes output works.
also after delete statement add commit; command to commit changes if you forget it.
by the way, you can get the affected rows for DML statements like delete statement by using sql%rowcownt as following:
set serveroutput ON;
create or replace procedure delete_from_temp_employee2 as
rows NUMBER;
begin
delete from temp_employee;
rows := SQL%rowcount;
DBMS_OUTPUT.PUT_LINE ('Deleted: ' || rows || ' row(s)');
end delete_from_temp_employee2;
I have a Stored Procedure that runs on MS SQL Server. It takes one parameter as input. Basically, it returns multiple rows.
I am calling the SP from my java application by using CallableStatement.
I would like to know if it possible to get the rows returned by the stored procedure in the form of ResultSet in my DAO layer?[like how we get a resultset when we do select * from EmployeeTable] . If yes, how do we do that?
P.S: I don’t have privilege to modify the stored procedure.
SQL Server knows two types of procedures returning results:
Batches
The procedure looks something like this:
CREATE PROCEDURE p_results(
#p_result_sets INT
)
AS
BEGIN
IF #p_result_sets = 1 BEGIN
SELECT 1 a;
END
ELSE IF #p_result_sets = 2 BEGIN
SELECT 1 a;
SELECT 1 b UNION SELECT 2 b;
END
END;
In this case, you don't know in advance what the result sets will look like, and how many of them you'll get. You will have to run the procedure using Statement.execute() as follows:
try (CallableStatement stmt = con.prepareCall("...")) {
boolean results = stmt.execute();
for (;;) {
if (results)
try (ResultSet rs = stmt.getResultSet()) {
// ... Fetch your results here
}
else if (stmt.getUpdateCount() != -1) {}
else
break;
results = stmt.getMoreResults();
}
// After all results are fetched, you can also retrieve OUT parameters, if applicable
}
Table-valued functions
The function looks something like this:
CREATE FUNCTION f_tables1 ()
RETURNS #out_table TABLE (
column_value INTEGER
)
AS
BEGIN
INSERT #out_table
VALUES (1)
RETURN
END
In this case, you don't really need a CallableStatement. An ordinary SELECT statement will do:
try (PreparedStatement stmt = con.prepareStatement("SELECT * FROM f_tables1()");
ResultSet rs = stmt.executeQuery()) {
// ... Fetch your results here
}
I made a function like ...
create or replace function
pile_data
(v_id IN NUMBER, v_area IN VARCHAR2, v_cust_id IN NUMBER)
return VARCHAR2
AS
rs VARCHAR2(2);
cur_id NUMBER;
-- PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
select if_seq.currVal into cur_id from dual;
select '00' into rs from dual;
insert into IF_WORK
(id, area, cust_id, rc)
values
(if_tx_seq.nextVal, v_area, v_cust_id, rs);
update IF_WORK set rc=rs where id = cur_id;
return rs;
exception
when dup_val_on_index then
select '01' into rs from dual;
return rs;
end;
And I got 2 questions
How to call this function as test?
Is it possible to return value in exception statement?
Follow code is what I want to implement(I made it with java, but have to implement with PLSQL)
public String pile_data(String ... params){
String rs = "00";
int cur_id = SEQ.currVal;
try{
insert(params);
}catch(Exception ex){
//HANDLING ERRORS
String exceptionName = ex.getClass().getName();
switch (exceptionName) {
case "KEY_DUPLICATION":
rs = "01";
break;
case "CONNECTION_TIMEOUT":
rs = "02";
break;
default:
rollback();
rs = "99";
break;
}
}finally{
// UPDATE ORIGINAL TABLE
update(cur_id, rs);
}
return rs;
}
My Conditions about the code.
Have to return anyway(SUCCESS OR NOT)
Have to update, whether insert success or not
I am sorry about java code, but this is what I can explain best. Thanks for answers. b
======== Edit
Sorry I did not told what I have failed. (Error Code from Oracle, message translated by myself)
execute pile_data(params ... );. it returns this error ORA-06550 : it is not a procedure or not defined
select pile_data(params) from dual;. it returns error ORA-14552: Can not execute DDL, Commit, Rollback which is inside of Query or DML
================= Edit #2 ================
So I am now trying to change this to a procedure with return value.
However, since caller need to get result, I wrapped with function.
create or replace procedure
pile_data
(params ... , rs OUT VARCHAR2)
IS
cur_id NUMBER;
BEGIN
select if_seq.currVal into cur_id from dual;
select '00' into rs from dual;
insert into IF_WORK
(target params ..., rc)
values
(params ..., rs);
update IF_WORK set rc=rs where tx_id = cur_id;
-- DBMS_OUTPUT.PUT_LINE(rs); -- it does not work
exception
when dup_val_on_index then
select '01' into rs from dual;
-- DBMS_OUTPUT.PUT_LINE(rs);
when others then
rollback;
select '99' into rs from dual;
-- DBMS_OUTPUT.PUT_LINE(rs);
end;
create or replace function pile_data_wrapper(params ...)
return varchar2
is
rs varchar2(2);
begin
pile_data(params ... , rs);
return rs;
end;
select pile_data_wrapper(params ... ) from dual;
And I got still ORA-14552.
I got a dilemma, a function cause an error and a procedure can not return.
I need better solution for it.
The goal I wanted is below
A remote DB will call my function, I need to return response code.
In this function I have to insert and update a table.
Thanks
Yes and yes :)
in order to test a function you can 1. save it as a database object by placing it in a package. or you can run it using the execute or exec keyword within your workbench (i usually do this in SQL developer)
(PWLSQL)you can always return a value in an exception block by using the return keyword.
(JAVA)you could write your own class that inherits from the exception class and write your own method that returns a certain value.
I hope this helps.
I can not say I solve this problem, but I found something. ORA-14552 error does not happen when I run this in sqlplus. That error only happens in sql developer.
follow is how I ran this in SQLPlus(terminal)
SQL> var tmp varchar2(2); -- define a variable
SQL> execute :tmp := PILE_DATA#DB_LINK('a', 1, 2); -- set value to variable
SQL> print tmp; -- print the result for checking
================================================
Other option is making a wrapper function which handle Exception.
i.e. Make a function without Exception handling like below.
FUNCTION CREATEWITH(v_id IN NUMBER,v_value1 IN VARCHAR2,v_value2 IN NUMBER)
return Number
Is
BEGIN
insert into DEVICE_BALJU(id, value1, value2)
values (v_id, v_value1, v_value2);
return SQL%ROWCOUNT ;
END CREATEWITH;
And build a wrapper class which handles Exception
i.e.
create or replace FUNCTION "FN_WRAPPER" (v_id IN VARCHAR2,v_value1 IN NUMBER,v_value2 IN NUMBER)
RETURN Varchar2
is
rs VARCHAR2(2);
tmp NUMBER;
begin
-- DBMS_OUTPUT.PUT_LINE('Hello!!!');
rs := '00';
tmp := createWith(v_id, v_value1, v_value2);
RETURN rs;
EXCEPTION
WHEN OTHERS THEN
rs := '99';
RETURN rs;
END;
This is how I avoid this error on final.
I want to copy all the packages's source in my local machine using jdbc.
select DBMS_METADATA.GET_DDL('PACKAGE_BODY','COLLECTSTATS','MYSCHEMA') from DUAL;
I have tried this but not getting it
You can create a custom table with ddl for each objects (packages, procedures, functions) like the following:
drop table myobjects;
create table myobjects (obj_name varchar2(128), sub_obj_name varchar2(128), obj_type varchar2(128), obj_ddl clob);
set serveroutput on
declare
uobj_ddl clob;
cnt number := 1;
begin
for dt in (select object_name, subobject_name, object_type from user_objects where object_type IN ('FUNCTION','PROCEDURE','PACKAGE')) loop
--dbms_output.put_line(dt.object_name);
uobj_ddl := dbms_metadata.get_ddl(upper(dt.object_type), upper(dt.object_name));
insert into myobjects values (dt.object_name, dt.subobject_name, dt.object_type, uobj_ddl);
if mod(cnt,100) = 0 then
commit;
end if;
cnt := cnt + 1;
end loop;
commit;
end;
/
Then you can do the select on this from java using jdbc:
select * from myobjects;
Sample java code:
try {
pStat = con.prepareStatement("select * from myobjects";
ResultSet rSet = pStat.executeQuery();
while(rSet.next()){
objName = rSet.getString(1);
subObjName = rSet.getString(2);
objType = rSet.getString(3);
objDDL = rSet.getClob(4);
}
rSet = null;
} catch (SQLException e) {
e.printStackTrace();
}
pStat = null;
The issue is probably with output size. The code of a procedure or package is bigger that 32k which is the varchar2 limitation in PL/SQL.
Even the sqlplus buffer may fail with huge packages.
One solution can be using the following query in a cursor:
select text from user_source where name = 'COLLECTSTATS' order by line asc;
This will work for packages, functions, procedures and triggers. Views are managed differently.
I have a problem with the JDBC executeUpdate() method. It always returns 1 whether it updates a row or not. As far as I understand the method it should return 0 is no rows are altered.
Here is a sample of my code:
try {
conn = pool.getConnection();
PreparedStatement ps = conn.prepareStatement("{CALL UPDATE_USER (?,?,?)}");
ps.setString(1, field.toString());
ps.setString(2, change);
ps.setString(3, userID);
int updated = ps.executeUpdate();
System.out.println(updated);
if(updated==0){
throw new NoUserException();
}
ps.close();
} catch (SQLException e) {
log.error("An error occurred while creating the connection");
e.printStackTrace();
} finally {
pool.returnConnection(conn);
}
Could this be because I'm using a prepared statement or a stored procedure?
Here is the stored procedure:
create or replace
PROCEDURE UPDATE_USER
(
updateColumn IN user_tab_columns.column_name%type,
changeStr IN VARCHAR2,
unID IN VARCHAR2
)
IS
BEGIN
EXECUTE IMMEDIATE
'UPDATE
users
SET ' || updateColumn || '= :1
WHERE
uniqueID = :2'
USING changeStr, unID;
END;
It can't get info from stored proc execution. If you want to get row count (or anything else) switch PROCEDURE to FUNCTION, add return clause in this function, and change your call to something like ? = CALL ... Test with sql%rowcount inside function to get impacted row count.
You can use Callable statement a procedure or function. And return the no of rows affected as a return parameter