PDO::rollback after using prepared statement - pdo

I understand that in order to prevent injection attack, PDO::prepare first sends the query to the server then the parameters go later; Now, I feel this introduces another problem: that implies there cannot be rollback after PDO::execute, or am I missing something?
I have two tables 1 and 2 in an application. The two tables are not supposed to contain the same row. When I use INSERT INTO table1 SELECT FROM Table2, I want to DELETE FROM table2 if the INSERT query succeeds. And if either of the queries fails, I want to rollback. So I have the following code:
$dbConn->beginTransaction();
$stmt1 = $dbConn->prepare( "INSERT INTO table1 ( field1, field2, field3 )
SELECT field1, field2, field3 FROM table2 WHERE field4 = :field4" );
$stmt1->execute( array( $field4 ) );
$stmt2 = $dbConn->prepare( "DELETE FROM table2 WHERE field4 = :field4" );
$stmt2->execute( array( $field4 ) );
if ( $stmt1->rowCount() > 0 && $stmt2->rowCount() > 0 )
{
$dbConn->commit();
return true;
}
else
{
$dbConn->rollBack();
return false;
}
Without prepared statement, this is very easy; but with it, it looks like difficult; has anyone done something like this before?
Thanks.

This isn't difficult and from what I can tell should work fine. However I would restructure the code to use exceptions:
try {
$dbConn->beginTransaction();
$stmt1 = $dbConn->prepare( "INSERT INTO table1 ( field1, field2, field3 )
SELECT field1, field2, field3 FROM table2 WHERE field4 = :field4" );
$stmt1->execute( array( $field4 ) );
$stmt2 = $dbConn->prepare( "DELETE FROM table2 WHERE field4 = :field4" );
$stmt2->execute( array( $field4 ) );
if ( $stmt1->rowCount() > 0 && $stmt2->rowCount() > 0 )
{
$dbConn->commit();
return true;
}
else
{
throw new LogicException('Unequal row counts.');
}
} catch (Exception $e) {
$dbConn->rollBack();
if ($e instanceof LogicException) {
// just return
return false;
} else {
// otherwise rethrow because something we didnt expect to go
// wrong did
throw $e;
}
}
This way we also roll back if something else generates an exception like PDO or some other thing we may be doing as part of our data preparation.

Related

DB2 SQL Error: SQLCODE=-104, SQLSTATE=42601, SQLERRMC=?;AT YEAR YEARS MONTH MONTHS DAY DAYS HOUR HOURS MINUTE MINUTES, DRIVER=3.64.82

My code is looking like this:
addToTable2(ObjectDTO objectDTO) {
def sql = sql.execute(
"""INSERT INTO SCHEMA.TABLE2 (
COLUMN1,
COLUMN2,
COLUMN3 )
VALUES (
${objectDTO.property1}
${objectDTO.property2}
${objectDTO.property3})""")
}
ArrayList<ObjectDTO> fetchValuesFromTable1() throws CollectionsException {
ArrayList<ObjectDTO> fetchValues= new ArrayList<ObjectDTO>()
def sql = """
SELECT *
FROM SCHEMA.TABLE1
ORDER BY DATE DESC FETCH FIRST 1 ROW ONLY"""
try {
if(this.sql != null) {
this.sql.eachRow(sql) {
resultSet ->
ObjectDTO objectDTO = new ObjectDTO()
objectDTO.setProperty1(resultSet.PROPERTY1)
objectDTO.setProperty2(resultSet.PROPERTY2)
objectDTO.setPropety3(resultSet.PROPERTY3)
objectList.add(poulSifr)
}
}
for(ObjectDTO objectDTO: objectList) {
addToTable2(objectDTO)
}
} catch (SQLException se) {
log.info "fetchValues error $se.message executed sql: $sql"
throw new CollectionsException("fetchValues message $se.message")
} finally {
if (this.sql != null) {
this.sql.close()
}
}
objectList
}
I got this error/warning:
WARN groovy.sql.Sql - Failed to execute: INSERT INTO SCHEMA.TABLE (
COLUMN1,
COLUMN2,
COLUMN3 )
VALUES (
?,
?,
null)
because: DB2 SQL Error: SQLCODE=-104, SQLSTATE=42601, SQLERRMC=?;AT
YEAR YEARS MONTH MONTHS DAY DAYS HOUR HOURS MINUTE MINUTES,
DRIVER=3.64.82
Whenever I fetch last value from Table1 I need to put that in Table2. I don't know is this proper way to do it or I'm doing something completely wrong.
you have missing commas in VALUES(...) part of insert

What SQL query is the equivalent to this function for retrieving a list of unique items

I'm trying to change this function into an SQL query (using Room). The goal is to return a list of items with no duplicates.
A duplicate is defined by either the item.id or any combination of linked ids being present.
fun removeDuplicates(items: List<Table>?) : List<Table>?{
val returnItems = ArrayList<Table>()
items?.distinctBy { _item ->
_item.id
}?.forEach { item ->
val LID1 = item.linked_id_1
val LID2 = item.linked_id_2
val isFoundReturnItem = returnItems.firstOrNull {
(it.linked_id_1 == LID2 && it.linked_id_2 == LID1) ||
(it.linked_id_1 == LID1 && it.linked_id_2 == LID2)
}
//only add to our new list if not already present
if(isFoundReturnItem == null)
returnItems.add(item)
}
return returnItems
}
If I read your question right here is the answer for Microsoft SQL. Structure:
Select Distinct Field1, Field2, ...
From Table
Where Field1 between 'a' and 'm'
Your Script: The distinct command makes distinct rows.
Select Distinct Item
From YourTableName
You can also use GROUP BY this allows aggregations on distinct values
Select Field1, Field2 = max(Field2), ...
From Table
Where Field1 between 'a' and 'm'
Group by Field1

Perl sql query statement

My sql statement is simple as below:
if not exists (select col_a from t_a where co_b = 'v_b')
begin
insert into t_a (col_a ,col_b )
VALUES(v_a,v_b)
end
else
begin
update t_a set col_a = v_a, col_b = v_b where col_b = 'v_b'
end
As I have hundreds of rows to update, how can I do this in Perl for the least time cost?
If I use Prepare + Execute, how to write the statement using the placeholder ? ?
Does the $dbh->prepare($statement); support multiple composite SQL lines like those above? Or do I have to save the lines into an sql file and run it using SQL server?
To make the question more clear, my Perl lines look like those below:
$statement = "if ... VALUES(?,?)...update t_a set col_a = ?, col_b = ?";
# better to use one binding values(v_a, v_b) couplets mapping
# the 2 placeholders of insert and update both?
foreach (#$va_arr) {
my $values_for_one_row = $_;
$dbh->prepare($statement);
$execute->execute($values_for_one_row->{col_a }, $values_for_one_row->{col_b });
}
I forgot one thing: the 'whatever' is also a value in $va_arr to be changed on every iteration: if not exists (select col_a from t_a where co_b = 'v_b'). Also, the update section should be: update t_a set col_a = ?, col_b = ? where col_b = "v_b". Seems no better way then include the prepare into the loop? Sorry I didn't think the example complete. But I think simbabque's answer is good enough.
You can use your SQL without problems. You need to prepare the statement once. I am assuming your $va_arr looks like this:
my $va_arr = [
{
col_a => 1,
col_b => 2,
},
{
col_a => 'foo',
col_b => 'bar',
},
];
Your code to run this could be as follows. Note that you have to pass the col_n params twice as it needs to fill them in two times into each ? with every execute. They get filled in the order of the ? in the query, so we need col_a, col_b for the INSERT and another col_a, col_b for the UPDATE.
my $sql = <<'EOSQL';
if not exists (select col_a from t_a where co_b = 'whatever')
begin
insert into t_a (col_a ,col_b )
VALUES(?, ?)
end
else
begin
update t_a set col_a = ?, col_b = ?
end
EOSQL
my $sth = $dbi->prepare($sql);
foreach ($values = #{ $va_arr }) {
$dbh->execute($values->{col_a }, $values->{col_b },
$values->{col_a }, $values->{col_b });
}
If you have a long list of columns and you know the order, consider this:
my #columns = qw( col_a col_b col_c col_n );
my $va_arr = [
{
col_a => 1,
col_b => 2,
col_n => 99,
},
{
col_a => 'foo',
col_b => 'bar',
col_n => 'baz',
},
];
# build the sql dynamically based on columns
my $sql = q{
if not exists (select col_a from t_a where co_b = 'whatever')
begin
insert into t_a (} . join(',' #columns) . q{)
VALUES(} . join(',', map '?', #columns) . q{)
end
else
begin
update t_a set } . join(',' map { "$_ => ?" } #columns) . q{
end
};
my $sth = $dbi->prepare($sql);
foreach ($values = #{ $va_arr }) {
$dbh->execute(#{$values}{#columns}, #{$values}{#columns});
}
Let's look at what this does. It's helpful if you have a really long list of columns.
You know their names and order, and put that into #columns.
Build the SQL based on these columns. We have to add the column name and a ? to the INSERT and the combination of both to the UPDATE for each of the columns.
Execute it with a hash ref slice
Please note that I have not run this, just hacked it in here.
you should put the prepare statement out of the loop and use a transaction
for example:
my $sql1 = qq(select col_a from t_a where col_b = ?);
my $sql2 = qq(insert into t_a (col_a, col_b) VALUES(?, ?));
my $sql3 = qq(update t_a set col_a = ? where col_b = ?);
my $query = $dbh->prepare($sql1);
$dbh->begin_work();
foreach (#$va_arr) {
my $values_for_one_row = $_;
$query->execute($values_for_one_row->{col_b});
my #out = $query->fetchrow_array();
$query->finish();
if ( not defined $out[0] )
{
$dbh->do($sql2, undef, $values_for_one_row->{col_a}, $values_for_one_row->{col_b});
}
else
{
$dbh->do($sql3, undef, $values_for_one_row->{col_a}, $values_for_one_row->{col_b});
}
}
$dbh->commit();
If upsert is not available, here's how I might do it:
Bulk load the data into a staging table.
Delete all data that joins to the target table.
Insert data from staging to target.
Alternatively you can update from staging to target, delete from the staging data that joins, then insert what's left in staging.
Or, a few hundred rows is not that many, so I might: prepare an insert and an update statement handle outside of the loop. Then in the loop:
my $rows = $upd_sth->execute(...);
$ins_sth->execute(...) if $rows == 0;

How to remove the extra record from table in sql

I have one table log:
checktype
---------
I
O
I
o
I
I
I
o
I
O
Now I want that I and O should be in a pair so I want to delete the extra I, means I want only one (I or O) after each other.
The table should contain only I and O in pairs.
You can get this result with the following query quite easily.
Select checktype,((ROW_NUMBER() OVER (ORDER BY checktype))*2 - 1) as Row_id From Table
Where checktype = 'I'
UNION
Select checktype,((ROW_NUMBER() OVER (ORDER BY checktype))*2 ) as Row_id From Table
Where checktype = 'O'
Order by Row_id
I have tested it and it is working for me
Try this:
ResultSet rs = stmt.executeQuery("SELECT checkType FROM myTable");
while(rs.next( )){
if (rs.getString("checkType").equals('I'){
if(rs.next(){
if(rs.getString("checkType").equals('O'){
}else{
//delete logic
}
}
} else if(rs.getString("checkType").equals('O'){
if(rs.next(){
if(rs.getString("checkType").equals('I'){
}else{
//delete logic
}
}
}
}

How to get the last insert ID from a table

I want to get the value of the last ID insert in a table. How I can do this?
Well the solution that I use is:
select id from NEW TABLE (insert into (val1, val2, ...) values ('lorem', 'ipsum', ...))
This gets the id column from the last row inserted in the DB :)
SELECT IDENTITY_VAL_LOCAL() AS VAL FROM SYSIBM.SYSDUMMY1
See docs.
Have a look at this answer.
http://www.sitepoint.com/php-database-db2/
// get the last inserted ID into the specified table
// int lastInsertID(string $tblName)
function lastInsertID($tblName)
{
if ($this->transIsOpen())
{
$sql = "SELECT SYSIBM.IDENTITY_VAL_LOCAL() AS id FROM " . $tblName;
$rs = $this->query($sql);
return $this->fetch($rs, "id");
}
return -1;
}
OR this
http://www.php.net/manual/en/function.db2-last-insert-id.php#98361
int keyId = -1;
preparedStatement.executeUpdate();
resultSet = preparedStatement.getGeneratedKeys();
if (resultSet.next()) {
keyId = rs.getInt(1);
}
https://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#getGeneratedKeys()
Update: and don't forget to create preparedStatement with the following flag Statement.RETURN_GENERATED_KEYS otherwise it won't work)))