I'm trying to write a CriteriaQuery which will query latest observation for each city. City is defined by city_code field, while latest record is defined by observation_time field.
I can easily write it in a plain SQL, but I cant understand how to do it with jpa criteria api.
select distinct m.* from
(select city_code cc, max(observation_time) mo
from observations group by city_code) mx, observations m
where m.city_code = mx.cc and m.observation_time = mx.mo`
It is possible when You are open for loose efficiency.
So first let's transform our query to logical equivalent one:
select distinct m.* from observations m where
m.observation_time = (select max(inn. observation_time) from observations inn
where inn.city_code = m.city_code);
then let's translate it to JPA CriteriaQuery:
public List<Observation> maxForEveryWithSubquery() {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Observation> query = builder.createQuery(Observation.class);
Root<Observation> observation = query.from(Observation.class);
query.select(observation);
Subquery<LocalDateTime> subQuery = query.subquery(LocalDateTime.class);
Root<Observation> observationInner = subQuery.from(Observation.class);
subQuery.where(
builder.equal(
observation.get(Observation_.cityCode),
observationInner.get(Observation_.cityCode)
)
);
Subquery<LocalDateTime> subSelect = subQuery.select(builder.greatest(observationInner.get(Observation_.observationTime)));
query.where(
builder.equal(subSelect.getSelection(), observation.get(Observation_.observationTime))
);
TypedQuery<Observation> typedQuery = entityManager.createQuery(query);
return typedQuery.getResultList();
}
Unfortunately JPA does not support sub queries in FROM clause. You need to write a native query or use framework like FluentJPA.
Related
I have the below piece of the code to get count query form the original query.
But this is the line causing the issue at compile time.
countQuery.from(sqmSubQuery);
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Long> countQuery = builder.createQuery(Long.class);
SqmSubQuery sqmSubQuery = (SqmSubQuery<Tuple>) countQuery.subquery(Tuple.class);
SqmSelectStatement sqmOriginalQuery = (SqmSelectStatement) query;
SqmQuerySpec sqmOriginalQuerySpec = sqmOriginalQuery.getQuerySpec();
sqmSubQuery.setQueryPart(sqmOriginalQuerySpec.copy(SqmCopyContext.simpleContext()));
Root<T> subQuerySelectRoot = (Root<T>) sqmSubQuery.getRoots().iterator().next();
sqmSubQuery.multiselect(subQuerySelectRoot.get("id").alias("id"));
countQuery.select(builder.count(builder.literal(1)));
countQuery.from(sqmSubQuery);
Based on you comment you want to select the distinct count of all employee types. The query you provided should be equivalent to SELECT COUNT(DISTINCT employee_type) FROM Employee.
This can be written in JPA as shown below:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> countQuery = builder.createQuery(Long.class);
Root<Employee> employeeRoot = countQuery.from(Employee.class);
countQuery.select(builder.countDistinct(employeeRoot.get("type")));
Long count = entityManager.createQuery(countQuery).getSingleResult();
where type is the name of the property that maps to the column employee_type
The type org.hibernate.query.criteria.JpaSelectCriteria declares this method:
<X> JpaDerivedRoot<X> from(jakarta.persistence.criteria.Subquery<X> subquery);
which is the one you need to call if you're trying to use a subquery in the from clause.
And SqmSelectStatement implements JpaSelectCriteria. (It is also the object which implements jakarta.persistence.criteria.CriteriaQuery.)
So you can cast any CriteriaQuery to JpaSelectCriteria and call from():
CriteriaQuery<Thing> query = ... ;
Subquery<OtherThing> subquery = ... ;
((JpaSelectCriteria<Thing>) query).from(subquery);
or whatever (I did not test this code).
SQL (that works in database):
SELECT ffm.REPORTING_PERIOD, count(DISTINCT ffm.FI_MESSAGE_ID), count(ffa.FI_ACCOUNT_ID) FROM RAZV.FINTAEOI2_FI_MESSAGE ffm
JOIN razv.FINTAEOI2_FI_MESSAGE_FI ffmf ON ffm.FI_MESSAGE_ID = ffmf.FI_MESSAGE_ID
JOIN razv.FINTAEOI2_FI_ACCOUNT ffa ON ffmf.FI_MESSAGE_FI_ID = ffa.FI_MESSAGE_FI_ID
WHERE ffm.REPORTING_PERIOD = '2020-12-31'
GROUP BY ffm.REPORTING_PERIOD
There will be 6 possible WHERE statements so I decided to use TypedQuery to simplify conditions appending.
Problem is; How can I include count into TypedQuery select and is it even possible? If not, what would be the best way except making multiple queries? If only way is multiple queries, how do I fill the dto?
This is my query where I should include count.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<ReportListDto> cq = cb.createQuery(ReportListDto.class);
Root<FiMessage> fromFIMessage = cq.from(FiMessage.class);
Join<FiMessage, FiMessageFi> fiMessageFi = fromFIMessage.join("fintaeoi2FiMessageFis", JoinType.INNER);
Join<FiMessageFi, FiAccount> fiAccount = fiMessageFi.join("fintaeoi2FiAccounts", JoinType.INNER);
List<Predicate> conditions = new ArrayList<Predicate>();
`**TypedQuery<ReportListDto> query = entityManager.createQuery(
cq.multiselect(
fromFIMessage.get("reportingPeriod"),
fromFIMessage.get("fiMessageId"),
fiAccount.get("fiAccountId"))
.where(conditions.toArray(new Predicate[] {})));**
List<ReportListDto> result = query.getResultList();
`
result of above is
[results without count included](https://i.stack.imgur.com/MO7Zk.png)
but i need
[result ash it should be with applying count in typedquery](https://i.stack.imgur.com/gqnC9.png)
So reporting period is fine and I need distinct count for messageId and count for accountId.
Thank you for your answers.
The answer is
TypedQuery<ReportListDto> query = entityManager.createQuery(cq.multiselect(fromFIMessage.get("reportingPeriod"), cb.countDistinct(fromFIMessage.get("fiMessageId")), cb.count(fiAccount.get("fiAccountId")))
.where(conditions.toArray(new Predicate[] {})));
I try do the folow JPQL clause to criteria Api
SELECT new ProductDTOOut(p.id,p.name,
(SELECT pr.price FROM Price pr WHERE pr.product.id = p.id and pr.company.id = :companyId) )
FROM Product p
without success,
how can I do it ?
tnx advanced
I know solve my problem follow answer :
CriteriaBuilder cb = manager.getCriteriaBuilder();
CriteriaQuery<ProductDTOOut> criteriaQuery = cb.createQuery(ProductDTOOut.class);
// create query
Root<Product> rootFrom = criteriaQuery.from(Product.class);
// creating subquery
Subquery<Double> subquery = criteriaQuery.subquery(Double.class);
Root<Price> subqueryRoot = subquery.from(Price.class);
subquery.select(subqueryRoot.get(Price_.PRICE));
subquery.where(
cb.equal(rootFrom.get(Product_.ID),
subqueryRoot.get(Price_.PRODUCT).get(Product_.ID)),
cb.equal(
subqueryRoot.get(Price_.COMPANY).get(Company_.ID), companyId));
criteriaQuery.select(cb.construct(ProductDTOOut.class,
rootFrom.get(Product_.ID),
rootFrom.get(Product_.NAME),
subquery // here put subquery
));
TypedQuery<ProductDTOOut> query = manager.createQuery(criteriaQuery);
return query.getResultList();
I hope that I helped someone with the same question.
I want to create a typed query.
TypedQuery<PubThread> query = em.createQuery(queryString, PubThread.class);
query.setParameter("threadId", threadId);
List<PubThread> otherPubThreads = query.getResultList();
In the queryString is the following SQL (currently without param and static selection values)
SELECT pt2 FROM pubthread pt2
JOIN pub_pubthread ppt2 ON pt2.id = ppt2.pubThreads_id
JOIN pub p2 ON ppt2.pups_id = p2.id
JOIN pubcategory pc2 ON p2.pubCategoryId = pc2.id
WHERE pt2.id != 1 and EXISTS (
SELECT DISTINCT(pt.id)
FROM pubthread pt
JOIN pub_pubthread ppt ON pt.id = ppt.pubThreads_id
JOIN pub p ON ppt.pups_id = p.id
JOIN pubcategory pc ON p.pubCategoryId = pc.id
WHERE pc2.id = pc.id and pt.id = 1
)
It does work, if I limit the String to a simple select: SELECT Distinct(pt2.id), pt2.name FROM pubthread pt2. As soon I add a JOIN line to it, it will complain. How do you properly query with JOINS in JPA? The error is:
org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: ON near line 1, column 81 [SELECT pt2 FROM com.brayan.webapp.model.PubThread pt2 JOIN pub_pubthread ppt2 ON pt2.id = ppt2.pubThreads_id ]
Doubtless, a criteria query would be nicer. I accept that as part of the solution space.
When you call createQuery you have to write HQL but not SQL (your queryString is not HQL).
In HQL you have to join objects according to your mapping entities.
If you sill need SQL query use createNativeQuery method.
See documentation about how to create HQL query.
Got it. See below a fully example of joins. It consists of:
multiple joins (join-chaining)
a subquery
a predicate correlation/equation over join tables, other than the root table.
I also commented the obsolete code lines for other to see what a wrong approach.
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery mainQuery = criteriaBuilder
.createQuery(PubThread.class);
// 1) MainQuery
// Create the FROM
Root<PubThread> rootPubThread = mainQuery.from(PubThread.class);
// Create the JOIN from the first select: join-chaining. You only need the return for ordering. e.g. cq.orderBy(cb.asc(categoryJoin.get(Pub_.title)));
Join<Pub, PubCategory> categoryJoin = rootPubThread.join(PubThread_.pups).join(Pub_.pubCategory);
// Create the WHERE
mainQuery.where(criteriaBuilder.not(criteriaBuilder.equal(rootPubThread.get(PubThread_.id), threadId)));
// Create the SELECT, at last
mainQuery.select(rootPubThread).distinct(true);
// 2) Subquery
Subquery<PubThread> subquery = mainQuery.subquery(PubThread.class);
Root<PubThread> rootPubThreadSub = subquery.from(PubThread.class);
//subquery.where(criteriaBuilder.equal(rootPubThread.get(PubThread_.id), threadId));
Join<Pub, PubCategory> categoryJoinSub = rootPubThreadSub.join(PubThread_.pups).join(Pub_.pubCategory);
subquery.select(rootPubThreadSub);
//Predicate correlatePredicate = criteriaBuilder.equal(rootPubThreadSub.get(PubThread_.id), rootPubThread);
Predicate correlatePredicate = criteriaBuilder.and(
//criteriaBuilder.equal(rootPubThreadSub.get(PubThread_.id), rootPubThread),
criteriaBuilder.equal(categoryJoinSub.get(PubCategory_.id), categoryJoin.get(PubCategory_.id)),
criteriaBuilder.equal(rootPubThreadSub.get(PubThread_.id), threadId)
);
subquery.where(correlatePredicate);
//Predicate correlatePredicate = criteriaBuilder.equal(rootPubThreadSub.get(PubThread_.id), rootPubThread);
Predicate mainPredicate = criteriaBuilder.and(
criteriaBuilder.not(criteriaBuilder.equal(rootPubThread.get(PubThread_.id), threadId)),
criteriaBuilder.exists(subquery)
);
//cq.where(criteriaBuilder.exists(subquery));
mainQuery.where(mainPredicate);
I am quite new to JPA and I have encountered a problem with understanding one particular query. I have rewritten in to the CriteriaQuery but the result and the query translated to SQL is not correct.
Background situation: I have a table of store transaction (moves) and the current amount in the store is defined as a sum of all changes. Now I want to select and display the last moves as they also contain an information about the resulting amount on store.
So, this is the query in JPQL:
SELECT m FROM move m WHERE m.id = (
SELECT MAX(o.id) FROM move o WHERE (o.item = m.item AND m.cell.store = :s)
I tried to rewrite it to the following CriteriaQuery:
CriteriaBuilder builder = model.getCriteriaBuilder();
CriteriaQuery<Move> query = builder.createQuery(Move.class);
Root<Move> root = query.from(Move.class);
Subquery<Long> subquery = query.subquery(Long.class);
Root<Move> subroot = subquery.from(Move.class);
subquery.select(builder.max(subroot.get("id").as(Long.class)));
subquery.where(builder.and(
builder.equal(
subroot.get("item").get("id"),
root.get("item").get("id")),
builder.equal(
subroot.get("cell").get("store").as(Store.class),
store)));
Expression<Boolean> where = builder.equal(
root.get("id"),
subquery);
query.where(where);
return model.getList(query);
The incorrect SQL query produced is following:
SELECT t0.id, (...) FROM move t0 WHERE (t0.id = (
SELECT MAX(t1.id) FROM item t3, item t2, move t1
WHERE (((t2.id = t3.id) AND
(t1.cell_store = ?)) AND
((t2.id = t1.item_ref) AND
(t3.id = t0.item_ref))))
)
I do not understand why there is a double cross join in the subquery. Thank you for helping!