I've read the other posts and the docs about how to use the "Where" clause to "create" parenthesis statements.
My requirement is simple:
... WHERE companyID=1 AND (director=true OR officer=true) ;
I'm writing a routine that takes an array of Object, which are then parsed into an Ormlite Where call. a typical call looks like this:
.., "companyID", 1, Q.AND, Q.Bracket, "director", true, Q.OR, "officer", true, Q.Bracket)
The intent is to speed up simple queries. There is no desire to replace Ormlite's querying tools. This is a simple meta-layer on top.
Everything works fine for simple queries, since the parameters are processed sequentially and the where clause is built incrementally.
For parenthesis I am postponing the processing until the bracket is closed.
This is where I am having a problem. The example from the docs I am using shows this:
-- From the OrmLite docs...
Where<Account, String> where = queryBuilder.where();
where.or(
where.and(
where.eq(Account.NAME_FIELD_NAME, "foo"),
where.eq(Account.PASSWORD_FIELD_NAME, "_secret")),
where.and(
where.eq(Account.NAME_FIELD_NAME, "bar"),
where.eq(Account.PASSWORD_FIELD_NAME, "qwerty")));
This produces the following approximate SQL:
SELECT * FROM account
WHERE ((name = 'foo' AND password = '_secret')
OR (name = 'bar' AND password = 'qwerty'))
The key thing I understand from the docs example, is that the same where instance is used in the nested and(...) call. This is precisely what I'm doing but I'm still getting a "Did you forget an AND or an OR" message.
The code implementing the "delayed" processing looks like this:
#SuppressWarnings("unchecked")
private void processWhere(Where<?, ?> where, Q q, List<QValue> list)
{
if (null == list || list.size() < 2)
{
System.err.println("Invalid where passed: " + list);
return;
}
if (q.equals(Q.AND))
where.and(getCondition(where, list.get(0)), getCondition(where, list.get(1)));
else
where.or(getCondition(where, list.get(0)), getCondition(where, list.get(1)));
}
The "QValue" item is just a "holder" for column, condition and value data.
The "getCondition" method is as follows:
#SuppressWarnings("rawtypes")
protected Where getCondition(Where<?, ?> where, QValue qv)
{
if (null != where && null != qv)
return getCondition(where, qv.getType(), qv.getColumn(), qv.getValue(), qv.getValue2());
else
return null;
}
#SuppressWarnings("rawtypes")
protected Where getCondition(Where<?, ?> where, Q cond, String key, Object val, Object val2)
{
if (null == where || null == cond || null == key || null == val)
return null;
SelectArg arg = new SelectArg();
arg.setValue(val);
try
{
switch (cond)
{
case NotNull:
where.isNotNull(key);
break;
case IsNull:
where.isNull(key);
break;
case Equals:
where.eq(key, arg);
break;
case NotEqual:
where.ne(key, arg);
break;
case GreaterThan:
where.gt(key, arg);
break;
case LessThan:
where.lt(key, arg);
break;
case Like:
arg.setValue("%" + val + "%");
where.like(key, arg);
break;
case LikeStart:
arg.setValue("" + val + "%");
where.like(key, arg);
break;
case LikeEnd:
arg.setValue("%" + val);
where.like(key, arg);
break;
case Between:
if (null != val && null != val2)
where.between(key, val, val2);
break;
default:
where.eq(key, arg);
break;
}
}
catch (SQLException e)
{
GlobalConfig.log(e, true);
return null;
}
return where;
}
As far as I can tell, I'm using the Where object correctly, but I am still getting a:
"Did you forget an AND or OR?" message.
I've tried creating "new" Where clauses with QueryBuilder:
Where w1 = qb.where() ;
//process w1 conditions...
return where.query() ;
Which also fails or generates incorrect SQL in the various combinations I've tried. Any suggestions on how to get the and(...) and or(...) methods working properly will be greatly appreciated.
BTW once the library is working properly, I'll put it up as Open Source or donate it to Gray, or both.
Thanks in advance.
Anthony
I faced the same issue and solved it like this:
where.eq("companyID", 1);
where.and(where, where.or(where.eq("director", true), where.eq("officer", true)));
or
where.and(where.eq("companyID", 1), where.or(where.eq("director", true), where.eq("officer", true)));
which in SQL gives us:
((`companyID` = 1 AND `director` = 1 ) OR (`companyID` = 1 AND `officer` = 1 ))
It's not identical to your example clause WHERE companyID=1 AND (director=true OR officer=true) but has the same meaning.
Related
I receive a list of models. The number of models could be large. This models has a bunch of properties and any of them could be null potentially.
I need to build a string for every model based of it's properties. If property == null then I add some static part to the result string like "property1 is null".
If else property != null then I add something like this "property1 == 'valueOfThePropertyHere'".
The result string should look something like this:
prop1 == 'value1' and prop2 is null and prop3 == 'value3' and prop4 == 'value4' and prop5 is null and ..... propN == 'valueN'
And I generate such string for every model from the list.
Obviously I do this in for loop and I use StringBuilder for this. The thing is that in append method of StringBuilder I check every field of the model for null using ternary operator and based on this I add the result of this check to the result string. But if a property is not null then I need to add some static part + value of the field itself + some more static stuff. And that means I need to add one more StringBuilder for every property I have. Or I can use '+' which will be transformed into StringBuilder anyway and as far as I know it's a bad practise to use '+' inside StringBuilder (but I have to use it anyway).
Example:
List<Model> models = repository.getModels();
for (Model m: models) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder
.append(m.getField1() == null ? "field1 is null" : "field1 == '" + new StringBuiler().append(m.getField1()).append("'").append(" and ").toString()))
.append(m.getField2() == null ? "field2 is null" : "field2 == '" + new StringBuiler().append(m.getField2()).append("'").append(" and ").toString()))
...............
.append(m.getFieldN() == null ? "fieldN is null" : "fieldN == '" + new StringBuiler().append(m.getFieldN()).append("'").append(" and ").toString()));
System.out.println(stringBuilder.toString());
}
In my opinion from the performance perspective it doesn't look so well because for every model from a list of models I create another bunch of StringBuilder objects in heap just to get the result string.
Am I missing something? Are there better ways to do so from the performance perspective? Or it's okay because I don't see other options for now.
Go for simple.
Instead of
stringBuilder
.append(m.getField1() == null ? "field1 is null" : "field1 == '" + new StringBuiler().append(m.getField1()).append("'").append(" and ").toString()))
use:
if (m.getField1() == null) {
stringBuilder.append("field1 is null");
} else {
stringBuilder.append("field1 == '").append(m.getField1()).append("'").append(" and ");
}
Aside from the distinct oddness of using a StringBuilder inside a StringBuilder.append call (and why not just use + anyway...), it's really hard to parse where the : is in the conditional expression. Breaking it into lines is much easier.
If you find yourself having to repeat this code pattern a lot, define a method:
void append(StringBuilder stringBuilder, String name, Object value) {
stringBuilder.append(name);
if (value == null) {
stringBuilder.append(" is null");
} else {
stringBuilder.append(" == '").append(value).append("'").append(" and ");
}
}
and then invoke like:
append(stringBuilder, "field1", m.getField1());
append(stringBuilder, "field2", m.getField2());
append(stringBuilder, "field3", m.getField3());
What a mess! Just because you can chain invocations, doesn't mean you should:
List<Model> models = repository.getModels();
for (Model m: models) {
StringBuilder stringBuilder = new StringBuilder();
String field = m.getField1();
if(field==null) {
stringBuilder.append("field1 is null");
} else {
stringBuilder.append("field1 == ").append(m.getField1()).append("'");
}
if(stringBuilder.length()>0) {
stringBuilder.append(" and ");
}
field = m.getField2();
if(field==null) {
stringBuilder.append("field2 is null");
} else {
stringBuilder.append("field2 == ").append(m.getField1()).append("'");
}
if(stringBuilder.length()>0) {
stringBuilder.append(" and ");
}
...
System.out.println(stringBuilder.toString());
}
To avoid all this potential repetition (depending on number of fields):
void appendField(StringBuilder stringBuilder, String fieldName, String value) {
if(stringBuilder.length()>0) {
stringBuilder.append(" and ");
}
stringBuilder.append(fieldName);
if(value==null) {
stringBuilder.append(" is null");
} else {
stringBuilder.append(" == '").append(value).append("'");
}
}
String toString(Model m) {
StringBuilder stringBuilder = new StringBuilder();
appendField(stringBuilder, "field1", m.getField1());
appendField(stringBuilder, "field2", m.getField2());
...
appendField(stringBuilder, "fieldN", m.getFieldN());
return stringBuilder.toString();
}
List<Model> models = repository.getModels();
for (Model m: models) {
System.out.println(toString(m));
}
I'm currently working on a fetaure that will allow the system to search public services receipts by the combination of 6 parameters which can be null meaning that receipts shouldn't be filtered by this parameter: accountNumber, amountRangeMin, amountRangeMax, dateRangeMin, dateRangeMax, publicServiceId. However making a method for each combination of the parameters is not an option, I'm thinking that there must be a better way, at first my approach was as following:
On my Service I have this method:
public Map<String,Object> findPublicServiceReceiptsByParams(Integer accountNumber, BigDecimal amountRangeMin,
BigDecimal amountRangeMax, LocalDate dateRangeMin, LocalDate dateRangeMax, Integer publicServiceId) {
Map<String,Object> publicServiceReceipts = new HashMap<String,Object>();
String accountNumberFilter = !(accountNumber==null) ? accountNumber.toString() : "AccountNumberTableName";
String amountRangeMinFilter = !(amountRangeMin==null) ? amountRangeMin.toString() : "table.AmountColumnName";
String amountRangeMaxFilter = !(amountRangeMax==null) ? amountRangeMax.toString() : "table.AmountColumnName";
String dateRangeMinFilter = !(dateRangeMin==null) ? dateRangeMin.toString() : "Table.ReceiptCreationDateColumn";
String dateRangeMaxFilter = !(dateRangeMax==null) ? dateRangeMax.toString() : "Table.ReceiptCreationDateColumn";
String publicServiceIdFilter = !(publicServiceId==null) ? publicServiceId.toString() : "table.publicServiceIdColumn";
publicServiceReceipts = publicServiceReceiptRepository.findPublicServiceReceiptsByParams(accountNumberFilter,
amountRangeMinFilter, amountRangeMaxFilter, dateRangeMinFilter, dateRangeMaxFilter,
publicServiceIdFilter);
return publicServiceReceipts;
}
And then in my repository I had:
final static String FIND_PUBLIC_SERVICES_BY_ARGS = "Select (Insert whatever logic should go in here to select columns from receipts the where clause is the one that matters)"
+ " WHERE ACT.ACT_AccountNumber=:accountNumberFilter\n"
+ " AND PSE.PSE_Id=:publicServiceIdFilter\n"
+ " AND PSR.PSR_CreateDate BETWEEN :dateRangeMinFilter AND :dateRangeMaxFilter\n"
+ " AND PSR.PSR_Amount BETWEEN :amountRangeMinFilter AND :amountRangeMaxFilter\n"
+ " order by PSR.PSR_CreateDate desc";
#Query(nativeQuery = true, value = FIND_PUBLIC_SERVICES_BY_ARGS)
Map<String, Object> findPublicServiceReceiptsByParams(#Param("accountNumberFilter") String accountNumberFilter,
#Param("amountRangeMinFilter") String amountRangeMinFilter,
#Param("amountRangeMaxFilter") String amountRangeMaxFilter,
#Param("dateRangeMinFilter") String dateRangeMinFilter,
#Param("dateRangeMaxFilter") String dateRangeMaxFilter,
#Param("publicServiceIdFilter") String publicServiceIdFilter);
}
My reasoning was that if a parameter was null meant that whoever consumed the Web Service is not interested in that paramater so if that happens I set that variable as the Column Name so that it wouldn't affect in the WHERE clause and in theory make it simpler, but what I found was that It would send the names as Strings so it wouldn't be recognized as an sql statement which was the flaw in my thinking and as I said there must be another way other than writing each method for each combination, I appreciate any help :).
You should use the Criteria API, which was designed for creating dynamic queries. Named queries aren't really meant to be used in this case.
With it you can do something like this:
#PersistenceContext
EntityManager em;
List<YourEntity> method(String argument) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<YourEntity> cq = cb.createQuery(YourEntity.class);
Root<YourEntity> root = cq.from(YourEntity.class);
List<Predicate> predicates = new ArrayList<>();
if (argument == null) {
predicates.add(cb.equal(root.get("yourAttribute"), argument);
}
// rest of your logic goes here
cq.where(predicates.toArray(new Predicate[]{}));
return em.createQuery(cq).getResultList();
}
I found a way to fix this, I did it like this (I'm going to show only the native Query since it's the inly thing that i changed):
DECLARE #actNum varchar(50),#crdNum varchar(50),#pseId varchar(50),#dateMin varchar(50),#dateMax varchar(50),#amountMin varchar(50),#amountMax varchar(50)
SET #actNum = :actNum
SET #crdNum = :crdNum
SET #pseId = :pseId
SET #dateMin = :dateMin
SET #dateMax = :dateMax
SET #amountMin = :amountMin
SET #amountMax = :amountMax
--Whatever Select with joins statement
WHERE ACT.ACT_AccountNumber = CASE WHEN #actNum = 'N/A'
THEN ACT.ACT_AccountNumber
ELSE #actNum END
AND CRD_CardNumber = CASE WHEN #crdNum = 'N/A'
THEN CRD_CardNumber
ELSE #crdNum END
AND PSE.PSE_Id= CASE WHEN #pseId = 'N/A'
THEN PSE.PSE_Id
ELSE #pseId END
AND PSR.PSR_CreateDate >= CASE WHEN #dateMin = 'N/A'
THEN PSR.PSR_CreateDate
ELSE #dateMin END
AND PSR.PSR_CreateDate <= CASE WHEN #dateMax = 'N/A'
THEN PSR.PSR_CreateDate
ELSE #dateMax END
AND PSR.PSR_Amount BETWEEN CASE WHEN #amountMin = 'N/A'
THEN PSR.PSR_Amount
ELSE #amountMin END
AND CASE WHEN #amountMax = 'N/A'
THEN PSR.PSR_Amount
ELSE #amountMax END
ORDER BY PSR.PSR_CreateDate DESC
The backend will send the parameters as either "N/A" (if it shouldn't be used to filter data) or the actual value, this worked fine for me!
I'm trying to implement some kind of data filtering opportunity for my Java web Application.
In my case user should be able to filter clients who - for example where born after 2 December and have clientSate ACTIVE or INCATIVE.
In my case ACTIVE and INACTIVE are enum values.
Since user can choose 2 or 3 or even 5 different statuses I'm doing it like this:
for (EnumValue enumValue : constraint.getValues().getEnumValue()) {
ClientState state = ClientState.valueOf(enumValue.getValue());
predicate = builder.or(builder.equal(client.get(constraint.getField().getValue()), state));
}
but it doesn't work.
Here is my full function code:
public List<Client> getClients(List<FilterConstraint> filters) {
try {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<?> mainQuery = builder.createQuery(Client.class);
Root<Client> client = mainQuery.from(Client.class);
Predicate predicate = builder.conjunction();
for (FilterConstraint constraint : filters) {
switch (constraint.getOperator()) {
case AFTER:
predicate = builder.and(builder.greaterThan(client.get(constraint.getField().getValue()), constraint.getValues().getStartDate()));
break;
case BEFORE:
predicate = builder.and(builder.greaterThan(client.get(constraint.getField().getValue()), constraint.getValues().getStartDate()));
break;
case BETWEEN:
if (constraint.getField().getType() == FieldDataType.DATE) {
predicate = builder.and(builder.between(client.get(constraint.getField().getValue()), constraint.getValues().getStartDate(), constraint.getValues().getEndDate()));
} else {
predicate = builder.and(builder.between(client.get(constraint.getField().getValue()), constraint.getValues().getMinValue(), constraint.getValues().getMaxValue()));
}
break;
case EMPTY:
predicate = builder.and(builder.isEmpty(client.get(constraint.getField().getValue())));
break;
case EQUALS:
if (constraint.getField().getType() == FieldDataType.ENUM) {
if (constraint.getValues().getEnumValue().size() > 1) {
for (EnumValue enumValue : constraint.getValues().getEnumValue()) {
ClientState state = ClientState.valueOf(enumValue.getValue());
predicate = builder.or(builder.equal(client.get(constraint.getField().getValue()), state));
}
break;
}
ClientState state = ClientState.valueOf(constraint.getValues().getEnumValue().get(0).getValue());
predicate = builder.or(builder.equal(client.get(constraint.getField().getValue()), state));
break;
}
predicate = builder.and(builder.equal(client.get(constraint.getField().getValue()), constraint.getValues().getValue()));
break;
case LESS_THAN:
case MORE_THAN:
case NOT_EMPTY:
case ON:
case STARTS_WITH:
case TODAY:
}
}
CriteriaQuery<Long> cq = builder.createQuery(Long.class);
cq.select(builder.count(cq.from(Client.class)));
em.createQuery(cq);
cq.where(predicate);
Long count = em.createQuery(cq).getSingleResult();
mainQuery.where(predicate);
//TODO Pagination Result should be returned
TypedQuery<?> q = em.createQuery(mainQuery);
List<Client> allClients = (List<Client>) q.getResultList();
return allClients;
} catch (Exception e) {
System.out.println(e.getMessage());
return null;
}
}
I have searched a lot and wasn't able to fund the example where was no exact value for OR operator - actually I found comparing 2 values or 3 - not more.
Can someone tell me how can I fix my code to be able to support any number of values in OR expression ?
CriteriaBuilder.or accepts an array of predicates. So just create an array of "equals enum value" predicates and call or of that array once.
Alternatively, you may want to consider in <enum values> instead.
lexicore is right. You can give it an array like this:
if (constraint.getValues().getEnumValue().size() > 1) {
List<Predicate> predicates = new ArrayList<>();
for (EnumValue enumValue : constraint.getValues().getEnumValue()) {
ClientState state = ClientState.valueOf(enumValue.getValue()); predicates.add(builder.equal(client.get(constraint.getField().getValue()), state));
}
predicate = builder.or(predicates.toArray(new Predicate[]{}));
break;
}
This code should work for you
I want to cycle through an ArrayList<String> named fileContent, using the Iterator interface, and I want it to be analyzed one string at a time which should result in a specific state by the switch statement, depending on what string it is.
Object token is supposed to be read as 'Table'.
void process() {
Iterator fileIterator = fileContent.iterator();
int state = 0;
Object token = null;
switch (state) {
case 0:
token = fileIterator.next();
if (token.equals("Table")) {
System.out.println(token);
state = 1;
} else {
System.err.println("Corrupt file format at state 0: "+ token);
System.exit(0);
}
break;
}
}
This doesn't switch state to 1, instead it prints out:
'Corrupt file format at state 0: Table'
So it seems as it reads the Object token correctly as 'Table' but not for the statement 'if (token.equals("Table"))'.
I have also tried 'if (token == ("Table"))'
Can somebody help me?
Looks like you want to equate Strings, try this
void process() {
Iterator fileIterator = fileContent.iterator();
int state = 0;
String token = ""; // notice this
switch (state) {
case 0:
token = ((String)fileIterator.next()).trim(); // notice this
if (token.equals("Table")) {
System.out.println(token);
state = 1;
} else {
System.err.println("Corrupt file format at state 0: "+ token);
System.exit(0);
}
break;
}
}
What makes you think "it reads the Object token correctly as 'Table'"? When the if() fails, then token must be something unlike Table. My guess is that the output is misleading. Try this instead:
System.err.println("Corrupt file format at state 0: ["+ token + "]");
The square brackets around the token will help you see unexpected whitespace characters.
Also note that token might not be a string but return Table when toString() is called on it (which will happen if you append it to a string).
I'm using JPA, hibernate 3.
String sqlQuery = " FROM TraceEntityVO where lotNumber =:lotNumber and mfrLocId=:mfrLocId and mfrDate=:mfrDate and qtyInitial=:qtyInitial and expDate=:expDate";
Query query = entityManager.createQuery(sqlQuery)
.setParameter("lotNumber", traceEntityVO.getLotNumber())
.setParameter("mfrLocId", traceEntityVO.getMfrLocId())
.setParameter("mfrDate", traceEntityVO.getMfrDate())
.setParameter("qtyInitial", traceEntityVO.getQtyInitial())
.setParameter("expDate", traceEntityVO.getExpDate());
This query works like a charm when the there were no empty or null values. But there could be possible of null or empty value for traceEntityVO.getLotNumber(),traceEntityVO.getMfrLocId(),traceEntityVO.getExpDate().
In this case the value 'null' or '' is checked against the variable instead of is null condition. How do I handle when I'm not sure about the parameter value, either null or empty?
I don't want to construct the query dynamically based on the values if empty or null.
Is this possible?
Thanks in advance..
I think you really can't do that without a dynamic query.
Building such a query however is easy with the criteria API (hibernate) (JPA), did you consider it?
I hope following code will sort your problem. Assuming getMfrDate and getExpDate will return Date Object and others either Number or String objects. But you can modify IsEmpty according to return types.
String sqlQuery = " FROM TraceEntityVO where lotNumber :lotNumber
and mfrLocId :mfrLocId and mfrDate :mfrDate and qtyInitial :qtyInitial and
expDate :expDate";
Query query = entityManager.createQuery(sqlQuery)
.setParameter("lotNumber", isEmpty(traceEntityVO.getLotNumber()))
.setParameter("mfrLocId", isEmpty(traceEntityVO.getMfrLocId()))
.setParameter("mfrDate", isEmpty(traceEntityVO.getMfrDate()))
.setParameter("qtyInitial", isEmpty(traceEntityVO.getQtyInitial()))
.setParameter("expDate", isEmpty(traceEntityVO.getExpDate()));
private String isEmpty(Object obj) {
if(obj!=null) {
if (obj instanceof java.util.Date) {
return " = to_date('"+obj.toString()+"') ";
} else if(obj instanceof String) {
return " = '"+obj.toString()+"' ";
} else if (obj instanceof Integer) {
return " = "+obj.toString()+" ";
}
}
return new String(" is null ");
}