Postgres UPSERT and id SERIAL issue - java

CREATE TABLE:
CREATE TABLE tests (id SERIAL PRIMARY KEY, "name" VARCHAR);
UPSERT:
INSERT INTO tests (id, "name")
VALUES (?, ?)
ON CONFLICT (id) DO UPDATE
SET "name" = EXCLUDED."name";
The problem is that it never executes the UPDATE part because SERIAL type resolves all conflicts - it just inserts existing records as new records with new ids (nextval) instead of updating them.
I am trying to implement a "Save All Records" function. It should update existing records and insert new ones if they do not exist.
The algorithm I imagined:
connection.setAutoCommit(false);
for (Test test : tests)
{
statement = connection.prepareStatement("INSERT INTO tests (id, \"name\") VALUES (?, ?) ON CONFLICT (id) DO UPDATE SET \"name\" = EXCLUDED.\"name\";");
statement.setInt(1, test.getId());
statement.setString(2, test.getName());
statement.execute();
}
connection.commit();

Related

Java prepared statement get generated keys do not work in DB2 but work in mySQL?

I have a very simple table that is in both MySQL and DB2 called STUDENT. The columns are: ID (primary key, auto incrementing), FIRST_NAME, LAST_NAME, AGE.
The table is replicated in both databases, so they should be the same syntactically. However, I have spent the entire day trying to figure out why, when I write a simple Java program to insert into the database, the MySQL version returns back the generated key via PreparedStatement.getGeneratedKeys(), while the DB2 version does not return anything.
My code looks like:
String sql = "INSERT INTO STUDENT (FIRST_NAME, LAST_NAME, AGE) VALUES ('Jacob', 'Eldy', 19)"
final Connection connection = getConnection(dataSource.get());
int[] insertedRows = null;
ResultSet rs = null;
PreparedStatement ps = null;
try {
ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
ps.addBatch();
insertedRows = ps.executeBatch();
rs = ps.getGeneratedKeys();
while(rs.next()) {
LOGGER.info(rs.getString(1));
}
connection.commit();
} catch (Exception e) {
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
} finally {
close(ps, connection);
}
After committing the connection for both databases DB2 and MySQL, both actually show the row being inserted, and the more I insert, a new row appears with an auto incremented ID, however only the MySQL database has a value in while(rs.next()), the DB2 version just skips over it since it is empty.
Am I doing something wrong? Is this just an incompatibility issue with DB2 and it just does not return the generated value? If so, what would be the best solution for tackling this issue?
UPDATE, adding the two DDLs for DB2 & mySQL:
mySQL DDL:
CREATE TABLE 'STUDENT'
...
`ID` int NOT NULL AUTO_INCREMENT
PRIMARY KEY('ID')
AUTO_INCREMENT=19073
DB2 DDL:
CREATE TABLE STUDENT
(
ID INTEGER DEFAULT IDENTITY GENERATED ALWAYS NOT NULL
PRIMARY KEY (ID)
)
CREATE TABLE STUDENT
(
ID INT NOT NULL GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
, FIRST_NAME VARCHAR (20)
, LAST_NAME VARCHAR (20)
, AGE SMALLINT
);
The following code based on the Making batch updates in JDBC applications links (this one is for Db2 for LUW) provided by others works as expected with the table definition above:
PreparedStatement ps = con.prepareStatement
(
"INSERT INTO STUDENT (FIRST_NAME, LAST_NAME, AGE) " +
"VALUES (?,?,?)"
, Statement.RETURN_GENERATED_KEYS
);
ps.setString (1, "Jacob");
ps.setString (2, "Eldy");
ps.setShort (3, (short) 19);
ps.addBatch();
ps.setString (1, "Jacob");
ps.setString (2, "Eldy");
ps.setShort (3, (short) 19);
ps.addBatch();
int [] numUpdates = ps.executeBatch();
for (int i=0; i < numUpdates.length; i++)
if (numUpdates[i] == Statement.SUCCESS_NO_INFO)
System.out.println("Execution " + i + ": unknown number of rows updated");
else
System.out.println("Execution " + i + " successful: " + numUpdates[i] + " rows updated");
ResultSet[] resultList = ((com.ibm.db2.jcc.DB2PreparedStatement) ps).getDBGeneratedKeys();
if (resultList.length != 0)
for (int i = 0; i < resultList.length; i++)
{
while (resultList[i].next())
System.out.println("Automatically generated key value = " + resultList[i].getBigDecimal(1));
resultList[i].close();
}
else
System.out.println("Error retrieving automatically generated keys");
Am I doing something wrong? Is this just an incompatibility issue with DB2 and it just does not return the generated value? If so, what would be the best solution for tackling this issue?
Yes, you are doing wrong. It's not incompatibility issue, and it's not an issue. DB2 is different than MySQL. You can't handle both because you have incompatible DDL. Since no records are inserted into DB2 the value of the key is not available.
The solution to the issue is to create a trigger on insert a record to make sure the primary key is inserted into DB. If you are missing a key then select it from the sequence and substitute the value.
Now if identity is generated into DB2 like this
CREATE TABLE STUDENT
(
ID INTEGER DEFAULT IDENTITY GENERATED ALWAYS NOT NULL
PRIMARY KEY (ID)
)
So it will always return getGeneratedKeys().

