How to use LIMIT in sub-query with JPA Criteria? - jpa

I want to make a query within it has a su-query, then this will make the query.
SELECT * FROM orders orda WHERE orda.id_orders IN
(SELECT ordb.id_orders FROM orders ordb WHERE orda.identifier = ordb.identifier LIMIT 1)
Currently my structure is as follows with other parameters:
return new Specification<Orders>() {
#Override
public Predicate toPredicate(Root<Orders> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
Subquery<Orders> ordersSubquery = criteriaQuery.subquery(Orders.class);
Root rootB = ordersSubquery.from(Orders.class);
ordersSubquery.select(rootB.get(Orders_.id_orders));
ordersSubquery.where(cb.equal(root.get(Orders_.identifier), rootB.get(Orders_.identifier)));
criteriaQuery.orderBy(cb.asc(root.get(Orders_.created_at)));
criteriaQuery.groupBy(root.get(Orders_.created_at));
Predicate temp = cb.and(
root.get(Orders_.id_orders).in(ordersSubquery)
);
return temp;
}
};
Can anyone help as I include the LIMIT statement in the sub-query? Thank you!

Add criteriaBuilder.min(rootB.<Number>get(Orders_.id_orders)) in your select statement. This should work.

Related

Alternative way to use order By within subquery Criteria API

`How can use order by to get order date ascending in subquery of specification?
public Predicate toPredicate(Root<B> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
Subquery<A> subquery = query.subquery(A.class);
Root<A> subqueryRoot = subquery.from(A.class);
subquery.select(subqueryRoot);
Predicate joinPredicate = builder.equal(subqueryRoot.get("Id"), root.<Long> get("Id"));
Predicate testCodePredicate = builder.equal(subqueryRoot.get("name"), "test");
subquery.select(subqueryRoot).where(joinPredicate, testCodePredicate);
return builder.exists(subquery);
}
`

How to pass a HashMap to CriteriaBuilder and add to Predicates list

I am currently receiving a Map<String, String> in the API request body. I have an entity,
class TagEntity {
#Column(name = "name", length = 128, nullable = false)
private String name;
#Column(name = "value", length = 256, nullable = false)
private String value;
}
There is a MachineEntity which has a Set<TagEntity>. It's a #OneToMany mapping. I am trying to fetch the machine entities according to the name and value passed in the HashMap (in request) which correspond to the TagEntity.
I tried this code below. However, it works only if I pass one name-value pair in the request. Suppose, if the HashMap contains 2 elements, the query returns an empty list when it should return 2 Machine Entities.
SetJoin<MachineEntity, TagEntity> join = root.join(MachineEntity_.tagEntities);
for (Map.Entry element : request.getTags().entrySet()) {
predicates.add(criteriaBuilder.and(
criteriaBuilder.equal(join.get(TagEntity_.name), element.getKey()),
criteriaBuilder.equal(join.get(TagEntity_.value), element.getValue())
));
}
Is there a way I can set the HashMap in CriteriaBuilder without iterating through the Map? I am clueless what can be done to solve this problem. Will greatly appreciate some help.
As always, writing the statement in (roughly) SQL helps:
SELECT ...
FROM Machine m
WHERE -- repeat for each map entry
EXISTS (SELECT ... FROM Tag t WHERE t.machine_id = m.id AND t.name = ?1 AND t.value = ?2)
AND EXISTS (SELECT ... FROM Tag t WHERE t.machine_id = m.id AND t.name = ?3 AND t.value = ?4)
...
If this suits your needs, it can be translated to criteria API roughly as follows (you will probably need to tweak it):
private Subquery<TagEntity> makeExistsSubquery(CriteriaQuery<MachineEntity> q, CriteriaBuilder cb, Map.Entry<String,String> e) {
Subquery<TagEntity> subquery = q.subquery(TagEntity.class);
Root<TagEntity> tagRoot = subquery.from(TagEntity.class);
subquery.where(cb.and(
cb.equal(tagRoot.get(TagEntity_.name), e.getKey()),
cb.equal(tagRoot.get(TagEntity_.value), e.getValue())
));
return subquery;
}
private void your_method() {
// assuming something like this exists:
CriteriaQuery<MachineEntity> query = criteriaBuilder.createQuery(MachineEntity.class);
// do this:
for (Map.Entry element : request.getTags().entrySet()) {
predicates.add(criteriaBuilder.exists(makeExistsSubquery(query, criteriaBuilder, element)));
}
...
}
I am not sure how efficient is this query going to be. But I hope this hints to a solution.
Additionally I am assuming you want machines that match ALL the given tags. If you want machines matching ANY of the given tags, the SQL query would be simpler:
SELECT ...
FROM Machine m JOIN Tag t ON t.machine_id = m.id
WHERE -- repeat for each map entry
t.name = ?1 AND t.value = ?2
OR t.name = ?3 AND t.value = ?4
...
Predicate tagsPredicate = criteriaBuilder.or(
request.getTags().entrySet().stream()
.map(e -> criteriaBuilder.and(
criteriaBuilder.equal(join.get(TagEntity_.name), e.getKey()),
criteriaBuilder.equal(join.get(TagEntity_.value), e.getValue())
))
.collect(Collectors.toList())
.toArray(new Predicate[0])
);
// add the tagsPredicate to your where clause, if the user has actually defined tag criteria

