Criteria API and JPA2 GroupBy issue with date - java

I am writing a query to find dates and their counts for that particular date from my entity table, and running into an issue with the groupBy statement.
Here is my criteria API calls:
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Tuple> criteriaQuery = criteriaBuilder.createTupleQuery();
Root<HtEntity> from = criteriaQuery.from(entityClassType);
Expression<String> log_date = criteriaBuilder.function("TO_CHAR",
String.class, from.get(Constants.PARAM_DATE), criteriaBuilder.literal("yyyy-MM-dd")
);
//create the select statement
criteriaQuery.select(
criteriaBuilder.tuple(
log_date.alias("log_date"),
criteriaBuilder.count(from)));
criteriaQuery.groupBy(log_date); //ISSUE HERE!!!
TypedQuery<Tuple> query = em.createQuery(criteriaQuery);
List<Tuple> results = query.getResultList();
Without trying to cast the date column with to_char and using just the date column it works, but obviously is not aggregated as I want to. I want just the date in yyyy-MM-dd and not the entire timestamp, and I can only see doing this by using groupBy which causes an error
"org.hibernate.exception.SQLGrammarException: could not extract
ResultSet"
And there is the query string that is build found from my debugger:
select function('TO_CHAR', generatedAlias0.date, :param0), count(generatedAlias0) from LogMessageEntity as generatedAlias0 group by function('TO_CHAR', generatedAlias0.date, :param1)
Please let me know what I am doing wrong. The corresponding Postgres SQL query should be working for this, as I can test it and see that it would work.

CriteriaApi has a good way of providing me with a headache, but I did end up finding a solution to this a while back, providing the answer for future curious folks:
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Object[]> criteriaQuery = criteriaBuilder.createQuery(Object[].class);
Root<MyEntity> from = criteriaQuery.from(classType);
criteriaQuery.multiselect(
from.get("date").as(java.sql.Date.class),
criteriaBuilder.count(from)
);
/*Add order by and group by clause*/
criteriaQuery.orderBy(criteriaBuilder.desc(from.get("date").as(java.sql.Date.class)));
criteriaQuery.groupBy(from.get("date").as(java.sql.Date.class));
TypedQuery<Object[]> query = em.createQuery(criteriaQuery);
List<Object[]> results = query.getResultList();
You will notice that I am casting the column projecting in my multi-select statement, and my groupBy as a java.sql.Date type. CriteriaApi has a strict type-safe format such that you cannot mix types for the groupBy and the select statement if you are aggregating on that specific column. My issue was that I was selecting on the date column with a function call, and also trying to apply that to the groupBy.
While the generated query statement works via command line, it does not work for the CriteriaApi. The reason it cannot generate a ResultSet is due to it assuming the projection and aggregation in the groupBy are not the same type (I think) and therefore, if you need to use a function on a column in your select statement and also aggregate on that column you need to either think of a way you can avoid using the function (which is what I did), or generate a native query with the Hibernate EntityManager object you have.
It is unfortunate, but I believe CriteriaApi has a number of limitations with this being one of them. It should probably be submit to their team as a bug, but I'm not sure where to do this.

Old question, but the issue is still relevant. I've tried your solution, but it didn't work for me. What did work is the trunc function, instead of the to_char:
Path<FeedbackType> feedbackTypePath = itemRoot.get("feedbackType");
final Expression date = criteriaBuilder.function("trunc", java.sql.Date.class, votesJoin.get("creationDate"));
selects.add(feedbackTypePath);
selects.add(date);
selects.add(criteriaBuilder.count(itemRoot.get("id")));
criteriaQuery.multiselect(selects);
criteriaQuery.where(criteriaBuilder.equal(votesJoin.get("creationDate"), oldestVoteSubquery));
criteriaQuery.groupBy(feedbackTypePath, date);
It looks like the criteriabuilder does not like the literal in the groupBy.

Related

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.

JPA Criteria API: how to retrieve date in "mm/dd/yyyy" format