How to check before insert?

I have a table with 6 columns.
1.ID (number)(PK)
2.USER_ID (number)
3.ROLE_ID (number)
4.CREATED_TS (date)
5.CREATED_BY (VARCHAR2)
6.ACTIVE_IND (VARCHAR2)
I am inserting data via a POST REST API Call in that table. I am firing a query in Java Repository.
public UserRoles save(final UserRoles u) {
final String insertQuery = "INSERT INTO VEB_USER_ROLES(ID, USER_ID, ROLE_ID, CREATED_TS, CREATED_BY, ACTIVE_IND) " +
"VALUES(VEB_USER_ROLES_SEQ.NEXTVAL, ?, ?, ?, 'PROD_MASTER', 'Y')";
GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(new PreparedStatementCreator() {
#Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
PreparedStatement ps = con.prepareStatement(insertQuery, new String[]{"ID"});
ps.setLong(1, u.getUserId());
ps.setLong(2, u.getRoleId());
ps.setDate(3, new java.sql.Date(System.currentTimeMillis()));
return ps;
}
}, keyHolder);
return u;
}
The query is working fine and the data is been inserted.I just want to add a check if same UserID and RoleID exists it should not insert.
for eg in the DB I have Userid '1' and roleId '1' and I send the same values ie userid '1' and role id '1' it should not get inserted.
Can anyone guide me regarding this.
Create a UNIQUE constraint on the two columns: USER_ID and ROLE_ID
ALTER TABLE yourtablename ADD CONSTRAINT uq_yourtablename UNIQUE(column1, column2);
Examples: Status on Insertion-
-- succeeds:
INSERT yourtablename(USER_ID, ROLE_ID) VALUES(1, 22);
-- succeeds:
INSERT yourtablename(USER_ID, ROLE_ID) VALUES(2, 23);
-- fails:
INSERT yourtablename(USER_ID, ROLE_ID) VALUES(1, 23);
If its not possible to ALTER the DB structure, you can check this also by your code:
- Before inserting new record check USER_ID & ROLE_ID exists in DB.
Use a select query & check whether a similar record exists.
Its just a idea, please make it working for you, if required:
$exists = SELECT COUNT(*) no_of_similar_records FROM yourTableName WHERE USER_ID = $USER_ID AND ROLE_ID = $ROLE_ID
If it has a value the do not INSERT again.
So here we do not need a DB update on structure.
This may help you..
Couple of ways to handle this
Add unique constraint on the table on columns Userid, Roleid or,
Do a SELECT before trying to insert to check if they already exists or,
Use merge into insert when NOT MATCHED

Java PreparedStatement and ON DUPLICATE KEY UPDATE: how do I know whether row was inserted or updated?

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.
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
the attempted INSERT is aborted because it would result in a duplicate UNIQUE key value, and
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.
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.

How to avoid inserting null values into Primary key or Not Null columns?

