Unneccessary join in jpa subquery - java

I have following jpa criteria query:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Company> cq = cb.createQuery(Company.class);
Root<Company> root = cq.from(Company.class);
cq.select(root);
Subquery<String> sq = cq.subquery(String.class);
Root<Employee> subroot = sq.from(Employee.class);
sq.select(subroot.get(Employee_.lastName));
Predicate typePredicate = cb.equal(subroot.get(Employee_.lastName), "Doe");
Predicate correlatePredicate = cb.equal(root.get(Company_.employees), subroot);
sq.where(cb.and(typePredicate, correlatePredicate));
cq.where(cb.exists(sq));
TypedQuery<Company> typedQuery = em.createQuery(cq);
List<Company> companies = typedQuery.getResultList();
Eclipselink produces following SQL:
SELECT t0.ID, ... FROM COMPANY t0
WHERE EXISTS (SELECT t1.LASTNAME FROM EMPLOYEES t2, EMPLOYEES t1
WHERE (((t1.LASTNAME = ?) AND (t1.ID = t2.ID))
AND (t2.COMPANY_ID = t0.ID)))
As you can see there is an unneccessary join on table EMPLOYEES. How do I get rid of this join?

You don't seem to need a subquery for the query, just a join should be enough,
http://en.wikibooks.org/wiki/Java_Persistence/Criteria#Join
Otherwise, what version are you using? Can you try EclipseLink 2.4.
If it still has the duplicate, please log a bug and vote for it.
You might be able to use the inverse ManyToOne, instead of the OneToMany (i.e. root == subroot.get("company") ).
Also try JPQL in 2.4, is the join optimized?

Related

How to create a subquery using criteria Api

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.

JPA Criteria query with inner join of aggregation

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.

JPA Criteria query double joins and hibernate error with-clause

Based on this answer https://stackoverflow.com/a/2111420/3989524 I first created a working SQL:
SELECT d.*
FROM device d
LEFT OUTER JOIN installation_record ir1 ON (d.id = ir1.device)
LEFT OUTER JOIN installation_record ir2 ON (d.id = ir2.device AND ir1.install_date < ir2.install_date)
WHERE ir2.id IS NULL AND ir1.uninstall_date IS NULL;
and then a criteria query which looks to produce an equivalent HQL (in the end), but Hibernate throws an error:
org.hibernate.hql.internal.ast.QuerySyntaxException: with-clause referenced two different from-clause elements
The HQL from the error message:
SELECT generatedAlias0 FROM Device AS generatedAlias0
LEFT JOIN generatedAlias0.installationRecordList AS generatedAlias1
LEFT JOIN generatedAlias0.installationRecordList AS generatedAlias2
WITH generatedAlias1.installDate<generatedAlias2.installDate
WHERE ( generatedAlias2.id IS NULL ) AND ( generatedAlias1.uninstallDate IS NULL )
The criteria query:
final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<Device> cq = cb.createQuery(Device.class);
final Root<Device> root = cq.from(Device.class);
final Join<Device, InstallationRecord> join1 = root.join(Device_.installationRecordList, JoinType.LEFT);
final Join<Device, InstallationRecord> join2 = root.join(Device_.installationRecordList, JoinType.LEFT);
join2.on(cb.lessThan(join1.get(InstallationRecord_.installDate), join2.get(InstallationRecord_.installDate)));
cq.select(root);
final List<Predicate> predicates = Lists.newArrayList();
predicates.add(cb.isNull(join2.get(InstallationRecord_.id)));
predicates.add(cb.isNull(join1.get(InstallationRecord_.uninstallDate)));
cq.where(predicates.toArray(new Predicate[] {}));
return em.createQuery(cq).getResultList();
Is there anyway to get what I want or there no other way around some internal hibernate bug.
Maybe:
final Join<InstallationRecord, InstallationRecord> join2 = root.join(InstallationRecord_.id, JoinType.LEFT);
because ON (d.id = ir2.device AND is missing in the HQL.

How to convert SQL query with sub query and count to JPA criteria builder

I'm trying to convert this SQL query to use a JPA criteria builder.
The answer needs to be a double or a float.
SELECT CAST((COUNT(m.id) -
(SELECT COUNT(s.id)
FROM mobile_unit as s
left JOIN incident as i ON s.incidentId=i.id
JOIN organizational_unit as o ON s.organizationalUnitId=o.id
WHERE (s.organizationalUnitId = 1 AND s.incidentId IS NULL))) AS float)
/COUNT(m.id)
FROM mobile_unit as m
JOIN organizational_unit as o ON m.organizationalUnitId=o.id
WHERE m.organizationalUnitId = 1
Without testing this, here is a rough sketch of how this could be done.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Float> cq = cb.createQuery(Float.class);
Root<MobileUnit> root = cq.from(MobileUnit.class);
// Join is actually unnecessary
root.join("organizationalUnit", JoinType.INNER);
cq.where(cb.equal(
root.get("organizationalUnitId"),
1
));
Subquery<Long> subquery = cq.subquery(Long.class);
Root<MobileUnit> subRoot = subquery.from(MobileUnit.class);
// Actually these joins are unnecessary, but you requested them..
subRoot.join("incident", JoinType.LEFT);
subRoot.join("organizationalUnit", JoinType.INNER);
subquery.select(cb.count(subRoot.get("id")));
subquery.where(cb.and(
subRoot.get("organizationalUnitId").eq(cb.literal(1)),
subRoot.get("incidentId").isNull()
));
cq.select(cb.quot(
cb.diff(
cb.count(root.get("id")),
subquery
).as(Float.class),
cb.count(root.get("id"))
));

How to formulate a JOIN in a typed query?

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

Categories