CriteriaBuilder greatest date only - java

I'm trying to understand how to select ONLY the latest date on a record from a join. My People entity is joined with a Membership entity. My Membership entity has a RefMembershipStatus entity.. I an trying to select ONLY the most current date from the Membership entity... My joins look as follows:
Join<People, Membership> membershipPath = root.join(People_.membershipList);
//Membership has property: Membership_.membershipStatusDate -- I must retrieve ONLY the latest (most current) date in membershipStatusDate..
Join<Membership, RefMembershipStatus> progPath = membershipPath.join(Membership_.refMembershipStatus);
predicateList.add(cb.and(progPath.in(selectedStatus)));

There are two ways how you can do this.
Either you need to add a predicate which finds the max date. In the CriteriaBuilder API you would use the greatest method. It would look something like this:
Root<Membership> membership = criteria.from(Membership.class);
predicateList.add(cb.greatest(membership.get(Membership_.membershipStatusDate)));
Or you could use orderBy and then just select the first result with getFirstResult:
criteriaQuery.orderBy(cb.desc(membership.get(Membership_.membershipStatusDate)));
entityManager.createQuery(criteriaQuery).getFirstResult();

Related

Comparing two columns in Hibernate Criteria

I have a table in Postgre Database. The table has 2 timestamp format columns. I need those rows where one column timestamp is not equals the second column timestamp.
For the below query I need the Hibernate criteria restrictions.
select * from A where column1<>column2
Did you try something like that?
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Foo> cQuery = builder.createQuery(Foo.class);
Root<Foo> from = cQuery.from(Foo.class);
CriteriaQuery<Foo> select = cQuery.select(from);
select.where(builder.notEqual(from.get("col1"), from.get("col2")));
List<Foo> result = entityManager.createQuery(select).getResultList();
Given your comment here, you'll have to do a full table scan, which will create troubles with a bigger table. You will likely need to change your approach altogether, and introduce a field which says something like "IsUpdated". Or, when creating that record, UpdatedTime should be set to null, indicating it hasn't been updated yet. Then, you can do a query like "give all records where UpdatedTime is not null".

JPA CriteriaQuery with LEAST and GREATEST functions

I am have a problem where i need to join two tables using the LEAST and GREATEST functions, but using JPA CriteriaQuery. Here is the SQL that i am trying to duplicate...
select * from TABLE_A a
inner join TABLE_X x on
(
a.COL_1 = least(x.COL_Y, x.COL_Z)
and
a.COL_2 = greatest(x.COL_Y, x.COL_Z)
);
I have looked at CriteriaBuilder.least(..) and greatest(..), but am having a difficult time trying to understand how to create the Expression<T> to pass to either function.
The simplest way to compare two columns and get the least/greatest value is to use the CASE statement.
In JPQL, the query would look like
select a from EntityA a join a.entityXList x
where a.numValueA=CASE WHEN x.numValueY <= x.numValueZ THEN x.numValueY ELSE x.numValueZ END
and a.numValueB=CASE WHEN x.numValueY >= x.numValueZ THEN x.numValueY ELSE x.numValueZ END
You can code the equivalent using CriteriaBuilder.selectCase() but I've never been a big fan of CriteriaBuilder. If requirements forces you to use CriteriaBuilder then please let me know and I can try to code the equivalent.
CriteriaBuilder least/greatest is meant to get the min/max value of all the entries in one column. Let's say you want to get the Entity that had the alphabetically greatest String name. The code would look like
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery query = cb.createQuery(EntityX.class);
Root<EntityX> root = query.from(EntityX.class);
Subquery<String> maxSubQuery = query.subquery(String.class);
Root<EntityX> fromEntityX = maxSubQuery.from(EntityX.class);
maxSubQuery.select(cb.greatest(fromEntityX.get(EntityX_.nameX)));
query.where(cb.equal(root.get(EntityX_.nameX), maxSubQuery));
I created a sample Spring Data JPA app that demonstrates these JPA examples at
https://github.com/juttayaya/stackoverflow/tree/master/JpaQueryTest
It turns out that CriteriaBuilder does support calling LEAST and GREATEST as non-aggregate functions, and can be accessed by using the CriteriaBuilder.function(..), as shown here:
Predicate greatestPred = cb.equal(pathA.get(TableA_.col2),
cb.function("greatest", String.class,
pathX.get(TableX_.colY), pathX.get(TableX_.colZ)));

Select records which doesn't have specific records in joined table

Hi I'm trying to select records from one table which doesn't have records in connected many-to-many table with specific values.
I will explain on sample tables:
documentation:
id_documentation
irrelevant_data
user:
id_user
irrelevant_data
documentation_user:
id_documentation
id_user
role
What I want to achieve is to select every single documentation which doesn't have user in specific role. Any ideas?
The main problem is that I'm using java's CriteriaBuilder to create query so using subqueries is impossible (I think).
You can add restrictions on your left join using: createAlias(java.lang.String, java.lang.String, int, org.hibernate.criterion.Criterion) method, see API.
Check this answer for an example on how to use the left join with a criteria.
Main problem does not exist - Criteria API do have SubQuery. Query itself selects instances of User and uses not in construct to limit results based to subquery. Subquery selects all users that are connected to document with specific role via DocumentationUser.
Try something like this (code not tested):
CriteriaQuery<Documentation> cq = cb.createQuery(Documentation.class);
Root<Documentation> u = cq.from(Documentation.class);
Subquery<Integer> sq = cq.subquery(Integer.class);
Root<User> su = sq.from(User.class);
sq.select(su.get("id_user"));
Join<User, DocumentationUser> du = su.join("documentationUserCollection");
sq.where(cb.equals(du.get("role"), "mySpecificRole"));
cq.where(cb.not(cb.in(u.get("id_user")).value(sq)));
See also this useful answer on SO.

