I am have a problem where i need to join two tables using the LEAST and GREATEST functions, but using JPA CriteriaQuery. Here is the SQL that i am trying to duplicate...
select * from TABLE_A a
inner join TABLE_X x on
(
a.COL_1 = least(x.COL_Y, x.COL_Z)
and
a.COL_2 = greatest(x.COL_Y, x.COL_Z)
);
I have looked at CriteriaBuilder.least(..) and greatest(..), but am having a difficult time trying to understand how to create the Expression<T> to pass to either function.
The simplest way to compare two columns and get the least/greatest value is to use the CASE statement.
In JPQL, the query would look like
select a from EntityA a join a.entityXList x
where a.numValueA=CASE WHEN x.numValueY <= x.numValueZ THEN x.numValueY ELSE x.numValueZ END
and a.numValueB=CASE WHEN x.numValueY >= x.numValueZ THEN x.numValueY ELSE x.numValueZ END
You can code the equivalent using CriteriaBuilder.selectCase() but I've never been a big fan of CriteriaBuilder. If requirements forces you to use CriteriaBuilder then please let me know and I can try to code the equivalent.
CriteriaBuilder least/greatest is meant to get the min/max value of all the entries in one column. Let's say you want to get the Entity that had the alphabetically greatest String name. The code would look like
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery query = cb.createQuery(EntityX.class);
Root<EntityX> root = query.from(EntityX.class);
Subquery<String> maxSubQuery = query.subquery(String.class);
Root<EntityX> fromEntityX = maxSubQuery.from(EntityX.class);
maxSubQuery.select(cb.greatest(fromEntityX.get(EntityX_.nameX)));
query.where(cb.equal(root.get(EntityX_.nameX), maxSubQuery));
I created a sample Spring Data JPA app that demonstrates these JPA examples at
https://github.com/juttayaya/stackoverflow/tree/master/JpaQueryTest
It turns out that CriteriaBuilder does support calling LEAST and GREATEST as non-aggregate functions, and can be accessed by using the CriteriaBuilder.function(..), as shown here:
Predicate greatestPred = cb.equal(pathA.get(TableA_.col2),
cb.function("greatest", String.class,
pathX.get(TableX_.colY), pathX.get(TableX_.colZ)));
Related
How do I implement ordering objects by aggregated nested property?
I have Photographer entity which one has a lot of PhotographerPrice entities (One to Many) with BigDecimal property called pricePerHour. When I retrieving photographers I want to sort them by the minimal price of whole prices they have.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Photographer> cq = cb.createQuery(Photographer.class);
Root<Photographer> root = cq.from(Photographer.class);
List<Predicate> predicates = new ArrayList<>(); // I have a lot of predicates which set if data was present by client
I tried to make a subquery to PhotographerPrice and than sort in root
Subquery<BigDecimal> subquery = cq.subquery(BigDecimal.class);
Root<PhotographerPrice> from = subquery.from(PhotographerPrice.class);
Predicate and = cb.and(
cb.equal(root.get(Photographer_.id), from.get(PhotographerPrice_.photographer).get(Photographer_.id)),
cb.isNotNull(from.get(PhotographerPrice_.pricePerHour))
);
subquery.correlate(root);
subquery.where(and);
subquery.select(cb.min(from.get(PhotographerPrice_.pricePerHour)));
subquery.groupBy(from.get(PhotographerPrice_.photographer).get(Photographer_.id));
...
cq.orderBy(cb.asc(subquery));
But, as I realized, it's not allowed to use a subquery in order by clause.
So, how do I can implement something like this using Criteria API:
select *
from photographer p
order by (
select min(price_per_hour) minPrice
from photographer_price pp
where p.id = pp.photographer_id
and pp.photo_per_hour is not null
group by photographer_id
);
When I tried to implement it with Join approach I've got duplicates in my result list.
Is it possible to implement it using Criteria API? Maybe there is another tool to make filtering for entities from DB more convenient? I have a lot of different parameters for filtering and sorting which related to nested properties, sometimes even related to nested in a nested property.
The only way I found to solve it:
ListJoin<Photographer, PhotographerPrice> join = root.join(Photographer_.photographerPrices);
Expression<BigDecimal> min = cb.min(join.get(PhotographerPrice_.pricePerHour));
cq.orderBy(cb.desc(min));
cq.groupBy(root.get(Photographer_.id));
But I don't sure about group by. Is it possible some troubleshooting appear later?
The approach I found that works in my case
Do left join to PhotographerPrice with Min aggregate function after that make an order based by result of this aggregate:
ListJoin<Photographer, PhotographerPrice> photographerPriceJoin = root.join(Photographer_.photographerPrices);
Expression<BigDecimal> min = cb.min(photographerPriceJoin.get(PhotographerPrice_.pricePerHour));
if (photographerCatalogFilter.getDirection().isDescending()) {
orderList.add(cb.desc(min));
} else {
orderList.add(cb.asc(min));
}
Recently, I am trying use spring-data-elasticsearch in my project and met lots of problems. I asked a question about not operation yesterday but I solved by myself. And now I met another problem when trying to use or operator.
Here is what I want:
I want to query an object with a code that is not "11" and its symbol is "22" or its subSymbol is "33". I have tried many times in many ways and failed.It seems that I have no way to make it work with CriteriaQuery. I don't know if I described my question clearly. And in SQL, it should be written like this,
select from x where x.code!='11' and (x.symbol='22' or x.subSymbol='33')
But with CriteriaQuery, I always get the results if symbol/subSymbol value is matched and code condition seems missing, its SQL works like below:
select from x where x.code!='11' or x.symbol='22' or x.subSymbol='33'
Here is what I tried:
1.
CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria()
.and("code").is("11").not()
.or("symbol").is("22").or("subSymbol").is("33"));
2.
CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria()
.and("code").is("11").not()
.or(new Criteria("symbol").is("22").and("subSymbol").is("33")));
3.
CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria()
.and("code").is("11").not()
.and(new Criteria("symbol").is("22").or("subSymbol").is("33")));
4.
CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria()
.and("code").is("11").not())
.addCriteria(new Criteria("symbol").is("22").or("subSymbol").is("33"));
List<xx> sampleEntities =
elasticsearchTemplate.queryForList(criteriaQuery,xx.class);
All of the above failed.
So any solution to deal with my problem? Or I need to try NativeSearchQueryBuilder maybe? It is so frustrating.
I think you need to index your properties as Terms, because you use them as some codes(nomenclatures). You can keep the existing indexing as text if you need it in future. So I found this tutorial which looks easy to implement for your case.
https://www.baeldung.com/spring-data-elasticsearch-queries
So if you add #InnerField on your fields and use query 2. but use verbatim suffix on fields it should work with query number 2 from your list. Your fields will be like code.verbatim, etc...
Also you can think of moving to NativeSearchQueryBuilder.
I'm trying to create a query using CriteriaBuilder where I need to have a predicate where the value of the predicate is like the value in the database.
Basically, I need to be able to do the following:
WHERE myTestValue LIKE columnValue
In native queries, it is an option to do that, but using the CriteriaBuilder or NamedQueries, it does not seem to work.
String myValue = "foo#bar.com";
cb.where(cb.like(myValue, root.get(Entity_.email));
Is there an option in JPA to do it like this? Or should I fall back to native queries?
EDIT
I need to be able to check if a given value matches a wildcard entry in database. So the database has a record %#bar.com%, and I need to check if my given value foo#bar.com matches to that record.
I think your params should be other way round:
cb.where(cb.like(root.get(Entity_.email),myValue);
Aditionally you may need to use add this to the second param:
cb.where(cb.like(root.get(Entity_.email),"%"+myValue+"%");
Chris found the answer. First I need to "generate" a parameter.
ParameterExpression<String> senderEmailParameter = cb.parameter(String.class, "senderEmailParameter");
Path<String> senderEmailPath = root.get(Entity_.senderEmail);
Predicate predEmail = cb.like(senderEmailParameter, senderEmailPath);
And then I need to fill the parameter in the query.
q.where(predEmail);
return em.createQuery(q).setParameter("senderEmailParameter", senderEmail).getSingleResult();
This works! Thanks Chris!
I would like to create a query that returns all roomIds of a Entity Client that are stored in the database. roomId is an attribute in the Client entity.
I would like to use a type safe construct like this:
TypedQuery<Set<Long>> q = em.createNamedQuery("getRoomIdsByServer", Set.class);
q.setAttribute("server",server);
however this will create a compilation error.
The NamedQuery might look something like this:
#NamedQuery(name = "getRoomIdsByServer", query = "SELECT c.room_id FROM Client c WHERE c.server = :server GROUP BY c.room_id")
However this seems to be beyond the scope of JPQL.
We are using openJPA (version 2.2.1)
There is one solution to solve this problem without using a typed query, see https://stackoverflow.com/a/6956037/1448704
But I would like to use a typed query to prevent those nasty cast operations.
There is no need to cast <long> in your TypedQuery.See this;
TypedQuery<Set> q = em.createNamedQuery("getRoomIdsByServer", Set.class);
q.setAttribute("server",server);
or you can use like this;
public List<String> getAllRoomIds(String server) {
return em.createNamedQuery("getRoomIdsByServer", Set.class).setParameter("server", server).getResultList();
}
Suppose, we have an entity User, which has many comments.
It is possible to do so:
List<Comment> = user.getComments();
But this will load all the comments of the user.
How should we retrieve just first 10 for example?
Is the anything similar to:
List<Comment> = user.getComments().setOffset(0).stLimit(10).getResultList();
?
You should limit this in the query using LIMIT and not in the code.
Eg:
SELECT comment.id, comment.name FROM comment WHERE comment.name =:username
ORDER BY comment.id DESC LIMIT 10;
OR you can use setMaxResults method from jpa: documentation here
Eg:
Query query=em.createQuery("SELECT st FROM Student st WHERE st.sroll > ?param");
query.setParameter(param, 100);
query.setMaxResults(3);
List stuList=query.getResultList();
The standard Java way to do it (and I'm pretty sure that JPA providers have the functionality to back that up) is:
List<Comment> = user.getComments().subList(0,10);
Reference
List.subList(from, to)
Or you can use the extremely verbose JPA 2 CriteriaQuery API:
CriteriaQuery<Comment> cQuery =
entityManager.getCriteriaBuilder()
.createQuery(Comment.class);
cQuery.select(cQuery.from(Customer.class)
.get(Customer_.comments));
List<Comment> comments =
entityManager.createQuery(cQuery)
.setFirstResult(0)
.setMaxResults(0)
.getResultList();