8

Having following code, how do I know if the execute() method resulted in insert or in update?:

Connection c = DriverManager.getConnection(connectionString);

PreparedStatement st = c.prepareStatement("INSERT INTO `table`(`field1`) VALUES (?) ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id);");

st.setString(1,"some value");
st.execute();

Thanks in advance.

Drew MacInnis
  • 8,267
  • 1
  • 22
  • 18
ed22
  • 1,127
  • 2
  • 14
  • 30

2 Answers2

9

Consider the following MySQL test table:

CREATE TABLE `customers` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL,
  `email` varchar(100) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

with existing sample data as follows:

id  name            email
--  --------------  ----------------
 1  Loblaw, Bob     bob@example.com
 2  Thompson, Gord  gord@example.com

With the default connection setting compensateOnDuplicateKeyUpdateCounts=false (described here) the following Java code

PreparedStatement ps = dbConnection.prepareStatement(
        "INSERT INTO customers (name, email) " +
        "VALUES (?, ?) " +
        "ON DUPLICATE KEY UPDATE " +
            "name = VALUES(name), " +
            "id = LAST_INSERT_ID(id)");
ps.setString(1, "McMack, Mike");
ps.setString(2, "mike@example.com");
int euReturnValue = ps.executeUpdate();
System.out.printf("executeUpdate returned %d%n", euReturnValue);
Statement s = dbConnection.createStatement();
ResultSet rs = s.executeQuery("SELECT LAST_INSERT_ID() AS n");
rs.next();
int affectedId = rs.getInt(1);
if (euReturnValue == 1) {
    System.out.printf("    => A new row was inserted: id=%d%n", affectedId);
}
else {
    System.out.printf("    => An existing row was updated: id=%d%n", affectedId);
}

produces the following console output

executeUpdate returned 1
    => A new row was inserted: id=3

Now run the same code again with the parameter values

ps.setString(1, "Loblaw, Robert");
ps.setString(2, "bob@example.com");

and the console output is

executeUpdate returned 2
    => An existing row was updated: id=1

This demonstrates that .executeUpdate really can return 2 if the unique index causes an existing row to be updated. If you require further assistance with your actual test code then you should edit your question to include it.

Edit

Further testing reveals that .executeUpdate will return 1 if

  1. the attempted INSERT is aborted because it would result in a duplicate UNIQUE key value, and
  2. the specified ON DUPLICATE KEY UPDATE changes do not actually modify any values in the existing row.

This can be confirmed by running the above test code twice in a row with the exact same parameter values. Note that the UPDATE ... id = LAST_INSERT_ID(id) "trick" does ensure that the correct id value is returned.

That probably explains OP's test results if the only value being inserted was the UNIQUE key value.

