views:

745

answers:

4

Is there some way to get a value from the last inserted row?

I am inserting a row where the PK will automatically increase due to sequence created, and I would like to get this sequence number. Only the PK is guaranteed to be unique in the table.

I am using Java with a JDBC and Oracle.

I forgot to add that I would like to retrieve this value using the resultset below. (I have tried this with mysql and it worked successfully, but I had to switch over to Oracle and now I get a string representation of the ID and not the actually sequence number)

Statement stmt = conn.createStatement();
stmt.executeUpdate(insertCmd, Statement.RETURN_GENERATED_KEYS);
stmt.RETURN_GENERATED_KEYS;
ResultSet rs = stmt.getGeneratedKeys();
if(rs.next()){
   log.info("Successful insert");
   id = rs.getString(1);
}

The above snippet would return the column int value stored in a mysql table. But since I have switched over to Oracle, the value returned is now a strange string value.

A: 

Can you not do something like this?

SELECT value FROM table ORDER BY id DESC LIMIT 1

Tyler Smith
No. What happens if someone else inserts a row between the time you insert and the time you select? You get the wrong value.
Ken White
+1  A: 

You should use ResultSet#getLong() instead. If in vain, try ResultSet#getRowId() and eventually cast it to oracle.sql.ROWID. If the returned hex string is actually the ID in hexadecimal flavor, then you can try converting it to decimal by Long#valueOf() or Integer#valueOf().

Long id = Long.valueOf(hexId, 16);

That said, Oracle's JDBC driver didn't support ResultSet#getGeneratedKeys() for a long time and is still somewhat troublesome with it. If you can't get that right, then you need to execute a SELECT CURRVAL(sequencename) on the same statement as you did the insert, or a new statement inside the same transaction, if it was a PreparedStatement. Basic example:

public void create(User user) throws SQLException {
    Connection connection = null;
    PreparedStatement preparedStatement = null;
    Statement statement = null;
    ResultSet generatedKeys = null;

    try {
        connection = daoFactory.getConnection();
        preparedStatement = connection.prepareStatement(SQL_INSERT);
        preparedStatement.setValue(1, user.getName());
        // Set more values here.
        int affectedRows = preparedStatement.executeUpdate();
        if (affectedRows == 0) {
            throw new SQLException("Creating user failed, no rows affected.");
        }
        statement = connection.createStatement();
        generatedKeys = statement.executeQuery(SQL_CURRVAL);
        if (generatedKeys.next()) {
            user.setId(generatedKeys.getLong(1));
        } else {
            throw new SQLException("Creating user failed, no generated key obtained.");
        }
    } finally {
        close(generatedKeys);
        close(statement);
        close(preparedStatement);
        close(connection);
    }
}

Oh, from your code example, the following line

stmt.RETURN_GENERATED_KEYS;

is entirely superfluous. Remove it.

You can find here another example which I posted before about getting the generated keys, it uses the normal getGeneratedKeys() approach.

BalusC
I tried the ResultSet#getLong(), it didn't work. Error returned was "Invalid column type: getLong not implemented for class oracle.jdbc.driver.T4CRowidAccessor" Am going to try your second suggestion.
Ruepen
Try `java.sql.RowId` or `oracle.sql.ROWID`.
BalusC
The following: oracle.sql.ROWID rid = rs.getObject(1);ID = rid.stringValue();ID contains a hexadecimal value which is exactly I was getting before with the rs.getString(1);Will try your last suggestion.
Ruepen
Maybe it's just the ID in hexadecimal format. Try converting it to decimal.
BalusC
Your suggestion should have worked, but perhaps it's because the value I'm getting back from oracle.sql.ROWID is not a hexadecimal value?I get this error: java.lang.NumberFormatException: For input string: "AAAS+XAAGAAAAEvAAP"~~~~ The value in quotes is the value that I get back from ROWID.stringValue();
Ruepen
That's indeed not hexadecimal. How did you come to this conclusion? Well, I don't do Oracle extensively so I can't help you further with this. Your last resort will be a `SELECT CURRVAL` or to wait for someone else with more Oracle experience.
BalusC
@BalusC Can you suggest how I can use the SELECT CURRVAL in the insert stmt? I tried the following, but I keep getting errors.stmt.executeUpdate(insertCmd + "SELECT CURRVAL(CONTACT_SEQ)", Statement.RETURN_GENERATED_KEYS);ORA-00933: SQL command not properly ended
Ruepen
Execute it on the same (or a new) statement **after** the insert. Thus, first `executeUpdate()` the `insert` and then `executeQuery()` the `select currval`. The ID should be available by `ResultSet#getLong()`.
BalusC
A: 