I am inserting into a table from my jdbc program,
like this
PreparedStatement ps = con.prepareStatement(sqlqry);
ps.setInt(1,dto.getInstall_id());
ps.setString(2, dto.getDashboard_name());
ps.setString(3, dto.getDashboard_type());
ps.setString(4, dto.getDashboard_image());
But in the table i have column say D_ID which in is primary key and i dont want o insert the D_ID from my program into table because the same id might be already exist. So for avoiding the PK_CONSTRAINT I am not inseting it.
But when i try this i am getting this error.
ORA-01400: cannot insert NULL into ("TESTDB"."TESTATBLE"."D_ID")
So how can i solve this problem, Any alternative like if i insert D_ID from the program my JDBC program the D_ID column should dynamically generate id's in the table.
I am banging my head for this. Please help!
You should create that ID using a sequence. So for each ID column that you have, you create a corresponding sequence:
create table testatble
(
d_id integer not null primary key,
install_id integer not null,
dashboard_name varchar(100)
... more columns ....
);
create sequence seq_testatble_d_id;
You can use it like this:
// note that there is no placeholder for the D_ID column
// the value is taken directly from the sequence
String sqlqry =
"insert into testatble (d_id, install_id, dashboard_name) " +
"values (seq_testatble_d_id.nextval, ?, ?)";
PreparedStatement ps = con.prepareStatement(sqlqry);
ps.setInt(1,dto.getInstall_id());
ps.setString(2, dto.getDashboard_name());
... more parameters ...
ps.executeUpdate();
That way the id will be generated automatically.
If you need the generated ID in your Java code after the insert, you can use getGeneratedKeys() to return it:
// the second parameter tells the driver
// that you want the generated value for the column D_ID
PreparedStatement ps = con.prepareStatement(sqlqry, new String[]{"D_ID"});
// as before
ps.setInt(1,dto.getInstall_id());
ps.setString(2, dto.getDashboard_name());
... more parameters ...
ps.executeUpdate();
// now retrieve the generated ID
int d_id = -1;
ResultSet rs = ps.getGeneratedKeys();
if (rs.next()) // important!
{
d_id = rs.getInt(1);
}
rs.close();
More on sequences in the Oracle manual: http://docs.oracle.com/cd/E11882_01/server.112/e26088/pseudocolumns002.htm#SQLRF00253
You should use Auto Increment number for ID(I Oracle you can use sequence). You can do this at the link:
Create ID with auto increment on oracle
You should also read this. If there is a sequence to your ID then here you can read information about that.

How to get an automatically incremented column value when inserting a row into a table in mysql using java?

How can I insert into table1 like: INSERT INTO table1 (description) VALUES ('Some test description here'); and have it return the automatically incremented ID so that I can then insert a row into table2? The description is not guaranteed to be unique. I am using a Java PreparedStatement to insert the value currently with execute()
I have the following table structure:
CREATE TABLE table1 (
table1ID INTEGER NOT NULL AUTO_INCREMENT,
description VARCHAR(255) NOT NULL,
CONSTRAINT PK_table1 PRIMARY KEY (table1ID)
);
CREATE TABLE table2 (
table1ID INTEGER NOT NULL,
personID INTEGER NOT NULL,
CONSTRAINT PK_table2 PRIMARY KEY (table1ID, personID)
);
ALTER TABLE table2 ADD CONSTRAINT FK_table1_table2
FOREIGN KEY (table1ID) REFERENCES table1 (table1ID);
ALTER TABLE table2 ADD CONSTRAINT FK_table2_person
FOREIGN KEY (personID) REFERENCES person (personID);
run this query right after the insert completes succesfully: SELECT LAST_INSERT_ID()
Try this:
PreparedStatement st = con.prepareStatement(
"Insert into table1 (name) values ('test desc')",
Statement.RETURN_GENERATED_KEYS);
st.executeUpdate();
ResultSet rs = st.getGeneratedKeys();
if(rs.next())
{
int data = rs.getRow();
int id = rs.getInt(1);
}
Observe values of data & id.
You don't need to fire insert & select queries separately.

Categories