MERGE INTO multiple statements at once Oracle SQL - sql

I have a merge into statement as this :
private static final String UPSERT_STATEMENT = "MERGE INTO " + TABLE_NAME + " tbl1 " +
"USING (SELECT ? as KEY,? as DATA,? as LAST_MODIFIED_DATE FROM dual) tbl2 " +
"ON (tbl1.KEY= tbl2.KEY) " +
"WHEN MATCHED THEN UPDATE SET DATA = tbl2.DATA, LAST_MODIFIED_DATE = tbl2.LAST_MODIFIED_DATE " +
"WHEN NOT MATCHED THEN " +
"INSERT (DETAILS,KEY, DATA, CREATION_DATE, LAST_MODIFIED_DATE) " +
"VALUES (SEQ.NEXTVAL,tbl2.KEY, tbl2.DATA, tbl2.LAST_MODIFIED_DATE,tbl2.LAST_MODIFIED_DATE)";
This is the execution method:
public void mergeInto(final JavaRDD<Tuple2<Long, String>> rows) {
if (rows != null && !rows.isEmpty()) {
rows.foreachPartition((Iterator<Tuple2<Long, String>> iterator) -> {
JdbcTemplate jdbcTemplate = jdbcTemplateFactory.getJdbcTemplate();
LobCreator lobCreator = new DefaultLobHandler().getLobCreator();
while (iterator.hasNext()) {
Tuple2<Long, String> row = iterator.next();
String details = row._2();
Long key = row._1();
java.sql.Date lastModifiedDate = Date.valueOf(LocalDate.now());
Boolean isSuccess = jdbcTemplate.execute(UPSERT_STATEMENT, (PreparedStatementCallback<Boolean>) ps -> {
ps.setLong(1, key);
lobCreator.setBlobAsBytes(ps, 2, details.getBytes());
ps.setObject(3, lastModifiedDate);
return ps.execute();
});
System.out.println(row + "_" + isSuccess);
}
});
}
}
I need to upsert multiple of this statement inside of PLSQL, bulks of 10K if possible.
what is the efficient way to save time : execute 10K statements at once, or how to execute 10K statements in the same transaction?
how should I change the method for support it?
Thanks,
Me

the most efficient way would be one that bulk-loads your data into the database. In comparison to one-by-one uploads (as in your example), I'd expect performance gains of at least 1 or 2 orders of magnitude ("bigger" data means less to be gained by bulk-inserting).
you could use a technique as described in this answer to bulk-insert your records into a temporary table first and then perform a single merge statement using the temporary table.

Related

Sqlite Update query with SqliteModernCpp

