Criteria API limit results in subquery - java

I'm trying to write a query similar to
select * from Table a
where a.parent_id in
(select b.id from Table b
where b.state_cd = ?
and rownum < 100)
using the Criteria API. I can achieve the query without the rownum limitation on the subquery fine using similar code to https://stackoverflow.com/a/4668015/597419 but I cannot seem to figure out how to appose a limit on the Subquery

In Hibernate, you can add the actual SQL restriction, but it is worth noting this will be Oracle-specific. If you switched over to PostgreSQL, this would break and you'd need LIMIT 100 instead.
DetachedCriteria criteria = DetachedCriteria.forClass(Domain.class)
.add(Restrictions.sqlRestriction("rownum < 100"));
In the JPA API, the short answer is that you can't... In your question you proposed using the Criteria API (along with a SubQuery). However it is not until you actually call EntityManager.createQuery(criteriaQuery) that you'll get a TypedQuery where you can specify the maxResult value.
That said, you could break it into 2 queries, the first where you get the inner-select results (max 100) and then a 2nd Criteria where you take the resulting list in an in():
// inner query
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<YourClass> innerCriteriaQuery = cb.createQuery(YourClass.class);
Root<YourClass> yourClass = innerCriteriaQuery.from(YourClass.class);
innerCriteriaQuery.select(yourClass).where(
cb.equal(yourClass.get(YourClass_.stateCode), someStateValue));
// list of 100 parent ids
List<YourClass> list = em.createQuery(innerCriteriaQuery).setMaxResults(100).getResultList();
// outer query
CriteriaQuery<YourClass> criteriaQuery = cb.createQuery(YourClass.class);
Root<YourClass> yourClass = criteriaQuery.from(YourClass.class);
criteriaQuery.select(yourClass).where(
cb.in(yourClass.get(YourClass_.parentId)).value(list);
return em.createQuery(criteriaQuery).getResultList();

There is no JPA Criteria solution for this. You could make use of a custom SQL function that runs during SQL query generation time. All JPA providers support something like that in one way or another.
If you don't want to implement that yourself or even want a proper API for constructing such queries, I can only recommend you the library I implemented called Blaze-Persistence.
Here is the documentation showcasing the limit/offset use case with subqueries: https://persistence.blazebit.com/documentation/core/manual/en_US/index.html#pagination
Your query could look like this with the query builder API:
criteriaBuilderFactory.create(entityManager, SomeEntity.class)
.where("id").in()
.select("subEntity.id")
.from(SomeEntity.class, "subEntity")
.where("subEntity.state").eq(someValue)
.orderByAsc("subEntity.id")
.setMaxResults(100)
.end()
It essentially boils down to using the LIMIT SQL function that is registered by Blaze-Persistence. So when you bootstrap Blaze-Persistence with your EntityManagerFactory, you should even be able to use it like this
entityManager.createQuery(
"select * from SomeEntity where id IN(LIMIT((" +
" select id " +
" from SomeEntity subEntity " +
" where subEntity.state = :someParam " +
" order by subEntity.id asc" +
"),1)) "
)
or something like
criteriaQuery.where(
cb.in(yourClass.get(YourClass_.parentId)).value(cb.function("LIMIT", subquery));
If you are using EclipseLink the calling convention of such functions looks like OPERATOR('LIMIT', ...).

Related

QueryDsl - How create inner join with sorted and grouped table

I have to find Salesmen that have sold some itemType. I created method (see below).
But client told me that he wants to find Salesmen by LAST sold itemType.
DB schema:
My attempts: we have in table ORDERS date column, so in normal SQL query I can do double subquery and it should work.
Double, because first I'm sorting by date, then group by salesman - that returns list with only last sold items.
SELECT *
FROM SALESMEN
JOIN
(SELECT *
FROM
(SELECT *
FROM ORDERS
ORDER BY ORDERS.date)
GROUP BY ORDERS.salesman_id) ON SALESMEN.id = ORDERS.salesman_id
WHERE ORDERS.item_type = "CAR"
Unfortunately, queryDSL can do subquery only in IN clause not in FROM.
I spend many hours to find a solution, and in my opinion, it's simply impossible using queryDSL to get sorted and grouped list and join it with another table in one query.
But maybe someone with grater experience has any idea, maybe a solution is simpler than I think :D
public List<SalesmanEntity> findSalesman(SalesmanSearchCriteriaTo criteria) {
SalesmanEntity salesmanEntity = Alias.alias(SalesmanEntity.class);
EntityPathBase<SalesmanEntity> alias = Alias.$(salesman);
JPAQuery<SalesmanEntity> query = new JPAQuery<SalesmanEntity>(getEntityManager()).from(alias);
... a lot of wird IF's....
if (criteria.getLastSoldItemTyp() != null) {
OrderEntity order = Alias.alias(OrderEntity.class);
EntityPathBase<OrderEntity> aliasOrder = Alias.$(order);
query.join(aliasOrder)
.on(Alias.$(salesman.getId()).eq(Alias.$(order.getSalesmanId())))
.where(Alias.$(order.getItemTyp()).eq(criteria.getLastSoldItemTyp()));
}
return query.fetch();
}
Environment:
Java 1.8
SpringBoot 2.0.9
QueryDSL 4.1.4
This is not a limitation of QueryDSL, rather it is a limitation of JPQL - the query language of JPA. For example, SQL does allow subqueries in the FROM clause, and as such querydsl-sql also allows it. With plain plain JPA, or even Hibernate's proprietary HQL it cannot be done. You would have to write a native SQL query then. For this you can have a look at #NamedNativeQuery.
It is possible to add subqueries on top of JPA using Common Table Expressions (CTE) using Blaze-Persistence. Blaze-Persistence ships with an optional QueryDSL integration as well.
Using that extension library, you can just write the following:
QRecursiveEntity recursiveEntity = new QRecursiveEntity("t");
List<RecursiveEntity> fetch = new BlazeJPAQuery<>(entityManager, cbf)
.select(recursiveEntity)
.from(select(recursiveEntity)
.from(recursiveEntity)
.where(recursiveEntity.parent.name.eq("root1"))
.orderBy(recursiveEntity.name.asc())
.limit(1L), recursiveEntity)
.fetch();
Alternatively, when using Hibernate, you can map a Subquery as an Entity, and then correlate that in your query. Using this you can achieve the same result, but you won't be able to reference any outer variables in the subquery, nor will you be able to parameterize the subquery. Both of these features will however be available with the above approach!
#Entity
#Subselect("SELECT salesman_id, sum(amount) FROM ( SELECT * FROM ORDERS ORDER BY ORDERS.date ) GROUP BY ORDERS.salesman_id")
class OrdersBySalesMan {
#Id #Column(name = "salesman_id") Long salesmanId;
#Basic BigDecimal amount; // or something similarly
}
And then in your query:
.from(QSalesman.salesman)
.innerJoin(QOrdersBySalesMan.ordersBySalesMan)
.on(ordersBySalesMan.salesmanId.eq(salesman.id))

Convert HQL query in the form of "select foo from Foo foo, Bar bar where ..." to Criteria query

In our (ancient) project, we use loads of select queries with HQL on Hibernate version 3.5.6-Final. Because Hibernate will do an auto-commit, because it doesn't know they are select queries, I'm in the process of rewriting all HQL select queries to Hibernate Criteria queries so it won't do in-between commits when we don't want it yet. This is pretty straight-forward in most cases, but I'm currently looking at a query like this, and I'm not sure how to transform it:
Query query = session.createQuery("select municapilityStreet"
+ " from MunicapilityStreet munStreet, Street street"
+ " where munStreet.id = street.MunicapilityStreet.id"
+ " and street.id = :streetId");
query.setParameter(":streetId", streetId);
MunicapilityStreet result = (MunicapilityStreet) query.uniqueResult();
return result;
Here what I have thus far in the process of transforming it:
Criteria criteria = session.createCriteria(MunicapilityStreet.class);
// No idea what to set here to only get the "municapilityStreet" as result
criteria.setProjection(??);
// I'm not sure if this is correct. With a criteria on a single table it would have been simply "Id".
// Both tables have the column-name Id, and I'm not sure how to differentiate between them.
criteria.add(Restrictions.eq("Street.Id", streetId));
MunicapilityStreet result = (MunicapilityStreet) criteria.uniqueResult();
return result;
Maybe I should create a different question for each, but converting the above HQL query to a Criteria has three points I'm not sure about how to do:
How to do a select with multiple tables (the from MunicapilityStreet munStreet, Street street part)?
How to have a projection to only return a single table of the two (the select municapilityStreet part)?
How to have an equal-Restriction on a column name of one table, even though both tables have the same column-name (the and street.id = :streetId part)?
I do oppose the rewrite approach, I hope I'm not impolite doing so.
Hibernate allows to control commits (autocommit is by default off), and what you're experiencing are Entitymanager-flushes, they are auto by default and can be disabled too. And, finally, I think, there is no difference if you're running HQL or criteria queries, same machinery underneath.

Is there equivalent clause in hibernate to Oracle 12c's "fetch first ..." clause? [duplicate]

In Hibernate 3, is there a way to do the equivalent of the following MySQL limit in HQL?
select * from a_table order by a_table_column desc limit 0, 20;
I don't want to use setMaxResults if possible. This definitely was possible in the older version of Hibernate/HQL, but it seems to have disappeared.
This was posted on the Hibernate forum a few years back when asked about why this worked in Hibernate 2 but not in Hibernate 3:
Limit was never a supported clause
in HQL. You are meant to use
setMaxResults().
So if it worked in Hibernate 2, it seems that was by coincidence, rather than by design. I think this was because the Hibernate 2 HQL parser would replace the bits of the query that it recognised as HQL, and leave the rest as it was, so you could sneak in some native SQL. Hibernate 3, however, has a proper AST HQL Parser, and it's a lot less forgiving.
I think Query.setMaxResults() really is your only option.
// SQL: SELECT * FROM table LIMIT start, maxRows;
Query q = session.createQuery("FROM table");
q.setFirstResult(start);
q.setMaxResults(maxRows);
If you don't want to use setMaxResults() on the Query object then you could always revert back to using normal SQL.
The setFirstResult and setMaxResults Query methods
For a JPA and Hibernate Query, the setFirstResult method is the equivalent of OFFSET, and the setMaxResults method is the equivalent of LIMIT:
List<Post> posts = entityManager.createQuery("""
select p
from Post p
order by p.createdOn
""")
.setFirstResult(10)
.setMaxResults(10)
.getResultList();
The LimitHandler abstraction
The Hibernate LimitHandler defines the database-specific pagination logic, and as illustrated by the following diagram, Hibernate supports many database-specific pagination options:
Now, depending on the underlying relational database system you are using, the above JPQL query will use the proper pagination syntax.
MySQL
SELECT p.id AS id1_0_,
p.created_on AS created_2_0_,
p.title AS title3_0_
FROM post p
ORDER BY p.created_on
LIMIT ?, ?
PostgreSQL
SELECT p.id AS id1_0_,
p.created_on AS created_2_0_,
p.title AS title3_0_
FROM post p
ORDER BY p.created_on
LIMIT ?
OFFSET ?
SQL Server
SELECT p.id AS id1_0_,
p.created_on AS created_on2_0_,
p.title AS title3_0_
FROM post p
ORDER BY p.created_on
OFFSET ? ROWS
FETCH NEXT ? ROWS ONLY
Oracle
SELECT *
FROM (
SELECT
row_.*, rownum rownum_
FROM (
SELECT
p.id AS id1_0_,
p.created_on AS created_on2_0_,
p.title AS title3_0_
FROM post p
ORDER BY p.created_on
) row_
WHERE rownum <= ?
)
WHERE rownum_ > ?
The advantage of using setFirstResult and setMaxResults is that Hibernate can generate the database-specific pagination syntax for any supported relational databases.
And, you are not limited to JPQL queries only. You can use the setFirstResult and setMaxResults method seven for native SQL queries.
Native SQL queries
You don't have to hardcode the database-specific pagination when using native SQL queries. Hibernate can add that to your queries.
So, if you're executing this SQL query on PostgreSQL:
List<Tuple> posts = entityManager.createNativeQuery(
SELECT
p.id AS id,
p.title AS title
from post p
ORDER BY p.created_on
""", Tuple.class)
.setFirstResult(10)
.setMaxResults(10)
.getResultList();
Hibernate will transform it as follows:
SELECT p.id AS id,
p.title AS title
FROM post p
ORDER BY p.created_on
LIMIT ?
OFFSET ?
Cool, right?
Beyond SQL-based pagination
Pagination is good when you can index the filtering and sorting criteria. If your pagination requirements imply dynamic filtering, it's a much better approach to use an inverted-index solution, like ElasticSearch.
If you don't want to use setMaxResults, you can also use Query.scroll instead of list, and fetch the rows you desire. Useful for paging for instance.
You can easily use pagination for this.
#QueryHints({ #QueryHint(name = "org.hibernate.cacheable", value = "true") })
#Query("select * from a_table order by a_table_column desc")
List<String> getStringValue(Pageable pageable);
you have to pass new PageRequest(0, 1)to fetch records and from the list fetch the first record.
You need to write a native query, refer this.
#Query(value =
"SELECT * FROM user_metric UM WHERE UM.user_id = :userId AND UM.metric_id = :metricId LIMIT :limit", nativeQuery = true)
List<UserMetricValue> findTopNByUserIdAndMetricId(
#Param("userId") String userId, #Param("metricId") Long metricId,
#Param("limit") int limit);
String hql = "select userName from AccountInfo order by points desc 5";
This worked for me without using setmaxResults();
Just provide the max value in the last (in this case 5) without using the keyword limit.
:P
My observation is that even you have limit in the HQL (hibernate 3.x), it will be either causing parsing error or just ignored. (if you have order by + desc/asc before limit, it will be ignored, if you don't have desc/asc before limit, it will cause parsing error)
If can manage a limit in this mode
public List<ExampleModel> listExampleModel() {
return listExampleModel(null, null);
}
public List<ExampleModel> listExampleModel(Integer first, Integer count) {
Query tmp = getSession().createQuery("from ExampleModel");
if (first != null)
tmp.setFirstResult(first);
if (count != null)
tmp.setMaxResults(count);
return (List<ExampleModel>)tmp.list();
}
This is a really simple code to handle a limit or a list.
Criteria criteria=curdSession.createCriteria(DTOCLASS.class).addOrder(Order.desc("feild_name"));
criteria.setMaxResults(3);
List<DTOCLASS> users = (List<DTOCLASS>) criteria.list();
for (DTOCLASS user : users) {
System.out.println(user.getStart());
}
Below snippet is used to perform limit query using HQL.
Query query = session.createQuery("....");
query.setFirstResult(startPosition);
query.setMaxResults(maxRows);
You can get demo application at this link.
You can use below query
NativeQuery<Object[]> query = session.createNativeQuery("select * from employee limit ?");
query.setparameter(1,1);
#Query(nativeQuery = true,
value = "select from otp u where u.email =:email order by u.dateTime desc limit 1")
public List<otp> findOtp(#Param("email") String email);

How to change order with TypedQuery in JPA

I wish to implement pagingation with FIQL support.
I am using apache cxf with JPA(Hibernate).
Here is sample example given for it http://cxf.apache.org/docs/jax-rs-search.html#JAX-RSSearch-JPA2.0
SearchConditionVisitor<Order, TypedQuery<Order>> visitor
= new JPATypedQueryVisitor<>(em, Order.class);
// connect FIQL cxf SearchCondition with our JPA visitor
searchCondition.accept(visitor);
// creeate JPA specific TypedQuery by our visitor
TypedQuery<Order> typedQuery = visitor.getQuery();
typedQuery.setFirstResult((page * perPage) - perPage);
typedQuery.setMaxResults(perPage);
// Run the query and return matching a complex FIQL criteria
return typedQuery.getResultList();
Every thing looks working including searching and pagination.
It looks have no order by clause being use from generate sql log and seems follow database insertion order.
Now I wish to change the default sorting order. For example I wish to sort by Order id field in descending order. How can I achieve that?
I can get it working with use of CriteriaQuery.
JPACriteriaQueryVisitor<Order, Order> jpa
= new JPACriteriaQueryVisitor<>(em, Order.class, Order.class);
searchCondition.accept(jpa);
CriteriaQuery<Order> cq = jpa.getCriteriaQuery();
CriteriaBuilder cb = em.getCriteriaBuilder();
Root<Order> root = (Root<Order>) cq.getRoots().iterator().next();
cq.orderBy(cb.desc(root.get("id")));
TypedQuery<Order> query = jpa.getTypedQuery();
query.setFirstResult((page * perPage) - perPage);
query.setMaxResults(perPage);
return query.getResultList();
You cannot change the order after creating the query.
If you use MySQL, then maybe you can use an integer parameter in ORDER BY :colnum DESC to specify the column number by which to sort (starting at 1 from the selected columns only), but you cannot change direction.
PostgreSQL does not allow you to do this. I do not know how it is on other databases, though. With MySQL, the parameters are replaced by the SQL Driver, so it always receives the full query with escaped sequences. With PG, the query is first parsed by the server, the execution plan is created (including if and how to order the results) and then the parameters are sent.

Creating inner query in hibernate

how to add a set parameter() metheod inside the inner query in hibernate?
I have try to do like this but already have a errors
this is my code
Query query=session.createQuery("select eq.euipmentName,eq.type from Euipment eq where eq.id in(select euipment from Quotation qt where qt. supQuotation=:ids)");
query.setParameter("ids",id);
list = (List<Euipment>)query.list();
I've done some corrections about your query:
1. qt. supQuotation has a space, I've removed
2. euipment in you sub query haven't alias, I add qt
String hql =
"select eq.euipmentName,eq.type " +
" from Euipment eq " +
" where eq.id in (select qt.euipment from Quotation qt where qt.supQuotation = :ids)";
Query query = session.createQuery(hql);
query.setParameter("ids",id);
list = (List<Euipment>)query.list();
Tell me, if it's OK
If not OK, please post here the error, and check if you have put in hibernate mappping file your classes
From Hibernate Documentation:
Execution of native SQL queries is controlled via the SQLQuery
interface, which is obtained by calling Session.createSQLQuery().
createQuery() creates Query object using the HQL syntax.
createSQLQuery() creates Query object using the native SQL syntax.
So replace createQuery with createSQLQuery for native SQL query.
Try with Criteria
Criteria c = getSession().createCriteria(Euipment.class, "e");
c.createAlias("e.quotation", "q"); // inner join by default
c.add(Restrictions.eq("q.supQuotation", id));
list = (List<Euipment>)c.list();

Categories

Resources