10

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.

Ruepen
  • 411
  • 5
  • 8
  • 19
  • have you seen this? http://stackoverflow.com/questions/201887/primary-key-from-inserted-row-jdbc – Nathan Hughes Dec 29 '09 at 20:02
  • @Nathan Yes I have seen that, I just updated my question with an example, which is similar to the link you just posted. – Ruepen Dec 29 '09 at 20:16
  • 1
    This question is **not a duplicate** of the linked question because this question is for Oracle and the other question is for PostgreSQL. The top answer for the other question **cannot be used** as an answer for this question because of feature differences between Oracle and PostgreSQL. – Russell Silva Aug 07 '13 at 00:42
  • The only column returned in the `getGeneratedKeys()` is a `ROWID` - oracle 11. I think that's the "strange" value you mentioned. – AlikElzin-kilaka Apr 11 '16 at 08:33

3 Answers3

10

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
  • 7,218
  • 30
  • 57
  • 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 Dec 29 '09 at 22:08
  • 1
    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 Dec 29 '09 at 22:46
  • @Pascal that trigger is absolutely portable. – David Dec 30 '09 at 01:21
  • @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 Dec 30 '09 at 10:25
  • 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 Dec 30 '09 at 13:30
5

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
  • 562,542
  • 136
  • 1,062
  • 1,124
  • No sucess, I'm getting syntax errors. I'll see what I'm doing wrong and give an update. – Ruepen Dec 29 '09 at 22:02
  • 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 Dec 30 '09 at 01:25
  • 1
    @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 Dec 30 '09 at 10:22
  • this is awesome, i comment this only to remember this hack :) – Enrique San Martín Apr 01 '16 at 18:13
2

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.

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • 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 Dec 29 '09 at 20:32
  • Try `java.sql.RowId` or `oracle.sql.ROWID`. – BalusC Dec 29 '09 at 20:40
  • 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 Dec 29 '09 at 20:47
  • Maybe it's just the ID in hexadecimal format. Try converting it to decimal. – BalusC Dec 29 '09 at 20:49
  • 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 Dec 29 '09 at 21:15
  • 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 Dec 29 '09 at 21:29
  • @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 Dec 29 '09 at 21:32
  • 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 Dec 29 '09 at 22:49
  • After 4 hours of searching the internet, I find BalusC's answer and it works the best for me. As long I use the same connection, I think I can confidently say that I will always get the correct value even if another INSERT happens simultaneously. – jeff Apr 19 '11 at 20:29