I am using the SqliteModernCpp library. I have a data access object pattern, including the following function:
void movie_data_access_object::update_movie(movie to_update)
{
// connect to the database
sqlite::database db(this->connection_string);
// execute the query
std::string query = "UPDATE movies SET title = " + to_update.get_title() + " WHERE rowid = " + std::to_string(to_update.get_id());
db << query;
}
Essentially, I want to update the record in the database whose rowid (the PK) has the value that the object to_update has in its parameter (which is returned by get_id()).
This code yields an SQL logic error. What is the cause of this?
It turned out single quotes (') within the query string being created were missing. The line should be:
std::string query = "UPDATE movies SET title = '" + to_update.get_title() + "' WHERE rowid = " + std::to_string(to_update.get_id());
Since there is no UPDATE example in the official docs on github, This is how UPDATE queries should be implemented with prepared statements and binding
#define MODERN_SQLITE_STD_OPTIONAL_SUPPORT
#include "sqlite_modern_cpp.h"
struct Book {
int id;
string title;
string details;
Book(int id_, string title_, string details_):
id(std::move(id_)),
title(std::move(title_)),
details(std::move(details_)) {}
}
int main() {
Book book = Book(0, "foo", "bar")
sqlite::database db("stackoverflow.db");
// Assuming there is a record in table `book` that we want to `update`
db <<
" UPDATE book SET "
" title = ?, "
" details = ? "
" WHERE id = ?; "
<< book.title
<< book.details
<< book.id;
return 0;
}

How to build SELECT * WHERE using collection of conditions

I want to build a SELECT statement using a list of conditions that come from the query string of a REST api. I wrote this function, but maybe it is vulnerable to SQL injection. Can someone tell me if this is vulnerable how to fix it? Perhaps I should use some kind of SQLBuilder package? or is there a way to do it with just dotNet. I'm using dotNet 4.6.1
string BuildSelect(NameValueCollection query)
{
var result = "SELECT * FROM MYTABLE";
if (query.Count == 0) return result;
var logic = " WHERE ";
foreach (string key in query)
foreach (string v in query.GetValues(key))
{
result += logic + key + " = " + v;
logic = " AND ";
}
return result;
}
Yes it is vulnerable to SQL injection attack. You could build your query to use parameters instead (you are simply using an = check only).
Since you know the tablename, that means you also know what the columns (keys) can be. Thus, you could loop your columns, if the collection has that key then add it to the where as a parameterized statement BUT value part is NOT passed as a string, you parse it to the type it should be (or let the backend do the conversion and get error if cannot be converted). In pseudocode:
List<string> clauses = new List<string>();
var result = "SELECT * FROM MYTABLE";
foreach( var col in myTable.Columns )
{
if (query.ContainsKey(col.Name))
{
clauses.Add( $"{col.Name} = #{col.Name}";
string v = query[col.Name];
command.Parameters.Add( $"#{col.Name}", col.Type).Value = typeParse(v);
}
}
if (clauses.Any())
{
result += " WHERE " + string.Join( " AND ", clauses );
}
return result;
HTH

SQL Isolation level

We are using session isolation serializable in our application. The intended behavior is that when a user is going insert a new row, it should-should check for the presence of the row with the same key and update the same if row found. But I have found multiple rows created for the same key in SQL server. Is this issue with isolation or the way we are handling the case?
Following is the code I am using,
private int getNextNumber(String objectName, Connection sqlConnection) throws SQLException {
// TODO Auto-generated method stub
int number = 0;
try{
sqlConnection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
System.out.println("##### Transaction isolation set : " + sqlConnection.getTransactionIsolation());
Statement stmt = sqlConnection.createStatement();
ResultSet rs = stmt.executeQuery("select * from [dbo].[db] where DocumentNumber = '" + objectName.toString() + "' FOR UPDATE");
while(rs.next()) {
printNumber = rs.getInt("PrintNumber");
}
System.out.println("#### Print number found from sql is : " + printNumber);
if(printNumber == 0) {
printNumber = printNumber + 1;
stmt.execute("INSERT INTO [dbo].[db] (number, DocumentNumber) VALUES (1 ,'" + objectName.toString() + "')");
} else {
number = number + 1;
stmt.execute("UPDATE [dbo].[db] SET Number =" + number + " WHERE DocumentNumber ='" + objectName.toString() + "'");
}
//sqlConnection.commit();
}catch(Exception e) {
sqlConnection.rollback();
e.printStackTrace();
} finally {
sqlConnection.commit();
}
return number;
}
Thanks,
Kishor Koli
It's an issue with the way your database is set up. You need a unique constraint to enforce uniqueness. You can check at insert time all you like but a unique constraint is the only way it's going to work 100% so it's just a waste of time selecting before inserting in the hope you'll prevent a duplicate. Insert, catch the exception/error or proceed.

List is returning with two sets of square brackets from sql executeInsert

I am working on a groovy script which uses the result of an sql exceuteInsert in the next insert statement executed.
The list returned from the executeInsert has two sets of squarebrackets around it, which is causing sql syntax errors. I am unsure why it is returning with two sets of brackets and if there is a way of removing both.
I have managed to remove one set of brackets using a join. My code is as follows:
db.withTransaction {
def ticketResultList
parsedTicketData.each { ticket ->
String ticketQuery = "INSERT INTO ticket" + "(name, summary) VALUES" + "('${ticket.name}','${ticket.summary}')"
def ticketResult = db.executeInsert(ticketQuery)
ticketResultList = ticketResult.join(",")
}
parsedStatusData.each { status ->
String ticketStatusQuery = "INSERT INTO ticket_status" + "(status, status_date, ticket_id, version) VALUES" + "('${status.status}','${status.statusDate}', ${ticketResultList}, 1)"
db.executeInsert(ticketStatusQuery)
}
The following is the sql error that is being received: Rolling back due to: Incorrect integer value: [31041] for column 'ticket_id' at row 1
Without knowing anything about the code or motivation behind the code /and without reading the comments too!) I'd write something like this:
db.withTransaction {
def ticketResultList = []
parsedTicketData.each { ticket ->
def ticketQuery = "INSERT INTO ticket" + "(name, summary) VALUES" + "('${ticket.name}','${ticket.summary}')"
def ticketResult = db.executeInsert(ticketQuery)
ticketResultList << ticketResult
}
parsedStatusData.each { status ->
def ticketStatusQuery = "INSERT INTO ticket_status" + "(status, status_date, ticket_id, version) VALUES" + "('${status.status}','${status.statusDate}', ${ticketResultList.flatten().join(',')}, 1)"
db.executeInsert(ticketStatusQuery)
}
Based on the information from Groovy executeInsert.
I'm assuming:
your table ticket returns one autogerarated key
the lists parsedStatusData and parsedTicketData have the same length and corresponding entries on the same possition.
db.withTransaction {
parsedTicketData.each { ticket ->
def INS1 = "INSERT INTO ticket (name, summary) VALUES (${ticket.name},${ticket.summary})"
def ticketResult = db.executeInsert(INS1)
ticketResultList << ticketResult[0][0] /* get the 1st returned value */
}
parsedStatusData.eachWithIndex { status, i ->
def INS2 = """INSERT INTO ticket_status (status, status_date, ticket_id, version)
VALUES (${status.status},${status.statusDate}, ${ticketResultList[i]}, 1)"""
db.executeInsert(INS2)
}
}
Note also that I reformulated your SQL strings, this is not only more compact, but leads also to the use of bind varibles. The produced SQL is something like
INSERT INTO ticket (name, summary) VALUES (:1,:2) RETURNING ID INTO :3

sqlite / websql: separate queries produce different results than join queries

I have a very simple db that has two tables, one to store a user's auth (username and pw) and one to store preferences. Currently when my app initializes, it checks for settings first and then auth. But I want to grab it all at once. The way the app is designed, there is only ever 1 record in either table, so I don't use WHERE clauses in my queries.
The problem is, when I do queries separately, they give different results than if I join them.
Consider this scenario: a user logs in, sets some settings and then logs out. In may app this causes the auth table to be cleared but the settings remain intact.
Sign-out query (just clears auth table)
db.transaction(function(tx) {
tx.executeSql('DELETE FROM userdata');
}, errorDB);
However this outputs "0 rows found":
db.transaction(function(tx) {
tx.executeSql('SELECT USERDATA.*, PREFS.* FROM USERDATA, PREFS', [], function (tx, results) {
var len = results.rows.length;
console.log(len + " rows found.");
for (var i=0; i<len; i++){
console.log("Row = " + i + " ID = " + results.rows.item(i).id + " Data = " + results.rows.item(i));
}
}, errorDB);
}, errorDB);
Whereas this outputs a row:
db.transaction(function(tx) {
tx.executeSql('SELECT * FROM PREFS', [], function (tx, results) {
var len = results.rows.length;
console.log(len + " rows found.");
for (var i=0; i<len; i++){
console.log("Row = " + i + " ID = " + results.rows.item(i).id + " Data = " + results.rows.item(i));
}
}, errorDB);
}, errorDB);
Even if there is no auth shouldn't the join query find the prefs data? What's going on here?
When you list two tables A and B in the FROM clause, you get a cross join, which returns A×B records.
So if one table is empty, the result will be empty, too.
To ensure that you have a record even for an empty table, add another record with UNION ALL, then use LIMIT 1 return the first record.
For example,
SELECT Name, PW FROM UserData
UNION ALL
SELECT NULL, NULL
LIMIT 1
will return either a single record with the name/password, or a single record with two NULL values.
To join that with the other table, use a subquery:
SELECT *
FROM (SELECT Name, PW FROM UserData
UNION ALL
SELECT NULL, NULL
LIMIT 1),
Prefs