I have an SQL Server stored procedure similar to this:
CREATE PROCEDURE proc1
(
#param DECIMAL
)
AS
BEGIN
INSERT INTO table1 (#param);
-- further commands
END;
GO
This procedure is called from Java. I introduced a unique constraint on table1, on the same column which is inserted above. Is expected to get an SQLException in Java in case of a constraint violation, but it is not happening. When the procedure is executed manually from SSMS, I can see that it prints the constraint violation error, and then continues along the rest of the process, which I think is weird, I expected it to fail. So I changed it like this:
CREATE PROCEDURE proc1
(
#param DECIMAL
)
AS
BEGIN
BEGIN TRY
INSERT INTO table1 (#param);
END TRY
BEGIN CATCH
THROW 51000, 'Unable to insert', 1;
END CATCH
-- further commands
END;
GO
Now when I execute it manually in SSMS, the procedure stops in case of a failure, and prints my error message. However the Java calling process is not receiving any indication of the error. How can I propagate this error to the Java calling layer?
UPDATE: Java calling layer:
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
String connectionUrl = "jdbc:sqlserver://x.x.x.x;database=x";
try (Connection conn = DriverManager.getConnection(connectionUrl, "x", "x")) {
try (CallableStatement stmt = conn.prepareCall("{call proc1(?)}")) {
stmt.setInt(1, 1);
stmt.execute();
System.out.println("Done");
}
}
At the end, I can see the "Done" message printed to the console.
Add SET NOCOUNT ON as the first statement in the proc to suppress the DONE_IN_PROC (rowcount) TDS messages. Otherwise, the code will need to consume all results returned using a ResultSet and getMoreResults before the error is raised on the client.
Related
I have a strange problem. I'm executing insert using prepared statement like this:
try (Connection connection = connectionPool.getConnection();
PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { //TODO: caching of PS
int i = 1;
ParameterMetaData pmd = ps.getParameterMetaData();
...
} catch (SQLException e) {
throw new TGFIOException("Error executing SQL command " + sql, e);
}
Insert statement is like this:
insert into dbo.CurrencyRates(RateDate, CurrencyID, Rate) values ( ?, ?, ? )
Unfortunately it fails with following exception:
com.microsoft.sqlserver.jdbc.SQLServerException: com.microsoft.sqlserver.jdbc.SQLServerException: Incorrect syntax near the keyword 'WHERE'.
at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDriverError(SQLServerException.java:190)
at com.microsoft.sqlserver.jdbc.SQLServerParameterMetaData.<init>(SQLServerParameterMetaData.java:426)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.getParameterMetaData(SQLServerPreparedStatement.java:1532)
at com.jolbox.bonecp.PreparedStatementHandle.getParameterMetaData(PreparedStatementHandle.java:246)
There is no WHERE in the statement, so I am puzzled why it fails on metadata extraction...
EDIT:
SQL Server = 10.50.2500.0 Express Edition,
Driver = sqljdbc4.jar from 4.0 package
Also, I am using getParameterMetaData because I need to set some params to null and the preferred method is to use setNull() where you need SQLType.
EDIT2:
I've tested with Driver sqljdbc41 from newest 6.0 package - results are the same
EDIT3:
I've removed call to getParameterMetaData() and it worked, unfortunately it is a generic part that should max portable, yet it does not work with this single table (inserts to other tables on the same database works fine !!!)
EDIT4:
I've tried with different insert statements for this table and all of them works fine if I skip ps.getParameterMetaData() and fail when I call it. If I try with 2 or more params I get usual near WHERE error. If I try one column insert I get an error stating that the column name is incorrect, even if it is correct and without the meta data call it works perfectly fine. I will try to trace what driver tries to do underneath...
After some tracing on what actually the driver does (many thanks a_horse_with_no_name), I've come to some funny conclusion.
The solution for my question is to:
Replace following insert statement
INSERT INTO CurrencyRates(RateDate, CurrencyID, Rate) VALUES ( ?, ?, ? )
With this statement
INSERT INTO CurrencyRates (RateDate, CurrencyID, Rate) VALUES ( ?, ?, ? )
Logic behind that is that SQL driver does some metadata extraction in the background, and it creates a query with following fragment: ... FROM CurrencyRates(RateDate WHERE ... if you do not put space after table name, yet for the ordinary call this is perfectly possible!
EDIT:
This is obviously an inconsistency as (putting aside what actually is a valid insert) it should consistently accept or reject this query no matter if I call for meta data or not.
I am inserting data into a teradata table using executeBatch method. Currently if one insert in the batch fails all the other inserts in the batch also fails and no records end up being inserted. How can I change this behaviour to let the other inserts in the batch succeed if any inserts fails and the some ability to track the rejected records.
PS: I have ensured that TMODE is set to TERA and autocommit enabled.
UPDATE:
target table definition.
CREATE SET TABLE mydb.mytable ,NO FALLBACK ,
NO BEFORE JOURNAL,
NO AFTER JOURNAL,
CHECKSUM = DEFAULT,
DEFAULT MERGEBLOCKRATIO
(
col1 INTEGER,
col2 VARCHAR(10) CHARACTER SET LATIN NOT CASESPECIFIC NOT NULL)
PRIMARY INDEX ( col1 );
Below is the sample scala code. As you can see, this batch contains 5 insert statements. The First insert is set to fail because it is trying to insert null into an not null field (col2). The other 4 inserts dont have any issues and should succeed. But as you can see from below all 5 inserts in the batch failed. Is there any way we can make other inserts succeed?. As stated above tmode is tera and autocommit is enabled. if there is no way other than re-submitting all failed queries individually then we would have to reduce the batch size and settle for lower throughput.
Class.forName("com.teradata.jdbc.TeraDriver");
val conn = DriverManager.getConnection("jdbc:teradata://teradata-server/mydb,tmode=TERA","username","password")
val insertSQL = "INSERT INTO mydb.mytable VALUES (?,?)"
val stmt = conn.prepareStatement(insertSQL)
stmt.setInt(1,1)
stmt.setNull(2,Types.VARCHAR) // Inserting Null here. This insert will fail
stmt.addBatch()
stmt.setInt(1,2)
stmt.setString(2,"XXX")
stmt.addBatch()
stmt.setInt(1,3)
stmt.setString(2,"YYY")
stmt.addBatch()
stmt.setInt(1,4)
stmt.setString(2,"ZZZ")
stmt.addBatch()
stmt.setInt(1,5)
stmt.setString(2,"ABC")
stmt.addBatch()
try {
val res = stmt.executeBatch()
println(res.mkString(","))
}
catch {
case th: BatchUpdateException => {
println(th.getUpdateCounts().mkString(","))
}
}
Result
-3,-3,-3,-3,-3
This is from Teradata's JDBC manual:
Beginning with Teradata Database 13.10 and Teradata JDBC Driver
13.00.00.16, PreparedStatement batch execution can return individual success and error conditions for each parameter set.
An application using the PreparedStatement executeBatch method must
have a catch-block for BatchUpdateException and the application must
examine the error code returned by the BatchUpdateException
getErrorCode method.
PreparedStatement BatchUpdateException Handling
Execute a multi-statement request using a PreparedStatement batch request and demonstrates the handling of the PreparedStatement BatchUpdateException
I have some old C code I'm moving to Java and I have to port along existing SQL functions into JDBC. The commands are written like this (two of many examples):
RESULT:=1 + ?
IF ? > 0 THEN RESULT:=0 ELSE RESULT:= 1; END IF;
Those are two examples (separate commands).
Changing the syntax of the commands isn't an option, but some clever runtime replacement is valid. Note the ? are values that will get populated at runtime from other data.
I've tried this as statements, prepared statements, and callable statements, and I can't seem to get the syntax correct for getting "RESULT" back (actually, can't get it to execute() on any statement.
For test purposes I've been trying a simple command of either:
RESULT:=1+2
RESULT:=1+?
Just to see if I can get the type of statement working. But no luck.
The most common answer, from code that otherwise looks reasonable, is this:
String queryString = "declare result integer; begin RESULT:= 1 + 3; ? := RESULT; end";
try (CallableStatement cst = conn.prepareCall(queryString))
{
cst.registerOutParameter(1, Types.INTEGER);
cst.execute();
}
When I run that, on the execute (or executeUpdate, doesn't make a difference) I get back an Oracle 17002/08006 (Size Data Unit mismatch, network issue / invalid connection). But I have to believe that error is somewhat of a red herring, because the connection is definitely valid, and a trivially statement works fine.
Does anyone know the correct JDBC approach to calling that and getting a valid result (out of RESULT)?
I'm using Java 7 and Oracle 11g, if it matters.
Here's an example of calling a stored procedure that accepts an Integer argument and returns a single value (my Java method returns an Object and you can cast it to whatever you like).
public Object getResultFromStoredProcedure(String query,Integer param) {
Object obj=null;
ResultSet rs=null;
PreparedStatement stmt=null;
try{
stmt=conn.prepareStatement(query);
stmt.setInt(1,param);
rs = stmt.executeQuery();
if(rs.next()){
obj=rs.getObject(1);
}
}catch(SQLException e){
//catch exception and process it or log it
}finally{
rs.close();
stmt.close()
}
return obj;
}
Provided that you called your procedure OrclProc, you'd call it thus
String query = "select OrclProc(?) from dual";
Object result = getResultFromStoredProcedure(query, 5);
I have java code that connects to a remote oracle 11g EE db server. If i run a particular query in sqlplus it returns one result
SQL> SELECT COURSENAME from COURSES where skillID=1;
COURSENAME
--------------------
basic
But if I run the same query from the java code below it returns no results. I can copy the query syntax out of the query variable in the java debugger and running it on oracle so I know there is no syntax issue with the query. Also, it is not SQL exceptions or class not found exceptions so it seems to be running the query successfully -- just returning zero results.
What might be going on?
private String getCourseForSkill(int skillID){
try{
Class.forName("oracle.jdbc.OracleDriver");
String query="SELECT COURSENAME from COURSES where skillID=" + skillID ;
con = DriverManager.getConnection(url, user, password);
Statement stmt = con.createStatement();
rs = stmt.executeQuery(query);
rs.next();
return rs.getString("COURSENAME");
}
catch (ClassNotFoundException ex){
System.out.println(ex.getMessage());
}
catch (SQLException ex) {
System.out.println(ex.getMessage());
}
return null;
}
I think you're connecting to different Oracle instances, or more likely, as different Oracle users in the two cases
#GreyBeardedGeek the URL looks like "jdbc:oracle:thin:#website:port:orcl I get to the manual query by doing ssh#website, authenticating and then running command=sqlplus
Safer to run sqlplus <username>/<password>#<orainstancename>, because you can explicitly specify the oracle instance ID. In your case, it seems your program is using jdbc connection jdbc:oracle:thin:#website:port:orcl, so your orainstancename would be 'orcl' - just ensure that your tnsnames.ora file has the instance 'orcl' with the same 'port' as used by the jdbc connection
How to debug a little more
Run the following code:
con = DriverManager.getConnection(url, user, password);
con.setAutoCommit(false);
String insert="INSERT INTO COURSES (SKILLID, COURSE)"+ // can add other columns
"values (?, ?) );" // add ? for other columns
PreparedStatement ps = con.createPreparedStatement();
ps.setInt(1, 999);
ps.setString(2, "Complete Bullwarks");
// can set other columns
ps.executeUpdate(insert);
con.commit();
NOW connect manually, re-run your original select statement & see if the added row is there. If no error in java and no new row in Oracle: extremely likely you're using 2 different Oracle instances/schemas.
ALSO rerun your original java select code, but with SkillID=999 - extremely likely it will work.
Cheers
I had to do a commit to add the rows. When I typed commit; into the sql plus terminal then the remote jdbc connection could 'see' the rows. I am used to SQL server where you don't have to explicitly do these kinds of commits when using linq-to-sql or sql management studio.
It can be three issues.
1) skillID <> 1 in your Java code. Add debug and check.
2a) You are connecting to another database.
2b) You are connecting to the same database but SELECTING from a table in another schema.
To check 2a and 2b:
select user from dual; -- connect username
select name from v$database; -- database name
select host_name from v$instance; -- host name database is running on
This query returns all three into one result.
select user || '' || d.name || '' || i.host_name
from v$database d, v$instance i;
Assuming you are actually connecting to the same database this is caused by not committing the INSERT in the sql*plus connection.
Oracle by default does not run in auto-commit mode when connecting via OCI (which sql*plus uses to connect). Any DML(INSERT ...) executed in sql*plus will not be visible to any other session until it is committed. This is because Oracle provides a read committed isolation level by default. The only thing visible to other users across sessions are write locks.
It doesn't matter if you connect the second connection via JDBC or OCI, it won't see the changes till you commit the first connection.
To test this out try opening 2 sql*plus connections and run the following:
-- Executing DDL in Oracle causes an implicit commit before and after the
-- command so the second connection will see the existence of this table:
CREATE TABLE foobar ( x VARCHAR(1) );
Execute this in connection #1 - you should get zero (we haven't inserted anything yet):
SELECT COUNT(*) FROM foobar;
Execute this in connection #2:
INSERT INTO foobar ( x ) VALUES ( 'A' );
Execute this in connection #1 - you should still get zero (INSERT is not committed so connection #1 cannot see it):
SELECT COUNT(*) FROM foobar;
Execute this in connection #2:
COMMIT;
Execute this in connection #1 - you should get 1 (it's committed now):
SELECT COUNT(*) FROM foobar;
I'm trying to execute this script through JDBC (it's SQL Server):
DECLARE #var VARCHAR(10)
SET #var = "test"
INSERT INTO foo (name) VALUES (#var)
It's just an example, in my business case I have a big collection of SQL statements in one script.
I'm trying this:
final Statement stmt = connection.createStatement();
for (String sql : lines) {
stmt.executeUpdate(sql);
}
stmt.close();
SQL Server is saying on the second line:
java.sql.SQLException: Must declare the scalar variable "#var".
Looks like I'm doing something wrong...
You're executing it one line at a time, so of course the second line fails as it depends on the first line. Why not execute it all at once?...