Creating Criteria By And or Or on Runtime by JPA Repository

I need to create a query by deciding on runtime. Basically I have few parameters and one of them will specify If I should use And or Or to combine criterias. By using Spring JPA Repository how can I do it? It is easy to do it for parameters as :
#Query(value = "SELECT u FROM User u WHERE u.name IN :names")
List<User> findUserByNameList(#Param("names") Collection<String> names);
But if I want to add one more criteria lets say size, and I need to decide which one of (AND,OR) to use to combine criteria, how can I do it?
Example:
SELECT u FROM User u WHERE u.name IN :names OR/AND size = 10;
public List<User> getUsers(List<String> names, Integer size, boolean useOrPredicate) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<User> criteriaQuery = criteriaBuilder.createQuery(User.class);
Root<User> user = criteriaQuery.from(User.class);
Predicate predicate1 = user.get("name").in(names);
Predicate predicate2 = criteriaBuilder.equal(user.get("size"), size);
Predicate predicate;
if(useOrPredicate)
predicate = criteriaBuilder.or(predicate1, predicate2);
else
predicate = criteriaBuilder.and(predicate1, predicate2);
criteriaQuery.where(predicate);
return entityManager.createQuery(criteriaQuery).getResultList();
}

How do I count the number of rows returned by subquery?

I want to do something like this:
select count(*) from (select ...)
(As it would be in SQL), but in JPA.
Any ideas on how I would do it?
I stumbled upon this issue as well. I would ultimately like to execute the following JPQL:
SELECT COUNT(u)
FROM (
SELECT DISTINCT u
FROM User u
JOIN u.roles r
WHERE r.id IN (1)
)
But this wasn't possible, also not with criteria API. Research taught that this was just a design limitation in JPA. The JPA spec states that subqueries are only supported in WHERE and HAVING clauses (and thus not in the FROM).
Rewriting the query in the following JPQL form:
SELECT COUNT(u)
FROM User u
WHERE u IN (
SELECT DISTINCT u
FROM User u
JOIN u.roles r
WHERE r.id IN (1)
)
using the JPA Criteria API like as follows:
CriteriaQuery<Long> query = cb.createQuery(Long.class);
Root<User> u = query.from(User.class);
Subquery<User> subquery = query.subquery(User.class);
Root<User> u_ = subquery.from(User.class);
subquery.select(u_).distinct(true).where(u_.join("roles").get("id").in(Arrays.asList(1L)));
query.select(cb.count(u)).where(cb.in(u).value(subquery));
Long count = entityManager.createQuery(query).getSingleResult();
// ...
has solved the functional requirement for me. This should also give you sufficient insight into solving your particular functional requirement.
This should do the trick (If you want to use JPA criteria API):
CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
CriteriaQuery<Long> query = cb.createQuery(Long.class);
Root<Entity> root = query.from(Entity.class);
//Selecting the count
query.select(cb.count(root));
//Create your search criteria
Criteria criteria = ...
//Adding search criteria
query.where(criteria);
Long count = getEntityManager().createQuery(query).getSingleResult();
On the other hand, if you want to use JP-QL, the following code should do the trick:
//Add the where condition to the end of the query
Query query = getEntityManager().createQuery("select count(*) from Entity entity where...")
Long count = query.getSingleResult();
Use the following snippet to count rows for a given Criteria Query:
public static Query createNativeCountQuery(EntityManager em, CriteriaQuery<?> criteriaQuery) {
org.hibernate.query.Query<?> hibernateQuery = em.createQuery(criteriaQuery).unwrap(org.hibernate.query.Query.class);
String hqlQuery = hibernateQuery.getQueryString();
QueryTranslatorFactory queryTranslatorFactory = new ASTQueryTranslatorFactory();
QueryTranslator queryTranslator = queryTranslatorFactory.createQueryTranslator(
hqlQuery,
hqlQuery,
Collections.emptyMap(),
em.getEntityManagerFactory().unwrap(SessionFactoryImplementor.class),
null
);
queryTranslator.compile(Collections.emptyMap(), false);
String sqlCountQueryTemplate = "select count(*) from (%s)";
String sqlCountQuery = String.format(sqlCountQueryTemplate, queryTranslator.getSQLString());
Query nativeCountQuery = em.createNativeQuery(sqlCountQuery);
Map<Integer, Object> positionalParamBindings = getPositionalParamBindingsFromNamedParams(hibernateQuery);
positionalParamBindings.forEach(nativeCountQuery::setParameter);
return nativeCountQuery;
}
private static Map<Integer, Object> getPositionalParamBindingsFromNamedParams(org.hibernate.query.Query<?> hibernateQuery) {
Map<Integer, Object> bindings = new HashMap<>();
for (var namedParam : hibernateQuery.getParameterMetadata().getNamedParameters()) {
for (int location : namedParam.getSourceLocations()) {
bindings.put(location + 1, hibernateQuery.getParameterValue(namedParam.getName()));
}
}
return bindings;
}

