select and delete record in transaction sql - sql

I am using oracle as my database server. I have a simple sql table which stores codes for each member. I want to remove code from the table but also get the value of it.
SQL> describe member_code_store;
Name Null? Type
----------------------------------------- -------- ----------------------------
MEMBER NOT NULL NUMBER(38)
CODE NOT NULL VARCHAR2(30)
So I want to run below queries in a transaction
PreparedStatement pstmt = null;
ResultSet rs = null;
String query =
"SELECT code FROM member_code_store where coupon=? AND rownum=1";
Connection connection = DBConnection.getConnection();
pstmt = connection.prepareStatement(query);
pstmt.setString(1, String.valueOf(3));
rs = pstmt.executeQuery();
rs.next();
String code = rs.getString(1);
delete the code now
String query =
"delete from member_code_store where coupon =? AND code=?;";
Connection connection = DBConnection.getConnection();
pstmt = connection.prepareStatement(query);
pstmt.setString(1, String.valueOf(3));
pstmt.setString(2, code);
rs = pstmt.executeUpdate();
Problem with the above code is that multiple workers removing the codes will get the same code. How do I enclose the transaction so that I just lock the record instead of locking the whole table.
Or should I use procedures or packages which are more efficient?

Essentially you should use row lock. The example I show includes the nowait option, which wil return an error if you try to select the row that is lock and your code will have to handle that.
select code, rowid
from member_code_store
where coupon=? AND rownum=1
for update of code nowait
Save the rowid so that you have a variable to supply to the delete statement
delete from member_code_store
where rowid = :row_id

Related

When is ResultSet Needed

Which SQL statement needs a ResultSet to process return data? Update, Select, Insert, Delete? In JDBC
A ResultSet represents the database result set of a query. See https://docs.oracle.com/javase/8/docs/api/java/sql/ResultSet.html
Here is an example:
Statement statement = connection.createStatement();
String sql = "select * from users";
ResultSet result = statement.executeQuery(sql);
You can use the ResultSet to get the data from the table
while(result.next()) {
String name = result.getString("name");
System.out.println(name);
}
...or to update an entry
result.absolute(5); // moves the cursor to the fifth row of rs
result.updateString("name", "Cthullhu");
result.updateRow();
After use, you should close the ResultSet and the Statement
result.close();
statement.close();
You use a ResultSet after a select query using execute on the statement.
If you have update, insert or delete you can use executeUpdate on the statement
See this introduction from Oracle:
https://docs.oracle.com/javase/tutorial/jdbc/basics/processingsqlstatements.html#creating_statements

Where Clause is not working in Update but working in Select in Oracle DB

I am trying to execute the below update query
update custom_field cfe set cfe.field_value =:valueId where cp_entity_id = :cId
0 rows updated.
This is not updating any row but same where clause is working fine with select query and returns 1 row
select * from custom_field where cp_entity_id = :cId
Also, if i hardcode the value of cId parameter then update works fine but I am executing it from java program so it's not possible for me to hardcode the value
Also cp_entity_id column is a foreign key.
Try this, I faced similar issue.
Use this
select primary_key from custom_field where cp_entity_id = :cId query to find out primary key and Then use that primary key in your where clause of update query.
One of the ways to set parameter is explained here.
PreparedStatement ps = conn.prepareStatement(
"UPDATE Messages SET description = ?, author = ? WHERE id = ? AND seq_num = ?");
// set the preparedstatement parameters
ps.setString(1,description);
ps.setString(2,author);
ps.setInt(3,id);
ps.setInt(4,seqNum);
// call executeUpdate to execute our sql update statement
ps.executeUpdate();
ps.close();

parameterizing all JOIN data in a large UPDATE operation