If/Case statement in JPA Criteria Builder

I want to build up a query which search dates on different entites. My structure is:
Contract has date (non nullable)
Employee has date (non nullable)
Employee may have a contract id (nullable)
If a Employee has a contract I want to retrieve the contract date. If an employee does not have a contract then I want to return the Employee date.
My code so far is:
if (inputDate!= null) {
ParameterExpression<Date> exp = criteriaBuilder.parameter(Date.class, "inputDate");
criteria.add(criteriaBuilder.or(
criteriaBuilder.isNull(employee.get("contract")),
criteriaBuilder.lessThanOrEqualTo(employee.<Date>get("creationDate"), exp), criteriaBuilder.lessThanOrEqualTo((employee.join("contract").<Date>get("fromDate")), exp) ));}
This does not seem to work though. I always appear to go into the isNull which I do not expect.
I am happy to look into this some more but I guess my question is whether this is the correct way of going about it. Is it? I have seen a selectCase as well in criteriaBuilder so perhaps that may be a better solution.
Any pointers would be greatly received.
Thanks
Here would be a solution, not sure if it works, but we will manage to get it work with your help :):
ParameterExpression<Date> inputDateExp = criteriaBuilder.parameter(Date.class, "inputDate");
Predicate employeeCreationDate = criteriaBuilder.lessThanOrEqualTo(
employee.<Date>get("creationDate"), inputDateExp);
Predicate contractStartDate = criteriaBuilder.lessThanOrEqualTo(
employee.join("contract").<Date>get("fromDate"), inputDateExp);
criteria.add(
criteriaBuilder.selectCase().when(employee.get("contract").isNull(), employeeCreationDate).otherwise(contractStartDate));
I do not understand why use "inputDate" as Expression instead of a Date? Also, I suggest renaming criteria to c and criteriaBuilder to cb, this would save space and I think it is more comfortable.
I think what you want to do is use Case<Date>, and use Predicate only as first argument to Case#when. Case<Date> is an Expression<Date>, which is what you want to pass into CriteriaQuery#multiselect.
CriteriaQuery<Employee> query = criteriaBuilder.createQuery(Employee.class);
Root<Employee> employee = query.from(Employee.class);
query.multiselect
(
criteriaBuilder.selectCase()
.when(criteriaBuilder.isNull(employee.get("contract")), employee.get("creationDate"))
.otherwise(employee.join("contract").get("fromDate"))
);

Referring to an earlier aliased field in a criteria query

In this query:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> q = cb.createTupleQuery();
// FROM GamePlayedEvent gpe
Root<GamePlayedEvent> gpe = q.from(GamePlayedEvent.class);
// SELECT gameId, COUNT(*) AS count, AVG(duration)
// AS avDur, AVG(rewardCurrency) AS avCur, AVG(rewardXP) avXp
q.select(cb.tuple(
gpe.<String>get("gameId"),
cb.count(gpe).alias("count"),
cb.avg(gpe.<Double>get("duration")).alias("avDur"),
cb.avg(gpe.<Integer>get("rewardCurrency")).alias("avCur"),
cb.avg(gpe.<Integer>get("rewardXp")).alias("avXp")
));
// WHERE loginTime BETWEEN ...
q.where(cb.between(gpe.<Date>get("time"), fromTime, toTime));
// GROUP BY gameId
q.groupBy(gpe.<String>get("gameId"));
// ORDER BY count DESC
q.orderBy(cb.desc(???));
How can I add the ORDER BY count DESC, referring to the "count" defined in the SELECT clause?
What if you just captured the count expression, and used it directly?
Expression event_count = cb.count(gpe);
q.select(cb.tuple(
gpe.<String>get("gameId"),
event_count,
...
));
q.orderBy(cb.desc(event_count));
I came across the same problem today but none of the suggested solutions worked for me because I needed to reuse the expression not only in the order by clause but also in the group by clause.
One obvious solution would be to create a view on the database level but this is a bit clumsy, creates an unnecessary subquery and even not possible if the db user isn't granted enough privileges. A better option which I ended up implementing is to write something like this
q.select(cb.tuple(
gpe.<String>get("gameId"),
cb.count(gpe),
...
)).groupBy(cb.literal(2)).orderBy(cb.literal(2));
The first downside of this approach is that the code is errorprone. The other drawback is that the generated sql query contains ordinal position notation, which works on some databases (like postgresql or mariadb) but doesn't work on others (like sql server). In my case, however, I found this to be the best option.
Tested on jpa 2.1 with hibernate 5.2.3 as a provider and postgresql 9.6.
Even though the Pro JPA 2 book describes that the alias method can be used to generate a sql query alias (on page 251) I have had no success with making it work with neither EclipseLink or Hibernate. For your question I would say that your orderBy line should read:
q.orderBy(cb.desc(cb.count(gpe));
if it was supported by the different vendors.
As far as my research goes it seams that the alias method is only used for naming elements in the tuble used in the select (so only for projection).
I have one question though. Why would you want to use the JPA Criteria API for this query. It (the query) seams to be static in nature so why not use JPQL where you can define your query aliases directly.
Have you tried setting up a projection with an alias?
criteria.setProjection(Projections.projectionList()
.add(Projections.count("item.id"), "countItems"));
criteria.addOrder(Order.desc("countItems"));
For a sum aggregation field I have the following code which worked for me:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<T> cq = cb.createQuery(entity);
Root<T> root = cq.from(entity);
cq.orderBy(cb.desc(cb.sum(root.get(orderByString))));
// orderByString is string entity field that is being aggregated and which we want to put in orderby clause as well.

Categories