Using SelectArg in ormlite Having clause? - java

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)");

Related

how PreparedStatement.setString inside of the loop works?

I have a list of query parameter in my url that I want to use them in my java web service to run a query on a table
public Set<Result> getResult(String query, List<String> sortedQueryParamsValue) {
Connection connection = getConnection();//jdbc connection
//query is some thing like: select * from table A where status = ? and Id = ?
try (PreparedStatement getStatement = connection.prepareStatement(query)) {
for (int i = 0; i <sortedQueryParamKeys.size(); i++) {// sortedQueryParamsValue length is matching the number of values I need for the query and the order matches the order I am expecting
String value = sortedQueryParamsValue.get(i);
getStatement.setString(1, value);
}
try (ResultSet rs = getStatement.executeQuery()) {
while (rs.next()) {
//add to the list of results
}
}
//return the resultset
}
The reason that I used 1 always in getStatement.setString(1, value); is that I thought in each iteration one ? is replaced with the value, but at the end I get some exception back saying java.sql.SQLException: IN or OUT param missing at position 2.
Does anyone knows what I am doing wrong in here?
You can't use 1 every time in getStatement.setString(). You have to increment that number too in order to replace second ? with an actual value.
Since you are only passing 1 all the time and then trying to execute the query, Java is saying that there is no value provided for the second ? when you get java.sql.SQLException: IN or OUT param missing at position 2.
Replacing getStatement.setString(1, value) with getStatement.setString(i+1, value) should do the trick. But you'll have to ensure that the number of elements in sortedQueryParamsValue is equal to the number of ? in your getStatement query.
EDIT: Corrected setString(i,value) to setString(i+1,value) after #Eritrean's comment.
The problem was that passing query parameter through URL to the rest API can have some white spaces and those white spaces causes the query to return empty,
I am going to change 1 to i+1 just to not causing misleading readers

SQL Join on bound variable

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

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.

JPQL count returning 0 when there is a row

I am using two identical JPQL NamedQueries, except that one is a COUNT. Here are the queries:
select i from IssueRingReqsBrit i where i.ringDataRequest = 'I' and i.onRingIssue = 'Y' and i.noCardIssued = 0
select COUNT(i) from IssueRingReqsBrit i where i.ringDataRequest = 'I' and i.onRingIssue = 'Y' and i.noCardIssued = 0
IssueRingReqsBrit is a view.
The first query is returning a list of 6, which is correct.
The second query, the count, return 0.
Databased being used is Oracle. Using Glassfish with Eclipselink. Shared cache mode on the PU is set to none.
Using native queries in Oracle, the correct values are returned.
Below is the code I use to execute the queries, and check the results. There is no other code between these lines, they were copy and pasted as they are in the Java.
query = em.createNamedQuery("IssueRingReqsBrit.onRingIssue_toSend_initial");
System.out.println("Size: " + query.getResultList().size() );
//ringingRequestRingIssueYesToSendInitial
query = em.createNamedQuery("IssueRingReqsBrit.onRingIssue_toSend_initial_count");
ringingRequestRingIssueYesToSendInitial = ((Long)query.getSingleResult()).intValue();
System.out.println("ringingRequestRingIssueYesToSendInitial = " + ringingRequestRingIssueYesToSendInitial);
Any suggestions are appreciated.
Based on the JPQL Language reference, in the COUNT function, the path expression that is the argument to the aggregate function must terminate in a state-field. The path expression argument to COUNT may terminate in either a state-field or a association-field, or the argument to COUNT may be an identification variable. So try something like this
select COUNT(i.<field>) from IssueRingReqsBrit i where i.ringDataRequest = 'I' and i.onRingIssue = 'Y' and i.noCardIssued = 0
COUNT returns Long.
Make use you are returning the resulat in a Long.

Multiple "Select Case" statements into Aggregate Named Query using EclipseLink

I'm writing a simple query in my java code using eclipselink v2.3.
This query must simply return a String and two integers, nothing strange I think, or at least I thought,
The query I'm building is the following:
q = entityManager.createQuery(
"SELECT new com.myclass.CalculationQueryResult(transits.device.name,"
+ " SUM(case when transits.direction = 1 then 1 else 0 end) ,"
+ " SUM(case when transits.direction = 0 then 1 else 0 end)) from Transits_Log transits "
+ " where transits.device.name in :devices and transits.dateTime >= :startDate"
+ " and transits.dateTime < :endDate group by transits.device.name" + " order by transits.device.name",
CalculationQueryResult.class);
While it, obviuosly works in SQL Server (our native counterpart), this does not work in JPQL.
The two different (SUM -> CASE) clauses were strangely (at least for me that i'm quite new to JPA) equals to each other. So, I decided to take out the native SQL from the JPQL to investigate deeper and the problem was there. The generated SQL is this one:
SELECT t0.Name,
**SUM(CASE WHEN (t1.Direction = 1) THEN 1 ELSE 0 END)** ,
**SUM(CASE WHEN (t1.Direction = 1) THEN 1 ELSE 0 END)** FROM dbo.ZZZ t0,
YYYY t1
WHERE ((((t1.DeviceName IN ('XXXXX'))
AND (t1.DateTime >= {ts '2012-09-24 17:26:48.031'}))
AND (t1.DateTime < {ts '2012-09-24 18:26:48.031'}))
AND (t0.Name = t1.DeviceName)) GROUP BY t0.Name
ORDER BY t0.Name ASC
As you can see, the SQL generated statement are wrong on the first two lines 'cause the first SUM and the second one should be one the opposite of the other while they're not.
Am I doing something extremely wrong? Does JPQL support multiple nested CASE and SUM? Are there any way to circumnavigate the error(if is the case) without having to write directly native SQL code?
That is very odd. Are you sure your JPQL is correct and compile/deployed?
Can you try the 2.4 release?
If it still occurs, please log a bug.

Categories

Resources