I have an app that makes a bunch of updates to objects hydrated with data from an SQL Server table and then writes the updates objects' data back to the DB in one query. I'm trying to convert this into a parameterized query so that I don't have to do manual escaping, conversions, etc.
Here's the most straightforward example query:
UPDATE TestTable
SET [Status] = DataToUpdate.[Status], City = DataToUpdate.City
FROM TestTable
JOIN
(
VALUES --this is the data to parameterize
(1, 0, 'A City'),
(2, 0, 'Another City')
) AS DataToUpdate(Id, [Status], City)
ON DataToUpdate.Id = TestTable.Id
I've also played around with using OPENXML to do this, but I'm still forced to write a bunch of escaping code when adding the values to the query. Any ideas on how to make this more elegant? I am open to ADO.NET/T-SQL solutions or platform-agnostic solutions.
One thought I had (but I don't really like how dynamic this is) is to dynamically create parameters and then add them to an ADO.NET SqlConnection, e.g.
for(int i = 0; i < data.Length; i++)
{
string paramPrefix = string.Format("#Item{0}", i);
valuesString.AppendFormat("{0}({1}Status)", Environment.NewLine, paramPrefix);
var statusParam = new SqlParameter(
string.Format("{0}Status", paramPrefix),
System.Data.SqlDbType.Int)
{ Value = data[i].Status };
command.Parameters.Add(statusParam);
}
I'm not exactly sure how you store your application data (and I don't have enough rep points to post comments) so I will ASSUME that the records are held in an object CityAndStatus which is comprised of int Id, string Status, string City held in a List<CityAndStatus> called data. That way you can deal with each record one at a time. I made Status a string so you can convert it to an int in your application.
With those assumptions:
I would create a stored procedure https://msdn.microsoft.com/en-us/library/ms345415.aspx in SQL Server that updates your table one record at at time.
CREATE PROCEDURE updateCityData (
#Id INT
,#Status INT
,#City VARCHAR(50)
)
AS
BEGIN TRAN
UPDATE TestTable
SET [Status] = #Status
,City = #City
WHERE Id = #Id
COMMIT
RETURN
GO
Then I would call the stored procedure https://support.microsoft.com/en-us/kb/310070 from your ADO.NET application inside a foreach loop that goes through each record that you need to update.
SqlConnection cn = new SqlConnection(connectionString);
cn.Open();
foreach (CityAndStatus item in data)
{
SqlCommand cmd = new SqlCommand("updateCityData",cn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#Id", item.Id);
cmd.Parameters.AddWithValue("#Status", Convert.ToInt32(item.Status));
cmd.Parameters.AddWithValue("#City", item.User);
cmd.ExecuteNonQuery();
cmd.Dispose();
}
cn.Close();
After that you should be good. The one thing left that might stand in your way is SQL Server makes web application users grant permission to execute stored procedures. So in SQL Server you may have to do something like this to allow your application to fire the stored proc.
GRANT EXECUTE
ON updateCityData
TO whateverRoleYouHaveGivenPermissionToExecuteStoredProcedures
Good Luck

Multiple SQL Strings in one PreparedStatement

I've read on some other issues here, that it is not possible to use multiple different sql queries in one prepared statement.
String name = "XYZ";
int orderId = 10;
int imageId = 5;
String statement = "UPDATE T_order SET name = ? WHERE orderId = ?; UPDATE T_Order_Relations SET imageId = ? WHERE T_Order_Relations.orderId = ?";
PreparedStatement ps = connection.prepareStatement(statement)
ps.setString(1, name);
ps.setInt(2,orderId);
ps.setInt(3,imageId);
ps.setInt(4,orderId);
I had assumed, that this would lead to an exception from dbms or anything else, but actually i can execute this statement without any problems. Also the changes are done in db. So is it may be possible to use queries like this ?
Another question would be, are those both query executed like a single transaction (auto commit mode) ?
As DBMS im using PostgreSQL 9.3

UPDATE does not lock table if no rows match, race condition

I want to increment and return a counter from a database table.
The java code is as follows:
String sqlUpdate = "UPDATE mytable SET col3 = col3 + 1 WHERE colpk1 = ? AND colpk2 = ?";
Query queryUpdate = manager.createNativeQuery(sqlUpdate);
queryUpdate.setParameter(1, ...);
queryUpdate.setParameter(2, ...);
int num = queryUpdate.executeUpdate();
if (num == 0) {
long count = 1;
String sqlInsert = "INSERT INTO mytable (colpk1, colpk2, col3) VALUES (?,?,?)";
Query queryInsert = manager.createNativeQuery(sqlInsert);
queryInsert.setParameter(1, ...);
queryInsert.setParameter(2, ...);
queryInsert.setParameter(3, count);
queryInsert.executeUpdate();
return count;
} else {
String sqlSelect = "SELECT col3 FROM mytable WHERE colpk1 = ? AND colpk2 = ?";
Query querySelect = manager.createNativeQuery(sqlSelect);
querySelect.setParameter(1, ...);
querySelect.setParameter(2, ...);
Object result = querySelect.getSingleResult();
return Long.parseLong(result.toString());
}
This works well also concurrently used (creates a lock) in case there is already a row with the given primary key. However, in case that row does not exist yet (num == 0), the UPDATE does not lock, and a concurrent access can happen in between the two queries, then leading to a Unique Constraint validation when executing the INSERT as the new row was already created in the meantime.
What's the best way to solve this problem? Would it be better to use a SELECT FOR UPDATE first and then depending on the result doing an UPDATE or INSERT?
The MERGE statement will avoid the split statements.
http://en.wikipedia.org/wiki/Merge_(SQL)
Alternatively, you could always trap the Unique constraint exception for the rare cases when the condition occurs, and retry.
As Merge can throw the Unique Constraint exception in concurrent execution, the best solution was to catch the exception when executing the insert, then the row must be there already, and continue with the update then.
Getting this transaction to commit in case of container managed transactions was the next problem, as the exception lead to isRollBackOnly == true. The way that worked was to use a new bean call for trying the insert within a new transaction, see Commit transaction after exception - undo setRollbackOnly