SQL Join on bound variable - java

I have a dynamically generated SQL Query being built in Java. The way it is being built is by having an array of parameters to be bound and a dynamically created String representing the query. Something like this:
List<String> bindVariables;
String query = "Select * from TABLE where id = ?";
After the query is finished being created. the variables are bound in based on their index in the array. Like this:
query = selectPortion + fromPortion + wherePortion;
// Bind all parameters
for (int i = 0; i < bindVariables.size(); i++) {
queryStatement.setNString(i + 1, bindVariables.get(i));
}
And then the query is executed.
This works fine when we are just binding variables to the WHERE clause, as it is simply just adding the condition and the variable to the end of the array. However, the issue is when I want to JOIN on a variable after the array already has some values.
wherePortion += "and table1.id = ? ";
bindVariables.add("15");
wherePortion += "and table2.color = ? ";
bindVariables.add("blue");
fromPortion += "INNER JOIN table3 on table3.size = ? ";
bindVariables.add(); //Here is the issue
Is there a recommended way to approach this situation?

It seems that I was looking for a CROSS JOIN

Related

Get the last inserted id from table in hibernate from MySql database

I have an entity named Item in which i have declared a property
#Column(name="ITEM_CODE", unique = true, length=30)
private String itemCode
while inserting a new item in the corresponding table i am trying to generate a unique code by prefixing it PRO- and then concatenating a random number generated by using Random class.
I am also trying to get the last item id inserted in the database and add 1 with the id and then add it to the product code as suffix.
My code to fulfill my purpose is
public String generateItemCode() {
String query = "SELECT max(i.id) FROM ITEM i";
List list = sessionFactory.getCurrentSession().createQuery(query).list();
int nextInsertId = ((Integer) list.get(0)).intValue() + 1;
Random random = new Random();
int number = random.nextInt(9999 - 1 + 1) + 1;
return "" + number+nextInsertId;
}
I have also tried by using this line of code in the method body.
int maxId= (Integer)sessionFactory.getCurrentSession().createQuery("select max(id) from items").uniqueResult();
the above code is not working.
How can i get the last inserted id. Is there any simpler way to do it?
Thanks in advance.
It appears your issue may likely be because of your query syntax mimicing that of native SQL when in reality you aren't asking Hibernate to execute a native query but instead a HQL/JPQL query.
If we assume your entity is named Item with an identifier property named id, you would use:
SELECT max(i.id) FROM Item i
You'll notice I use the property name id and the entity name Item (case is important here).
To put the pieces together, the following should work:
// be sure to check for null lastId in case you have no items.
final String sql = "SELECT max( i.id ) FROM Item i";
Integer lastId = (Integer) session.createQuery( sql ).uniqueResult();

How to resolve ORA-01795 in Java code

