i'm inserting multiple rows into a table using a single insert statement. The table has an auto increment field as the primary key.
Like so: INSERT INTO MyTable VALUES (?,?,?,?),(?,?,?,?),(?,?,?,?),(?,?,?,?)
StringJoiner sj = new StringJoiner(",");
for(int i=0;i<salesmen.size();i++)
sj.add("(?,?,?,?)");
sql.append("INSERT INTO MyTable ");
sql.append("VALUES ");
sql.append(sj.toString());
try(PreparedStatement statement = connection.prepareStatement(sql.toString(),Statement.RETURN_GENERATED_KEYS)){
int i = 1;
for(Salesman salesman : salesmen){
statement.setDate(i++, DateUtil.toSqlDate(date));
statement.setString(i++, salesman.getName());
statement.setInt(i++, salesman.getWeeklyTargetCustomerId());
statement.setInt(i++, salesman.getCycle());
}
statement.executeUpdate();
ResultSet generatedKeys = statement.getGeneratedKeys();
while(generatedKeys.next()) {
log.info("generated key: " + generatedKeys.getLong(1)); //only prints 1 id
}
}
catch(SQLException e){
log.info("(!!) SQL Exception in Execution: " + e.getMessage());
log.info("\n\n" + sql.toString() + "\n");
}
When i call getGeneratedKeys(), it returns a ResultSet with only the id of the last row inserted, and not all of the rows. If i insert 50 rows, how do i obtain a resultset with 50 generated keys?
Although it's an old question, I hope it helps someone.
PreparedStatement pre = connection.prepareStatement("SQL", PreparedStatement.RETURN_GENERATED_KEYS);
for(Salesman salesman: salesmen) {
pre.setString(i++, ...);
pre.setString(i++, ...);
....
pre.addBatch(); // Add this
}
pre.executeBatch(); // Add this
ResultSet rs = pre.getGeneratedKeys();
while(rs.next()) {
int id = rs.getInt(1); // This should contain the id of the inserts in order.
}
Related
I have used this method without using the join in the query and it was working as expected. But I added a inner join and now it can't update the "used" column
public HashMap<String, Comparable> getPhoneNumberAndMarkAsUsed() {
String[] colNames = { "phone_number.id", "phone_number.phone_number",
"phone_number.account_id", "phone_number.used AS used",
"(now() AT TIME ZONE account.timezone)::time AS local_time" };
String query = "select " + Stream.of(colNames).collect(Collectors.joining(", "))
+ " from account INNER JOIN phone_number ON account.id = phone_number.account_id where phone_number.used = false order by id DESC limit 1 for update";
HashMap<String, Comparable> account = new HashMap<String, Comparable>();
try (Connection conn = DriverManager.getConnection(url, props); // Make sure conn.setAutoCommit(false);
Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
ResultSet rs = stmt.executeQuery(query)) {
conn.setAutoCommit(false);
ResultSetMetaData rsmd = rs.getMetaData();
int columnsNumber = rsmd.getColumnCount();
while (rs.next()) {
for (int i = 1; i <= columnsNumber; i++) {
if (i > 1)
System.out.print(", ");
String columnValue = rs.getString(i);
System.out.print(columnValue + " " + rsmd.getColumnName(i));
}
// Get the current values, if you need them.
account.put("phone_number", rs.getString("phone_number"));
account.put("account_id", rs.getLong("account_id"));
rs.updateBoolean("used", true);
rs.updateRow();
}
conn.commit();
} catch (SQLException e) {
e.printStackTrace();
}
return account;
}
the loop prints the following
7223 id, 10001234567 phone_number, 1093629 account_id, f used, 23:32:42.502472 local_time
accourding to the output above, then I am use that column "used" is part of the ResultSet. But I get the following Exception
org.postgresql.util.PSQLException: ERROR: column "used" of relation "account" does not exist
This is the query when printed
select phone_number.id, phone_number.phone_number, phone_number.account_id, phone_number.used AS used, (now() AT TIME ZONE account.timezone)::time AS local_time from account INNER JOIN phone_number ON account.id = phone_number.account_id where phone_number.used = false order by id DESC limit 1 for update
used belongs to the phone_number table not the account table. How can this be resolved?
here is the problem in your code:
rs.updateBoolean("used", true);
this statement will try to update the data of table through resultset but to do that you cannot user join and also there is one problem.
As you are updating via resultset it will try to update account table and if we find used column is account table then error occurs.
so your code is trying to find column "used" in account table but it is not there.
try this one:
String query = "select " + Stream.of(colNames).collect(Collectors.joining(", "))
+ " from phone_number INNER JOIN account phone_number ON account.id = phone_number.account_id where phone_number.used = false order by id DESC limit 1 for update";
I want to add values in a table that has dynamic columns.
I managed to create a table with dynamic columns but I cannot figure out how to insert data.
//Create Table
sql = "CREATE TABLE MyDB.myTable" +
"(level INTEGER(255) )";
int columnNumber = 5; //Number of columns
//Add columns
for (i=0;i<columnNumber;i++){
String columnName = "Level_" +i:
String sql = "ALTER TABLE MyDB.myTable ADD " + columnName + " INTEGER(30)";
}
//Insert Data
//How to insert data dynamically, without knowing the number of columns?
You can also use database metadata to get the column names. This has the advantage that you even don't need to know the column names, rather they are retrieved dynamically in your code.
public static List<String> getColumns(String tableName, String schemaName) throws SQLException{
ResultSet rs=null;
ResultSetMetaData rsmd=null;
PreparedStatement stmt=null;
List<String> columnNames =null;
String qualifiedName = (schemaName!=null&&!schemaName.isEmpty())?(schemaName+"."+tableName):tableName;
try{
stmt=conn.prepareStatement("select * from "+qualifiedName+" where 0=1");
rs=stmt.executeQuery();//you'll get an empty ResultSet but you'll still get the metadata
rsmd=rs.getMetaData();
columnNames = new ArrayList<String>();
for(int i=1;i<=rsmd.getColumnCount();i++)
columnNames.add(rsmd.getColumnLabel(i));
}catch(SQLException e){
throw e;//or log it
}
finally{
if(rs!=null)
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
throw e
}
if(stmt!=null)
try {
stmt.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
throw e
}
}
return columnNames;
}
Once you have the column names, you can use it as you normally would (List.size() would of course give the number of columns).
UPDATE:
//I will assume that your values (data to be inserted) is a List of Object types and that it is already populated
List<Object> data = new ArrayList<>();
//you will populate this list
//getting the column names
List<String> columnNames = getColumns("MyTable", "MyDB");
String insertColumns = "";
String insertValues = "";
if(columnNames != null && columnNames.size() > 0){
insertColumns += columnNames.get(0);
insertValues += "?";
}
for(int i = 1; i < columnNames.size();i++){
insertColumns += ", " + columnNames.get(i) ;
insertValues += "?";
}
String insertSql = "INSERT INTO MyDB.MyTable (" + insertColumns + ") values(" + insertValues + ")";
try{
PrepareStatement ps = conn.prepareStatement(insertSql);
for(Object o : data){
ps.setObject(o); //you must pass objects of correct type
}
ps.execute(); //this inserts your data
}catch(SQLException sqle){
//do something with it
}
This code assume that you pass objects of correct types to PreparedStatement.setObject(Object o) method. It's also possible to retrieve column types using metadatabase information and then use that info to enforce type checking but that would make your code much more complicated
If you know the number of columns you want to insert, you can make your insert query same way you made your CREATE TABLE. Explicitly name the columns you want to add your data into, and make sure the columns you leave empty are allowed to be empty (NULL or default=0)
INSERT INTO MyDB.myTable (Level_1, Level_2, ...) VALUES (Val_1, Val_2, ...);
The alternative approach would be to store each inserted value in a separate row. In that way you don't vhave to change the number of columns in your table.
You need a table where you have a ID for every group of values:
- ID
- Level
- Value
You could have a separate table where you can register each ID:
- ID (bigInt, auto increment, primary key)
- info field
- timestamp
Now, for every set of data you want to insert, first insert need a Unique ID. If you use the second table, inserting a new row would give you a new ID:
INSERT INTO register_table (ID, info, timestamp) VALUES (NULL, 'some info', NOW());
This will give you a new ID (last_inserted_id).
With this ID now insert all values in the other table:
for(i=0i<columnNumber;i++){
"INSERT INTO _table (ID, Level, _Value) VALUES ("+ the_ID +", "+ i +", "+ the_VALUE +");";
}
If you want to fetch the data:
"SELECT Level, _Value FROM _table WHERE ID="+ the_ID +" ORDER BY Level;";
I'm having an issue where my GUI program is showing no error and also not updating the jTable with the UPDATE statement I'm giving it. INSERT and DELETE are working fine, but this isn't for some reason.
I'm using UCanAccess so I can use an MSAccess DB and rs2xml for resultset stuff.
The table I'm modifying here is a junction table with 2 primary keys for director_id and movie_id which are linked to separate tables so this is a many-to-many relationship.
private void jButtonEditActionPerformed(java.awt.event.ActionEvent evt) {
DefaultTableModel tableModel = (DefaultTableModel) jTableDBView.getModel();
// If there's an issue...
if(error handling snipped out...)
{
}
else
{
runQuery(Integer.parseInt(jTextFieldDirectorID.getText()), Integer.parseInt(jTextFieldMovieID.getText()), "UPDATE");
jTextAreaDBView.append("Updated row: " + (jTableDBView.getSelectedRow() + 1) + " (Director ID: " + jTextFieldDirectorID.getText() + " & Movie ID: " + jTextFieldMovieID.getText() +")\n");
updateDB();
}
Which calls...
public void runQuery(int movieID, int directorID, String typeOfCommand) throws SQLException
{
Connection conn = DriverManager.getConnection(jdbcURL);
ResultSet rs = null;
PreparedStatement stat = null;
try
{
//snipped insert/delete
else if(typeOfCommand.equals("UPDATE"))
{
stat = conn.prepareStatement("UPDATE Movie_Directors SET director_id = ?, movie_id = ? WHERE (director_id = ?) AND (movie_id = ?)");
stat.setInt(1, directorID);
stat.setInt(2, movieID);
stat.setInt(3, directorID);
stat.setInt(4, movieID);
int result = stat.executeUpdate();
}
And
private void updateDB()
{
try
{
Connection conn = DriverManager.getConnection(jdbcURL);
PreparedStatement pst = conn.prepareStatement(query);
Class.forName(driver);
ResultSet rs = pst.executeQuery();
jTableDBView.setModel(DbUtils.resultSetToTableModel(rs)); // Imported lib function
conn.close();
pst.close();
rs.close();
}
catch (Exception ex)
{
// Add a window here
}
}
UPDATE - Fixed the runQuery block with this: I added 2 new jTextFields to input the new data and I understand how it all works now. Thank you #jan
int result;
int newDirectorID = Integer.parseInt(jTextFieldNewDirectorID.getText());
int newMovieID = Integer.parseInt(jTextFieldNewMovieID.getText());
stat = conn.prepareStatement("UPDATE Movie_Directors SET director_id = ?, movie_id = ? WHERE director_id = ? AND movie_id = ?");
stat.setInt(1, newDirectorID);
stat.setInt(2, newMovieID);
stat.setInt(3, directorID);
stat.setInt(4, movieID);
result = stat.executeUpdate();
I might be mistaken - but your update does nothing. It assigns (movieId, directorId) to that row which already has exactly that (movieId,directorId)
So if there is already a row - nothing happens. And if there's no matching row - nothing happens as well.
An update would make sense if for instance a movie's director did change at some time.
In that case, you'd need a variable newDirectorID and put it in as new value:
int newDirectorID; // YOU NEED TO PROVIDE A VALUE
stat = conn.prepareStatement("UPDATE Movie_Directors SET director_id = ?, movie_id = ? WHERE director_id = ? AND movie_id = ?");
stat.setInt(1, newDirectorID);
stat.setInt(2, movieID);
stat.setInt(3, directorID);
stat.setInt(4, movieID);
int result = stat.executeUpdate();
This would infact change some data in your table, so number of updated rows should be 1 if (directorID, movieID) existed before.
There is a mysql table with primary key as id int auto_increment,
I need to insert multiple rows in batch with multiple insert statement, with autocommit disabled, as following:
SET autocommit=0;
INSERT INTO dummy(NAME, `size`, create_date) VALUES('test', 1, NOW());
INSERT INTO dummy(NAME, `size`, create_date) VALUES('test', 2, NOW());
COMMIT;
Is it possible to get each generated id, instead of only the last id.
If yes, when was each id generated, and how to get all the ids via jdbc?
Thx.
If you want to retrieve the AUTO_INCREMENT keys via JDBC you need to use the JDBC features for doing so (RETURN_GENERATED_KEYS and .getGeneratedKeys()), like this:
try (Connection conn = DriverManager.getConnection(myConnectionString, "root", "beer")) {
try (Statement st = conn.createStatement()) {
st.execute(
"CREATE TEMPORARY TABLE dummy (" +
"`id` INT AUTO_INCREMENT PRIMARY KEY, " +
"`NAME` VARCHAR(50), " +
"`size` INT, " +
"`create_date` DATETIME " +
")");
}
conn.setAutoCommit(false);
System.out.println("AutoCommit is OFF.");
String sql = "INSERT INTO dummy(NAME, `size`, create_date) VALUES('test', ?, NOW())";
try (PreparedStatement ps = conn.prepareStatement(
sql,
PreparedStatement.RETURN_GENERATED_KEYS)) {
// first batch
ps.setInt(1, 1); // `size` = 1
ps.addBatch();
ps.setInt(1, 2); // `size` = 2
ps.addBatch();
ps.executeBatch();
System.out.println("First batch executed. The following AUTO_INCREMENT values were created:");
try (ResultSet rs = ps.getGeneratedKeys()) {
while (rs.next()) {
System.out.println(rs.getInt(1));
}
}
try (Statement st = conn.createStatement()) {
sql = "SELECT COUNT(*) AS n FROM dummy";
try (ResultSet rs = st.executeQuery(sql)) {
rs.next();
System.out.println(String.format("The table contains %d row(s).", rs.getInt(1)));
}
}
conn.rollback();
System.out.print("Transaction rolled back. ");
try (Statement st = conn.createStatement()) {
sql = "SELECT COUNT(*) AS n FROM dummy";
try (ResultSet rs = st.executeQuery(sql)) {
rs.next();
System.out.println(String.format("The table contains %d row(s).", rs.getInt(1)));
}
}
// second batch
ps.setInt(1, 97); // `size` = 97
ps.addBatch();
ps.setInt(1, 98); // `size` = 98
ps.addBatch();
ps.setInt(1, 99); // `size` = 99
ps.addBatch();
ps.executeBatch();
System.out.println("Second batch executed. The following AUTO_INCREMENT values were created:");
try (ResultSet rs = ps.getGeneratedKeys()) {
while (rs.next()) {
System.out.println(rs.getInt(1));
}
}
}
try (Statement st = conn.createStatement()) {
sql = "SELECT COUNT(*) AS n FROM dummy";
try (ResultSet rs = st.executeQuery(sql)) {
rs.next();
System.out.println(String.format("The table contains %d row(s).", rs.getInt(1)));
}
}
}
... which produces the following console output:
AutoCommit is OFF.
First batch executed. The following AUTO_INCREMENT values were created:
1
2
The table contains 2 row(s).
Transaction rolled back. The table contains 0 row(s).
Second batch executed. The following AUTO_INCREMENT values were created:
3
4
5
The table contains 3 row(s).
I want to print my rows count at the end, But it shows 1
public void showRecords() {
try {
Statement st1 = con.createStatement();
ResultSet result1 = st1.executeQuery("select * from mytable");
while (result1.next()) {
System.out.println(result1.getString(1) + " " + result1.getString(2));
}
ResultSet rs1 = st1.executeQuery("select count(*) from mytable");
int rows = rs1.last() ? rs1.getRow() : 0;
System.out.println("Number of rows is: "+ rows); //print 1
} catch (SQLException sqle) {
System.out.println("Can not excute sql statement");
sqle.printStackTrace();
}
}
Output:
...
Number of rows is: 1
Output: ... Number of rows is: 1
That's absolutely correct because the ouput of a count query like
select count(*) from mytable
would only contain a single row containing the total number of rows. For you to now retrieve that count you should make use of the Resultset's getter methods as usual.
int rows = rs1.getInt(1);
To retrieve the count the way you wanted to; use the same approach with your first query
ResultSet result1 = st1.executeQuery("select * from mytable");
int rows = result1.last() ? result1.getRow() : 0;
System.out.println("Number of rows is: "+ rows); // should print the count
The count(*) does not have a column name (or only a "generated" one that you might not know). Therefor you need to get the value by column index.
Additionally you need to call next() on the ResultSet in order to be able to obtain the value:
ResultSet rs1 = st1.executeQuery("select count(*) from mytable");
int rows = 0;
if (rs1.next() {
rows = rs1.getInt(1);
}
System.out.println("Number of rows is: "+ rows); //print 1
Selecting the count from a RecoredSet always returns a value of 1, i.e. the record containing the result of the query. You want
ResultSet rs1 = st1.executeQuery("select count(*) from mytable");
if (rs1.next()) {
int rows = rs1.getInt("COUNT")
}
You must read the value from the rowcount query, as it is a normal query. Like
rows = rs1.getInt(1);
I've written a blog post about retrieving query metadata without extra roundtrip, which is something people typically do to paginate their data. I really recommend you don't re-run your queries all the time just to count stuff. A simple approach would be to use window functions, which are now supported in a lot of SQL dialects:
SELECT *, count(*) OVER () FROM mytable
Of course, since you're using low level JDBC API to iterate your ResultSet, why not just count things in the client at that point? E.g.
try (ResultSet rs = s.executeQuery("select * from mytable")) {
int i = 0;
while (rs.next()) {
System.out.println(rs.getString(1) + " " + rs.getString(2));
i++;
}
System.out.println("Number of rows is: " + i);
}