Gord Thompson
  • 116,920
  • 32
  • 215
  • 418
  • Wow, thanks for extensive explanation! So, adding some assignment like 'name = VALUES(name)' to the query makes executeUpdate() return 2 on update? I will try to do that. Could you elaborate on why SELECT_LAST_INSERT_ID() does not ensure correct id value returned? Thanks again. – ed22 Mar 30 '14 at 21:57
  • @ed22 **(1)** re: "So, adding some assignment like 'name = VALUES(name)' to the query makes executeUpdate() return 2 on update?" - Not necessarily. In order for `executeUpdate` to consistently return 2 on update you need to ensure that the UPDATE actually *changes* something. One obvious candidate would be a `lastUpdated` column to hold the date/time of the most recent change (insert or update). **(2)** As I said, the `SELECT_LAST_INSERT_ID()` function *does* work even if `executeUpdate` returns 1 on update, so that is not really an issue. – Gord Thompson Mar 30 '14 at 22:27
  • @GordThompson are you sure this is correct? the documents say that executeUpdate will return the number of rows that were changed, thus returning 1 when a new row is inserted or updated with a change, and 0 if it was a duplicate and nothing was changed, or say 7 if 7 rows were inserted or 4 inserted and 3 modified for example. – Pochi Apr 17 '15 at 14:25
  • @Chiquis I am sure that my answer accurately described the results of the actual tests I performed. Which "documents" are you referring to: the JDBC documentation for `executeUpdate`, or the specific documentation for MySQL Connector/J? – Gord Thompson Apr 17 '15 at 14:34
  • @GordThompson im refering to this one http://docs.oracle.com/javase/7/docs/api/java/sql/PreparedStatement.html#executeUpdate() which says "either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 for SQL statements that return nothing" – Pochi Apr 17 '15 at 14:42
  • @GordThompson by the way i posted my own question about this here: http://stackoverflow.com/questions/29702595/what-does-the-java-mysql-executeupdate-returns-for-insert-on-on-duplicate-key because I have also seen the behavior you describe in other answers, but also seen the one i describe in yet, other answers and forums. So confusing imo... – Pochi Apr 17 '15 at 14:53
  • @Chiquis The Java docs are saying what the JDBC driver *should* do, but there are lots of cases where the actual behaviour of a JDBC driver may be somewhat different. In this case MySQL Connector/J returns slightly different results, at least for the default connection settings. There is a `compensateOnDuplicateKeyUpdateCounts` configuration property described [here](http://dev.mysql.com/doc/connector-j/en/connector-j-reference-configuration-properties.html) that sounds like it might produce results closer to what you expect if it is enabled (by default it isn't). – Gord Thompson Apr 17 '15 at 14:54
2

Use executeUpdate instead as it returns an int row count.

UPDATE 1: According to the MySQL INSERT ... ON DUPLICATE KEY UPDATE documentation:

With ON DUPLICATE KEY UPDATE, the affected-rows value per row is 1 if the row is inserted as a new row, and 2 if an existing row is updated.

UPDATE 2: INSERT IGNORE may also be an option:

INSERT IGNORE INTO `table`(`field1`) VALUES (?)

executeUpdate should return 1 when a new row is inserted and 0 when there is a duplicate.

Community
  • 1
  • 1
Drew MacInnis
  • 8,267
  • 1
  • 22
  • 18
  • Thanks for your answer, but it does not do it for me. It returns 1 no matter if an insert or update was performed. What I need to know is if the operation performed was insert or was it update (I need to know which one). – ed22 Mar 30 '14 at 12:04
  • 1
    I believe MySQL will use the return code to inform you if an insert (returns 1) or update (returns 2) occurred: http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html – Drew MacInnis Mar 30 '14 at 12:27
  • I'm not sure why the affected rows count being returned is the same regardless of whether insert or update occurred, I don't have a MySQL server at hand to test with at the moment. – Drew MacInnis Mar 30 '14 at 12:39
  • @ed22 - This answer is correct. For `INSERT ... ON DUPLICATE KEY` queries `.executeUpdate()` returns 1 if a new row is inserted and it returns 2 if an existing row is updated. The sample code in your question could be misleading because if the row already exists then there is really nothing else to change so `.executeUpdate()` may return an unexpected value. If you can create a sample query that better illustrates your issue then please edit your question to include it. – Gord Thompson Mar 30 '14 at 13:13
  • I have done some testing and executeUpdate() always returns 1 (no mater which part of my query was run). execute() always returns false with getUpdateCount() always returning 1. Maybe it works that way only for this particular query which updates last insert id no mater which operation took place (insert or update). I guess my only option is to run SELECT (to check if row exists) and then INSERT. These are two queries instead of one, but I would know if row would be updated or not. I just hoped it could be don in one query. – ed22 Mar 30 '14 at 15:57
  • I'm not sure what the return value behavior would be for `UPDATE id=LAST_INSERT_ID(id)` if on a duplicate the primary key is updated. Any luck with `INSERT IGNORE`? – Drew MacInnis Mar 30 '14 at 16:02
  • INSERT IGNORE does not update the last insert id when duplicate is found so it is not an option for me either... – ed22 Mar 30 '14 at 16:04
  • Ok, I wasn't aware that you also want the id to increase even in the case of duplicate. Sounds like you may need more than one statement or a stored procedure – Drew MacInnis Mar 30 '14 at 16:09
  • 1
    Actually, I believe that the `LAST_INSERT_ID()` "trick" is simply a way of ensuring that `LAST_INSERT_ID()` returns the `id` value of the record affected, even if it was an existing row (and no INSERT was actually performed). See the example in my answer for details. – Gord Thompson Mar 30 '14 at 17:37