I want to retrieve one of my column date in "mm/dd/yyyy" format. This column is currently returning date and time both but i want only date in "mm/dd/yyy" format.
Below is my postgresql query that i want to convert to criteria api
select DISTINCT c.name as Facility,
to_char(begin_exam,'mm/dd/yyyy') as begin_exam
from a inner join b on a.rad_exam_id = b.id
inner join c on c.id = b.site_id
group by c.name,to_char(begin_exam,'mm/dd/yyyy')
order by c.name,to_char(begin_exam,'mm/dd/yyyy')
I searched on the internet a lot but did't find any solution that will help me. please help me in writing criteria api query for this.
Criteria API defines function expression to execute native SQL functions in the CriteriaBuilder interface as follows:
<T> Expression<T> function(String name, Class<T> type, Expression<?>... args);
where name is the name of the SQL function, type is the expected return type and args is a variable list of arguments (if any).
Here is an example how to use it in a Criteria query:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery(String.class);
Root<RadExamTimes> root = cq.from(RadExamTimes.class);
cq.select( cb.function("to_char", String.class, root.get("begin_exam"), cb.literal("MM/DD/YYYY")));
TypedQuery<String> query = entityManager.createQuery(cq);
List<String> result = query.getResultList();
where
RadExamTimes: a hypothetical root entity
MM/DD/YYYY: a database-specific format (in this example
Postgresql date format; for Oracle use Ora format, etc)
to_char: Postgresql function to convert date value to string
begin_exam: the date field to be formatted
The format string cannot be passed as is so that the literal() method is used to wrap it.
Note: The above example is tested on MySQL database with MySQL function and corresponding date format; but the example changed to match Postgresql syntax.
SELECT '2001-02-16 20:38:40'::date;
date
----------------
2001-02-16
(1 row)
Or you can use #TemporalType on JPA entity field.
If you want to display the column date in "mm/dd/yyyy" format for the sake of displaying it in the front end. Following might work on this case.
public class SomeDTO {
#JsonFormat(pattern="mm/dd/yyyy")
private Date someDate;
}

How to generate "select count(*) ..." using CriteriaQuery

I wrote the following code to get the count of "ExampleEntity":
cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> cq = cb.createQuery(Long.class);
root = cq.from(ExampleEntity);
cq.select(cb.count(root));
return entityManager.createQuery(cq).getSingleResult();
Generated sql:
Hibernate: select count(exampleen0_.id) as col_0_0_ from EXAMPLEENTITY exampleen0_
But, for performance reasons (using a Oracle 11g database),I need to generate the following sql:
Desired sql: select count(*) as col_0_0_ from EXAMPLEENTITY exampleen0_
It's quite simple to do creating JPQL queries, but I would have to rewrite a lot of existing code for filters.
How can I generate "count(*)" instead of "count(exampleen0_.id)" using CriteriaQuery?
It's extremely hard to force the hand of the SQL generated by Hibernate. Instead, I would consider writing a native query.
Query query = entityManager.createNativeQuery(
"SELECT COUNT(*) FROM EXAMPLEENTITY", Long.class);
return query.getSingleResult();
Note: Native queries can get messy when using more complex logic, but this particular one complies to the ANSI standard and should run without issue against any commonly used DB without having to worry about any db-specific syntax issues.
It works in Oracle...
BigDecimal count = (BigDecimal) manager.createNativeQuery(query).getSingleResult();

Criteria API limit results in subquery

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', ...).

JPQL query containing LIKE converted into criteria

I need to use the LIKE operator into an JPA query. I need to use it for types other then String but the JPA criteria API allows me to add only String parameters. I tried using the .as(String.class) but something fails and also tried calling the CAST function from the underlying Oracle that again fails for unknown reasons to me.
I tried writing the query also in JPQL and it works as expected. This is the query:
SELECT p from CustomerOrder p where p.id like '%62%'
UPDATE:
The query must be built in a generic fashion as it is for filtering, so it needs to be created at runtime. On the query that is already created I tried to add the LIKE clause like this:
query.where(builder.like(selectAttributePath.as(String.class), "%"+filterValue.toString().toLowerCase()+"%"));
But this crashes with this exception:
org.hibernate.hql.internal.ast.QuerySyntaxException: expecting CLOSE, found '(' near line 1, column 156 [select distinct generatedAlias0.id from de.brueckner.mms.proddetailschedact.data.CustomerOrder as generatedAlias0 where cast(generatedAlias0.id as varchar2(255 char)) like :param0]
I executed the same query directly to Oracle using SQLDeveloper, so it should be sound from this point of view. So the problem is the Hibernate is the issue. Any suggestions on how to fix it?
How can I write this query using JPA Criteria?
I fixed the problem by invoking the 'TO_CHAR' function from the underlying Oracle DB and using the LIKE operator like for normal String's.
query.where(builder.like(selectAttributePath.as(String.class), "%" +filterValue.toString().toLowerCase() + "%")
You can try the below code, it might require modifications.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<CustomerOrder> cq = cb.createQuery(CustomerOrder.class);
Root<CustomerOrder> order = cq.from(CustomerOrder.class);
cq.where(cb.like(Long.valueOf(order.get(CustomerOrder_.id)).toString(), "%62%"));
TypedQuery<CustomerOrder> q = em.createQuery(cq);
List<CustomerOrder> results = q.getResultList();

Categories

Resources