JPA 2.0, Criteria API, Subqueries, In Expressions

I have tried to write a query statement with a subquery and an IN expression for many times. But I have never succeeded.
I always get the exception, " Syntax error near keyword 'IN' ", the query statement was build like this,
SELECT t0.ID, t0.NAME
FROM EMPLOYEE t0
WHERE IN (SELECT ?
FROM PROJECT t2, EMPLOYEE t1
WHERE ((t2.NAME = ?) AND (t1.ID = t2.project)))
I know the word before 'IN' lose.
Have you ever written such a query? Any suggestion?
Below is the pseudo-code for using sub-query using Criteria API.
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Object> criteriaQuery = criteriaBuilder.createQuery();
Root<EMPLOYEE> from = criteriaQuery.from(EMPLOYEE.class);
Path<Object> path = from.get("compare_field"); // field to map with sub-query
from.fetch("name");
from.fetch("id");
CriteriaQuery<Object> select = criteriaQuery.select(from);
Subquery<PROJECT> subquery = criteriaQuery.subquery(PROJECT.class);
Root fromProject = subquery.from(PROJECT.class);
subquery.select(fromProject.get("requiredColumnName")); // field to map with main-query
subquery.where(criteriaBuilder.and(criteriaBuilder.equal("name",name_value),criteriaBuilder.equal("id",id_value)));
select.where(criteriaBuilder.in(path).value(subquery));
TypedQuery<Object> typedQuery = entityManager.createQuery(select);
List<Object> resultList = typedQuery.getResultList();
Also it definitely needs some modification as I have tried to map it according to your query. Here is a link http://www.ibm.com/developerworks/java/library/j-typesafejpa/ which explains concept nicely.
Late resurrection.
Your query seems very similar to the one at page 259 of the book Pro JPA 2:
Mastering the Java Persistence API, which in JPQL reads:
SELECT e
FROM Employee e
WHERE e IN (SELECT emp
FROM Project p JOIN p.employees emp
WHERE p.name = :project)
Using EclipseLink + H2 database, I couldn't get neither the book's JPQL nor the respective criteria working. For this particular problem I have found that if you reference the id directly instead of letting the persistence provider figure it out everything works as expected:
SELECT e
FROM Employee e
WHERE e.id IN (SELECT emp.id
FROM Project p JOIN p.employees emp
WHERE p.name = :project)
Finally, in order to address your question, here is an equivalent strongly typed criteria query that works:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> emp = c.from(Employee.class);
Subquery<Integer> sq = c.subquery(Integer.class);
Root<Project> project = sq.from(Project.class);
Join<Project, Employee> sqEmp = project.join(Project_.employees);
sq.select(sqEmp.get(Employee_.id)).where(
cb.equal(project.get(Project_.name),
cb.parameter(String.class, "project")));
c.select(emp).where(
cb.in(emp.get(Employee_.id)).value(sq));
TypedQuery<Employee> q = em.createQuery(c);
q.setParameter("project", projectName); // projectName is a String
List<Employee> employees = q.getResultList();
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Employee> criteriaQuery = criteriaBuilder.createQuery(Employee.class);
Root<Employee> empleoyeeRoot = criteriaQuery.from(Employee.class);
Subquery<Project> projectSubquery = criteriaQuery.subquery(Project.class);
Root<Project> projectRoot = projectSubquery.from(Project.class);
projectSubquery.select(projectRoot);
Expression<String> stringExpression = empleoyeeRoot.get(Employee_.ID);
Predicate predicateIn = stringExpression.in(projectSubquery);
criteriaQuery.select(criteriaBuilder.count(empleoyeeRoot)).where(predicateIn);
You can use double join, if table A B are connected only by table AB.
public static Specification<A> findB(String input) {
return (Specification<A>) (root, cq, cb) -> {
Join<A,AB> AjoinAB = root.joinList(A_.AB_LIST,JoinType.LEFT);
Join<AB,B> ABjoinB = AjoinAB.join(AB_.B,JoinType.LEFT);
return cb.equal(ABjoinB.get(B_.NAME),input);
};
}
That's just an another option
Sorry for that timing but I have came across this question and I also wanted to make SELECT IN but I didn't even thought about double join.
I hope it will help someone.