How to create JDBC upsert query based on 2 keys - java

I want to preform a upsert(update or create) based on 2 column:
if the column A and Column B exsists in the table then update values else create a new row with this key.
//pasdo code for my query
if(table.key1 == firstKey && table.key2 == secKey){
//update values for the row with key1, key2
} else {
//create a row with firstKey, secKey as keys
}
I have a oracle sql server on the backend.

you can do something similar like this...
In oracle, dual is like dummy table. it does not have any rows.. it helps to create temporary table needed for merge query
following may not exact SQL syntax..
merge into table m using (select firstKey,secKey from dual d) on
(m.key1 = d.firstKey and m.key2 = d.secKey )
when not matched then insert... -- put insert statement here
when matched then update .. -- put statemenet here

Related

Placeholder in prepared statement

I am using the below query in a prepared statement. Earlier I was using in procedure and using callable but now I am trying to use select query in jdbc prepared statement.
I know in preparestatement we write insert into abc values(?,?,?);
but here I have insert-select. same variable has been used many places. in this query I have 2 variable
p_entity_type and p_update_mode
INSERT INTO dynamicEntitynotgett
(entity_type, entity_id, entity_code, synonyms, action)
WITH data_view AS
( -- ITEM table
SELECT 'ITEM' entity_type, -- This separates inserted values
item_id data_id,
item_name data_name,
item_desc data_desc,
creation_date
FROM itemde
UNION ALL
-- ORG table
SELECT 'ORG' entity_type, -- This separates inserted values
org_id,
org_name,
org_desc,
creation_date
FROM orgde
UNION ALL
-- Feature table
SELECT 'FEATURES' entity_type, -- This separates inserted values
FEATURE_id data_id,
FEATURE_NAME data_name,
FEATURE_DESC data_desc,
CREATION_DATE
FROM FEATURESDE
)
SELECT upper(t.entity_type),
t.data_id,
t.data_name,
t.data_desc,
CASE lower(p_update_mode)
WHEN 'INCREMENTAL' THEN
CASE
WHEN t.creation_date > b.last_update_date THEN
'update'
WHEN t.creation_date < b.last_update_date THEN
'add'
END
WHEN 'full' THEN
'add'
END action
FROM data_view t
LEFT JOIN ODA_REFRESH_DETAILS b
ON b.entity_type = t.entity_type
AND lower(p_update_mode )='incremental'
WHERE (upper(p_entity_type) = t.entity_type OR p_entity_type IS NULL)
AND (lower(p_update_mode) = 'full'
OR (lower(p_update_mode) = 'incremental' AND b.entity_type IS NOT NULL)
);
I will receive p_entity_type and p_update_mode from upper stream. which solution would be better? Resultset or Preparedstatement and how can I replace those values in query or use setXXX().
I think you are looking for namedParameterStatement. This would allow you to name the parameters.
I'm not exactly sure what you are referring to in your statement, but for instance, this line:
SELECT 'ITEM' entity_type
could be replaced with:
SELECT :ITEM as entity_type
where :ITEM is passed in just like a ?, but could be used multiple times in the statement.

How can I use exception handling while sending duplicate values in an insert statement, if I'm sending many values in batches?