This is not consistent with other databases but, when using Oracle, getGeneratedKeys() returns the ROWID for the inserted row when using Statement.RETURN_GENERATEDKEYS. So you need to use the oracle.sql.ROWID proprietary type to "read" it:

Statement stmt = connection.createStatement();
stmt.executeUpdate(insertCmd, Statement.RETURN_GENERATED_KEYS);
ResultSet rs = stmt.getGeneratedKeys();
oracle.sql.ROWID rid = (oracle.sql.ROWID) rs.getObject(1); 

But this won't give you the generated ID of the PK. When working with Oracle, you should either use the method executeUpdate(String sql, int[] columnIndexes) or executeUpdate(String sql, String[] columnNames) instead of executeUpdate(String sql, int autoGeneratedKeys) to get the generated sequence value. Something like this (adapt the value to match the index or the name of your primary key column):

stmt.executeUpdate(INSERT_SQL, new int[] {1});
ResultSet rs = stmt.getGeneratedKeys();

Or

stmt.executeUpdate(INSERT_SQL, new String[] {"ID"});
ResultSet rs = stmt.getGeneratedKeys();

While digging a bit more on this, it appears that this approach is shown in the Spring documentation (as mentioned here) so, well, I guess it can't be totally wrong. But, unfortunately, it is not really portable and it may not work on other platforms.

Pascal Thivent
No sucess, I'm getting syntax errors. I'll see what I'm doing wrong and give an update.
Ruepen
What I don't get Pascal is you are against the RETURNING clause but have proposed a solution that requires two database calls. The benefit of Adam's solution is it's a single database call.
David
@David I don't really like triggers, mostly because they do some "magic" stuff behind the scene, stealthy, and because I don't think that spreading logic over Java and the database is a good idea. So I don't use them if I can avoid them. Regarding the *"two database calls"*, can you elaborate on this, I'm not sure I'm following you.
Pascal Thivent
+4  A: 

What you're trying to do is take advantage of the RETURNING clause. Let's setup an example table and sequence:

CREATE TABLE "TEST" 
( "ID" NUMBER NOT NULL ENABLE, 
 "NAME" VARCHAR2(100 CHAR) NOT NULL ENABLE, 
  CONSTRAINT "PK_TEST" PRIMARY KEY ("ID")
  );

CREATE SEQUENCE SEQ_TEST;

Now, your Java code should look like this:

String insertSql = "BEGIN INSERT INTO TEST (ID, NAME) VALUES (SEQ_TEST.NEXTVAL(), ?) RETURNING ID INTO ?; END;";
java.sql.CallableStatement stmt = conn.prepareCall(insertSql);
stmt.setString(1, "John Smith");
stmt.registerOutParameter(2, java.sql.Types.VARCHAR);
stmt.execute();
int id = stmt.getInt(2);
Adam Hawkes
I really think that using a trigger for this is an horrible solution: it makes the code even less portable and adds extra complexity for nothing.
Pascal Thivent
The original question states "I am inserting a row where the PK will automatically increase due to sequence created...." How else would you do this in Oracle if not using a trigger to populate the primary key?
Adam Hawkes
@Pascal that trigger is absolutely portable.
David
@Adam Using a trigger absolutely **not** necessary, you can include `seq.nextval` in the INSERT statement. No, really, I find this approach unnecessary cumbersome and I don't get why people do like it.
Pascal Thivent
I didn't say that "I" do it, but I made an assumption based upon the question. Yeah, I guess that does make my answer a bit more complicated than necessary. I may update the SQL, but the crux of my answer was to use the `RETURNING` clause to get the value of the PK.
Adam Hawkes