I am getting ORA-01795 error in my Java code while executing more than 1000 records in IN clause.
I am thinking to break it in the batch of 1000 entries using multiple IN clause separated by OR clause like below:
select * from table_name
where
column_name in (V1,V2,V3,...V1000)
or
column_name in (V1001,V1002,V1003,...V2000)
I have a string id's like -18435,16690,1719,1082,1026,100759... which gets generated dynamically based on user selection. How to write a logic for condition like 1-1000 records ,1001 to 2000 records etc in Java. Can anyone help me here?
There are three potential ways around this limit:
1) As you have already mentioned: split up the statement in batches of 1000
2) Create a derived table using the values and then join them:
with id_list (id) as (
select 'V1' from dual union all
select 'V2' from dual union all
select 'V3' from dual
)
select *
from the_table
where column_name in (select id from id_list);
alternatively you could also join those values - might even be faster:
with id_list (id) as (
select 'V1' from dual union all
select 'V2' from dual union all
select 'V3' from dual
)
select t.*
from the_table t
join id_list l on t.column_name = l.id;
This still generates a really, really huge statement, but doesn't have the limit of 1000 ids. I'm not sure how fast Oracle will parse this though.
3) Insert the values into a (global) temporary table and then use an IN clause (or a JOIN). This is probably going to be the fastest solution.
With so many values I'd avoid both in and or, and the hard-parse penalty of embedded values, in the query if at all possible. You can pass an SQL collection of values and use the table() collection expression as a table you can join your real table to.
This uses a hard-coded array of integers as an example, but you can populate that array from your user input instead. I'm using the built-in collection type definitions, like sys.odcinumberlist, which us a varray of numbers and is limited to 32k values, but you can define your own table type if you prefer or might need to handle more than that.
int[] ids = { -18435,16690,1719,1082,1026,100759 };
ArrayDescriptor aDesc = ArrayDescriptor.createDescriptor("SYS.ODCINUMBERLIST", conn );
oracle.sql.ARRAY ora_ids = new oracle.sql.ARRAY(aDesc, conn, ids);
sql = "select t.* "
+ "from table(?) a "
+ "left join table_name t "
+ "on t.column_name = a.column_value "
+ "order by id";
pStmt = (OraclePreparedStatement) conn.prepareStatement(sql);
pStmt.setArray(1, ora_ids);
rSet = (OracleResultSet) pStmt.executeQuery();
...
Your array can have as many values as you like (well, as many as the collection type you use and your JVM's memory can handle) and isn't subject to the in list's 1000-member limit.
Essentially table(?) ends up looking like a table containing all your values, and this is going to be easier and faster than populating a real or temporary table with all the values and joining to that.
Of course, don't really use t.*, list the columns you need; I'm assuming you used * to simolify the question...
(Here is a more complete example, but for a slightly different scenario.)
I very recently hit this wall myself:
Oracle has an architectural limit of a maximum number of 1000 terms inside an IN()
There are two workarounds:
Refactor the query to become a join
Leave the query as it is, but call it multiple times in a loop, each call using less than 1000 terms
Option 1 depends on the situation. If your list of values comes from a query, you can refactor to a join
Option 2 is also easy, but less performant:
List<String> terms;
for (int i = 0; i <= terms.size() / 1000; i++) {
List<String> next1000 = terms.subList(i * 1000, Math.min((i + 1) * 1000, terms.size());
// build and execute query using next1000 instead of terms
}
In such situations, when I have ids in a List in Java, I use a utility class like this to split the list to partitions and generate the statement from those partitions:
public class ListUtils {
public static <T> List<List<T>> partition(List<T> orig, int size) {
if (orig == null) {
throw new NullPointerException("The list to partition must not be null");
}
if (size < 1) {
throw new IllegalArgumentException("The target partition size must be 1 or greater");
}
int origSize = orig.size();
List<List<T>> result = new ArrayList<>(origSize / size + 1);
for (int i = 0; i < origSize; i += size) {
result.add(orig.subList(i, Math.min(i + size, origSize)));
}
return result;
}
}
Let's say your ids are in a list called ids, you could get sublists of size at most 1000 with:
ListUtils.partition(ids, 1000)
Then you could iterate over the results to construct the final query string.

Trying to pass a Java variable into a sql string

I've had a look around on the web but can't seem to find a definite answer to my question.
Basically, I have a database and table that are successfully working. Now I want to read each line from my table one by one and store the result into a array and I am trying to use a for loop to be more professional rather then using repetition.
I have this code
for (int i=1; i<=8; i++)
{
String query = "Select * FROM Table1 WHERE ID = i";
Rs = St.executeQuery(query);
COL1Title[i] = Rs.getString("CO1Name");
COL2Age[i] = Rs.getString("CO2Rating");
}
The for loop is in a try catch statement and it's complaining with the error "Unknown column 'i' in 'where clause'"
Im guessing there's a certain way for how variable i is to be inserted in the the query.
I should point out ID is a column that has the auto increment feature added on and is primary key if that helps
Could anyone help me out here?
First, we can simplify the task be executing a single query. Note the addition of the range limit and the ORDER BY - without an ORDER BY the results have an unspecified order!
PreparedStatement stmt = "Select ID, CO1Name, CO2Rating"
+ " FROM Table1"
+ " WHERE ID >= ? AND ID <= ?"
+ " ORDER BY ID";
And bind in placeholders (unless there is good reason otherwise, always use placeholders when injecting data into a query). The values could have been hard-coded above in this case, just as they are hard-coded in the for-loop, but the binding is shown here for future reference:
stmt.setInt(1, 1);
stmt.setInt(2, 8);
Then execute the query:
ResultSet rs = stmt.executeQuery();
And iterate the results. Note that rs.next() must be invoke once before any column is read (the cursor starts before any records) and, in this case, it makes it easy to handle a bunch of results.
while (rs.next()) {
int id = rs.getInt("ID");
String title = rs.getString("CO1Name");
String name = rs.getString("CO2Rating");
// do stuff with this record
}
Note that even though the ORDER BY guarantees that the results are iterated in order of ID, assuming a database cardinality rule ensures each result has a unique ID, there may be 0 to 8 records returned - that is, non-existent records may need to be detected/handled separately.
Also (but not shown), make sure to cleanup (close) the ResultSet when done: use a try/finally or try-with-resources construct.
You need to pass i in string as integer, Replace line by:
String query = String.format("Select * FROM Table1 WHERE ID = %d",i);

Using SelectArg in ormlite Having clause?

I need to us a placeholder in the having clause of my query builder.
I tried using a "?" mark but then I get:
java.sql.SQLException: argument holder index 1 not valid, only 1 in statement
This is the thing I try to accomplish:
QueryBuilder<ArchiefTag, Integer> archiefTagQb = helper
.getArchiefTagDao().queryBuilder();
tagSelectArg = new SelectArg();
archiefTagQb.where().in(ArchiefTag.TAG_FIELD, tagSelectArg);
archiefTagQb.groupBy(ArchiefTag.ARCHIEF_ENTRY_FIELD);
QueryBuilder<ArchiefEntry, Date> archiefEntryQb = helper
.getArchiefEntryDao().queryBuilder();
archiefEntryQb.join(archiefTagQb);
//the having must be on the outer join query. Don't know why
archiefEntryQb.having(String.format("COUNT(%s) = ?",ArchiefTag.TAG_FIELD));
preparedGetArchiefForTags = archiefEntryQb.prepare();
The generated sql is:
SELECT `ARCHIEF_ENTRY`.*
FROM `ARCHIEF_ENTRY`
INNER JOIN `ARCHIEF_TAG` ON `ARCHIEF_ENTRY`.`id_entry` = `ARCHIEF_TAG`.`entry`
GROUP BY `ARCHIEF_TAG`.`entry`
HAVING COUNT(tag) = ?
But when I call:
preparedGetArchiefForTags.setArgumentHolderValue(1, 3);
The above Exception occurs.
Any ideas how to solve this?
But when I call: preparedGetArchiefForTags.setArgumentHolderValue(1, 3); The above Exception.
If you take a look at the javadocs for the setArgumentHolderValue(...) method, the index argument should be 0 based and not 1 based like SQL.
To quote:
index - The index of the holder you are going to set, 0 based. See NOTE above.
So your code should be:
preparedGetArchiefForTags.setArgumentHolderValue(0, 3);
I've tweaked the exception to make this more plain.
throw new SQLException("argument holder index " + index + " is not valid, only "
+ argHolders.length + " in statement (index starts at 0)");

What is the best alternative to BatchStatement execute for retriving values from database (MSSQL 2008)

I have a SQL query as shown below.
SELECT O_DEF,O_DATE,O_MOD from OBL_DEFINITVE WHERE OBL_DEFINITVE_ID =?
A collection of Ids is passed to this query and ran as Batch query. This executes for 10000
times for retriveing values from Database.(Some one else mess)
public static Map getOBLDefinitionsAsMap(Collection oblIDs)
throws java.sql.SQLException
{
Map retVal = new HashMap();
if (oblIDs != null && (!oblIDs.isEmpty()))
{
BatchStatementObject stmt = new BatchStatementObject();
stmt.setSql(SELECT O_DEF,O_DATE,O_MOD from OBL_DEFINITVE WHERE OBL_DEFINITVE_ID=?);
stmt.setParameters(
PWMUtils.convertCollectionToSubLists(taskIDs, 1));
stmt.setResultsAsArray(true);
QueryResults rows = stmt.executeBatchSelect();
int rowSize = rows.size();
for (int i = 0; i < rowSize; i++)
{
QueryResults.Row aRow = (QueryResults.Row) rows.getRow(i);
CoblDefinition ctd = new CoblDefinition(aRow);
retVal.put(aRow.getLong(0), ctd);
}
}
return retVal;
Now we had identified that if the query is modified to
add as
SELECT O_DEF,O_DATE,O_MOD from OBL_DEFINITVE WHERE OBL_DEFINITVE_ID in (???)
so that we can reduce it to 1 query.
The problem here is MSSQL server is throwing exception that
Prepared or callable statement has more than 2000 parameter
And were struck here . Can some one provide any better alternative to this
There is a maximum number of allowed parameters, let's call it n. You can do one of the following:
If you have m*n + k parameters, you can create m batches (or m+1 batches, if k is not 0). If you have 10000 parameters and 2000 is the maximum allowed parameters, you will only need 5 batches.
Another solution is to generate the query string in your application and adding your parameters as string. This way you will run your query only once. This is an obvious optimization in speed, but you'll have a query string generated in your application. You would set your where clause like this:
String myWhereClause = "where TaskID = " + taskIDs[0];
for (int i = 1; i < numberOfTaskIDs; i++)
{
myWhereClause += " or TaskID = " + taskIDs[i];
}
It looks like you are using your own wrapper around PreparedStatement and addBatch(). You are clearly reaching a limit of how many statements/parameters can be batched at once. You will need to use executeBatch (eg every 100 or 1000) statements, instead of having it build up until the limit is reached.
Edit: Based on the comment below I reread the problem. The solution: make sure you use less than 2000 parameters when building the query. If necessary, breaking it up in two or more queries as required.

Categories

Resources