I've written a class method that will take "batches" of data (each row that makes a "value" to be inserted, via SQL, to the database comes from a two-dimensional array labeled "data_values").
However, there will be instances when my program will be getting redundant data, i.e. data that might already be in the database. Because there's a primary key in the database, the program will break if it cannot upload the data because of a duplicate entry.
Is there a way to use a try/catch so that the program will continue uploading data, effectively "skipping" the duplicates? If so, how can I implement it?
Thank you in advance. If I could clarify my question, please let me know.
My current code is here:
public void insertData(ArrayList<String> data_types, String[][] data_values) {
try{
c.setAutoCommit(false);
// creates insert statement
String insertDataScript = "INSERT INTO "+tableName+" VALUES (";
for(int q = 0; q < data_types.size()-1; q++) {
insertDataScript += "?, ";
}
insertDataScript += "?)";
PreparedStatement stmt = c.prepareStatement(insertDataScript);
for (int i = 0; i < data_values.length; i++) {
for(int j = 1; j < data_types.size()+1; j++) {
if(data_types.get(j-1).toLowerCase().equals("double")) {
stmt.setDouble(j, Double.valueOf(data_values[i][j-1]));
}
else if(data_types.get(j-1).toLowerCase().equals("string")) {
stmt.setString(j, data_values[i][j-1]);
}
else {
System.out.println("Error");
}
}
stmt.addBatch();
}
stmt.executeBatch();
c.commit();
c.setAutoCommit(true);
stmt.close();
}
catch ( Exception e ) {
System.err.println( e.getClass().getName() + ": " + e.getMessage() );
System.exit(0);
}
}
My first suggestion would be to deduplicate the data before inserting it into the db. (Edit: totally missed the "already in the db" part, so this probably won't work unless you want to do a query before every insert. Maybe you can use an INSERT IGNORE?)
If you cannot do this because you do not have control over the primary key or there is no way to ignore duplicates in the insert, then there are ways to catch specific exception types and continue the program instead of calling System.exit. In order to do that you would probably need to have smaller prepared statements and put the try/catch inside the for loop over 'data_values`.
Here is a post talking about catching this type of exception: Catch duplicate key insert exception.
INSERT OR IGNORE
Simply change (albeit it not really exception handling, but rather exception bypassing)
String insertDataScript = "INSERT INTO "+tableName+" VALUES (";
to
String insertDataScript = "INSERT OR IGNORE INTO "+tableName+" VALUES (";
Consider the following demo (equivalent to suggested and then what you currently have) :-
rowid has been used for convenience as it's basically a build in primary key.
the only reason why the columns have been specified i.e.(rowid,othercolumn,mydatecolumn) is that rowid is normally hidden. In your case just VALUES (without the preceding columns) will expect values for all columns and thus include the defined primary key column(s).
shown/actioned in reverse order as both can then run together
:-
INSERT OR IGNORE INTO mytable (rowid,othercolumn,mydatecolumn) -- rowid is a PRIMARY KEY as such
VALUES
(10,'x','x'),
(11,'x','x'),
(12,'x','x'),
(13,'x','x'),
(14,'x','x'),
(10,'x','x')
;
INSERT INTO mytable (rowid,othercolumn,mydatecolumn) -- rowid is a PRIMARY KEY as such
VALUES
(20,'x','x'),
(21,'x','x'),
(22,'x','x'),
(23,'x','x'),
(24,'x','x'),
(20,'x','x')
;
results in :-
INSERT OR IGNORE INTO mytable (rowid,othercolumn,mydatecolumn) -- rowid is a PRIMARY KEY as such
VALUES
(10,'x','x'),
(11,'x','x'),
(12,'x','x'),
(13,'x','x'),
(14,'x','x'),
(10,'x','x')
> Affected rows: 5
> Time: 0.208s
i.e. 5 of the 6 were added the 6th a duplicate (according to the primary key) was skipped.
INSERT INTO mytable (rowid,othercolumn,mydatecolumn) -- rowid is a PRIMARY KEY as such
VALUES
(20,'x','x'),
(21,'x','x'),
(22,'x','x'),
(23,'x','x'),
(24,'x','x'),
(20,'x','x')
> UNIQUE constraint failed: mytable.rowid
> Time: 0.006s
i.e. none are inserted due to 1 duplicate.
INSERT OR REPLACE (may be useful)
If you wanted the data from the duplicates to be applied then instead of INSERT OR IGNORE, you could use INSERT OR REPLACE.
e.g. the following (run after the above i.e. all are duplicates bit with different data):-
INSERT OR REPLACE INTO mytable (rowid,othercolumn,mydatecolumn) -- rowid is a PRIMARY KEY as such
VALUES
(10,'xx','x'),
(11,'x','xx'),
(12,'aa','x'),
(13,'x','aa'),
(14,'x','bb'),
(10,'cc','x')
;
then you get :-
INSERT OR REPLACE INTO mytable (rowid,othercolumn,mydatecolumn) -- rowid is a PRIMARY KEY as such
VALUES
(10,'xx','x'),
(11,'x','xx'),
(12,'aa','x'),
(13,'x','aa'),
(14,'x','bb'),
(10,'cc','x')
> Affected rows: 6
> Time: 0.543s
i.e. now all 6 INSERTs are actioned (5 rows updated as the 1st and last update the same row twice).

Can I compare resultsets like this? I'm facing the below error

I have 2 ResultSets. 1st ResultSet contains the records from table1 from database1 and 2nd ResultSet contains the records from table2 from database2. I need a list of records from resultset1 which are not present in resultSet2. For this I wrote this logic but it is not working and throwing me the following error.
java.sql.SQLException: Invalid operation for read only resultset: deleteRow
if ( table1ResultSet != null )
{
while ( table1ResultSet.next() )
{
final String table1Record = table1ResultSet.getString( 1 );
if ( table2ResultSet != null )
{
while ( table2ResultSet.next() )
{
final String table2Record = table2ResultSet.getString( 1 );
if ( table1Record.toString().equalsIgnoreCase( table2Record.toString() ) )
{
table1ResultSet.deleteRow();
break;
}
}
}
}
}
return table1ResultSet;
That exception says what the problem is - your result set doesn't support delete. In order to have updateable result set there are some requirements:
When you prepare statement did you make it with ResultSet.CONCUR_UPDATABLE?
A query can select from only a single table without any join operations.
The query must select all non-nullable columns and all columns that do not have a default value. A query cannot use "SELECT * ". Cannot select derived columns or aggregates such as the SUM or MAX of a set of columns.
You might want to move the results sets into Java sets before working doing what you are doing though because using deleteRow will actually delete the row from the database (unless that's the expected result)
There is another problem with your code though. Even if delete works your code will fail on the second iteration of result set 1 because you never reset table2ResultSet and for the second iteration there won't be more results in table2resulset.
But on top of all that. Why would you go through all that hussle and get all that rows that you don't need instead of doing it with one single query like:
select * from table 1 where id not in select id from table 2
or
delete from table 1 where id not in select id from table 2
if that's the goal
Your logic:
Assumes the records come in some order (which may or may not be true, depending on your SQL)
Consumes the entire result set 2 for each row of result set 1, which is unlikely your intent
Deletes things, which is also not what you mentioned in the question
Your question can be implemented easily as such:
Set<String> list1 = new HashSet<>();
while (table1ResultSet.next())
list1.add(table1ResultSet.getString(1).toLowerCase());
while (table2ResultSet.next())
list1.remove(table2ResultSet.getString(1).toLowerCase());
System.out.println(list1);
This will print all the values (without duplicates) that are present in the first result set, but not in the second.

Limit the number of rows in a room database

How can I limit the number of rows in an Android room database by removing the oldest item in the row and inserting the newest one?
I am guessing its a standard query when adding an item to the database?
EDIT: I want to limit a database table to have a max row count of say 20. If that limit is reached, we remove the oldest item and insert the new one by keeping the current row count to 20.
I think you can insert the data into your table then remove all the rows except last 20 (limit)
To delete you can use the following query
DELETE FROM tableName where id NOT IN (SELECT id from tableName ORDER BY id DESC LIMIT 20)
In this case, id is the primary key which is set to auto increment. You can use date as key as well if you are storing them by date
Here is sample solution:
Query is :
#Query("SELECT * FROM user LIMIT :limit OFFSET :offset")
User[] loadAllUsersByPage(int limit,int offset);
Here, it will give a list of user based on limit and offset.
if loadAllUsersByPage(2,0) it will return first 2 rows from table.
if loadAllUsersByPage(2,1) it will return 2nd and 3rd rows from table.
but if loadAllUsersByPage(-1,10) then it will serve first 10 rows from table.
Assuming:
Your table is
create table example_table (
ts timestamp,
uid number(19),
some_other_field varchar(64)
);
And you don't want to care about running some query manually.
Use database triggers:
create trigger
if not exists -- I don't actually know if you DB will support this line.
-- Might want to remove it if it's not.
example_table_limiter
on example_table
after insert
begin
delete
from example_table
where ts in (
select ts
from example_table
order by ts
limit -1 -- we don't want to limit how many rows we want to delete
offset 25 -- but we want to offset query result so it leaves 25 rows in table
);
end;
"Offset without limit" syntax is inspired by this answer.
To enable your trigger in java:
Simple Android, where you can override SQLiteOpenHelper:
public class DataBaseSchemaHelper extends SQLiteOpenHelper {
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(<trigger string from above>);
}
}
Android Room version:
public class MyDatabase extends RoomDatabase {
#Override
public void init(DatabaseConfiguration _config) {
super.init(_config);
getOpenHelper().getWritableDatabase().execSQL(<trigger string from above>);
}
}
You can limit columns/rows by doing this: this query will return the new data and remove old data when its reach its limit.
Explanation:
First query is select all data order by descending
Second query is remove data from columns/rows id > 20
If you want your table only have 20 row then set the OFFSET to 20, the LIMIT is represent how many rows inserted & deleted at once.
In my example I remove 1 row (the oldest/last row) when the user inputs 1 new data
#Query("SELECT * FROM my_table ORDER BY timeStamp DESC")
fun getAllData(): List<MyEntityClass>
#Query("DELETE FROM my_table WHERE id IN (SELECT id FROM my_table ORDER BY timeStamp DESC LIMIT 1 OFFSET 20)")
fun removeOldData()
Follow this steps :
1> get count of rows of that table
your_count = SELECT count( * ) FROM table_name;
2> if count is >(greater than) 20 than to get oldest record
SELECT *
FROM table_name
ORDER BY entry_Date ASC
LIMIT 1;
3> now delete these selected records
4> insert new datas
NOTE : if you are inserting multiple entries than put this in loop

Send an Array as a parameter of a query from Java to SQL Server

What i need is to send an Array or something that can contain multiple ordered data that will be read into a WHILE cyle in a Stored Procedure in SQL Server, the only problem is that i don't know how to send a parameter as an array or a table.
If there's a way that doesn't involve Arrays, but still keeps the idea of send multiple data into a single parameter i'll be thankful.
NOTE: I will send the parameter from Java to SQL Server using JDBC
You can send it as a comma separated list and shred it at the SQL Server side or you could use a XML variable and shred the XML data.
However, at the SQL Server side I'd avoid using a WHILE loop due to possible performance impacts. Instead, shred and use the data ll at once.
Just to share, this is the best solution that i found (thanks to user2067753):
(What's below comes from here)
Let's create a Person table which have an ID and Name column.
CREATE TABLE Person(
ID INT IDENTITY(1,1) NOT NULL,
Name VARCHAR(200) NOT NULL
CONSTRAINT PK_Person PRIMARY KEY CLUSTERED (ID ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
We have to create Table Value function which will split our comma separated string into table
Before going into this i would recommend you to read following topics on MSDN
CHARINDEX
SUBSTRING
LEN
Create function ‘SplitDelimiterString’ which will split string with delimiter.
CREATE FUNCTION SplitDelimiterString (#StringWithDelimiter VARCHAR(8000), #Delimiter VARCHAR(8))
RETURNS #ItemTable TABLE (Item VARCHAR(8000))
AS
BEGIN
DECLARE #StartingPosition INT;
DECLARE #ItemInString VARCHAR(8000);
SELECT #StartingPosition = 1;
--Return if string is null or empty
IF LEN(#StringWithDelimiter) = 0 OR #StringWithDelimiter IS NULL RETURN;
WHILE #StartingPosition > 0
BEGIN
--Get starting index of delimiter .. If string
--doesn't contain any delimiter than it will returl 0
SET #StartingPosition = CHARINDEX(#Delimiter,#StringWithDelimiter);
--Get item from string
IF #StartingPosition > 0
SET #ItemInString = SUBSTRING(#StringWithDelimiter,0,#StartingPosition)
ELSE
SET #ItemInString = #StringWithDelimiter;
--If item isn't empty than add to return table
IF( LEN(#ItemInString) > 0)
INSERT INTO #ItemTable(Item) VALUES (#ItemInString);
--Remove inserted item from string
SET #StringWithDelimiter = SUBSTRING(#StringWithDelimiter,#StartingPosition +
LEN(#Delimiter),LEN(#StringWithDelimiter) - #StartingPosition)
--Break loop if string is empty
IF LEN(#StringWithDelimiter) = 0 BREAK;
END
RETURN
END
Let's create a store procedure which will take Ids string and return names against those Ids
CREATE PROCEDURE GetPersonsByIds #Ids VARCHAR(8000)
AS
BEGIN
SELECT * FROM Person
WHERE ID IN (SELECT * FROM SplitDelimiterString(#Ids, ','))
END
Now pass Ids to store procedure and let's see what is the output
EXEC GetPersonsByIds '3,7,9'
Output:
ID Name
3 Amancio Ortega
7 David Koch
9 Liliane Bettencourt

Categories

Resources