I'm using javax.persistence.criteria.CriteriaBuilder and javax.persistence.criteria.CriteriaQuery to select some entities.
I now want to select only the entities that are unique which should be specified by a certain column.
There is the method javax.persistence.criteria.CriteriaQuery#distinct which only returns unique entities.
I would rather need something like
CriteriaQuery<T> distinct(String... columnNames)
Do you know how I can bake such a distinct in my JPA CriteriaQuery?
It seems to be possible with Hibernate.
The following statement has no sense:
I now want to select only the entities that are unique which should be
specified by a certain column.
The result sets are filtered by 'distinct' if they are 'exactly the same'.
The entities are not the same if only some fields are the same.
You can make distinct clause on resultset in the following manner:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery query = builder.createQuery();
Root<Friend> c = query.from(Friend.class);
query.multiselect(c.get(Friend_.firstName),c.get(Friend_.lastName)).distinct(true);
then you will get unique combination of firstName and lastName from Friend entities.
So for instance... 'Give me all unique combinations from my Friends where the firstName and lastName of the friend is unique.' But it doesn't mean give me unique friends.
distinct takes a boolean as parameter. You can use multiselect to select more than one column like in this example:
CriteriaQuery<Country> q = cb.createQuery();
Root<Country> c = q.from(Country.class);
q.multiselect(c.get("currency"), c.get("countryCode")).distinct(true);
Related
I currently have a query like this:
SELECT DISTINCT t.column1, SUM(t2.column2 IS NOT NULL)
FROM table t
LEFT OUTER JOIN table t2 on table.id = t2.id
GROUP BY column1, column2;
I am trying to implement the query using Spring JPA CriteriaBuilder. I see the CriteriaBuilder.sum() method, but I don't see a way to apply the IS NOT NULL part to the selection. Column2's data type is string.
Sample of my code
criteriaBuilder.multiselect(root.get("column1"), cb.sum(root.get("column2")));
Only in MySQL would such a query run, due MySQL’s relaxed syntax rules.
In particular, in mysql sum(column2 is not null) is a count, not a sum. The expression column2 is not null is boolean and in mysql false is 0 and true is 1, so summing this expression is a mysql hack to count the number of times column2 is not null.
To convert it to standard sql:
select
t.column1,
count(t2.column2)
from table t
left join table t2 on t.id = t2.id
group by t.column1
This works because count() (and all aggregate functions) ignore nulls.
This version also corrects the errant column in the group by clause - in any other database, your query would have produced a “grouping by aggregate expression” error.
This query will produce the same result in MySQL as your current query.
I was able to find a solution to my problem. Thanks to #bohemian for helping me write a correct sum expression.
final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<Model1> cq = cb.createQuery(Model1.class);
final Root<Model1> root = cq.from(Model1.class);
final Join<Model1, Model1> selfJoin =
root.join("tableJoinColumn", JoinType.LEFT);
selfJoin.on(...);
cq.multiselect(root.get("column1"), cb.sum(cb.selectCase()
.when(cb.isNull(selfJoin.get("column2")), 0).otherwise(1).as(Long.class)));
...
The self join required me to create an additional property on my model.
Model1.java
/**
* Property for LEFT INNER JOIN.
*/
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name="id")
private Model1 tableJoinColumn;
How to use JPA CriteriaBuilder selectCase() so that it can have Predicate as result?
Self join in criteria query
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".
I have a query that has more columns then what my entity class has.
In order to not let hibernate complaints, I have to add an annotation to the field like
#Transient
private Integer count;
But by doing this makes hibernate not able to map count. Let's say my query is
session.createSQLQuery("SELECT p.*, count(p.id), sqrt(1+2) as distance FROM post p group by p.id")
I know the query doesn't make any logical sense. This is just for example. The columns return from query above will have everything in post and two extra columns, count and distance. I wanted to map the result to my entity with count and distance are annotated with #Transient, or if there's a better way to map the result. I'm more than happy to do so. The goal is not to do this in an entity but a class with mapped result. I've tried calling addEntity() but doesn't seem to help.
You can use Result Set Transformer to achieve this.
Step 1 ) Create a new DTO class with all the fields that you query going to return
Step 2 ) Add the below line
setResultTransformer( Transformers.aliasToBean(DTO.class))
Example :
List resultWithAliasedBean = session.createQuery(
"SELECT p.*, count(p.id), sqrt(1+2) as distance FROM post p group by p.id")
.setResultTransformer(Transformers.aliasToBean(DTO.class))
.list();
DTO dto = (DTO) resultWithAliasedBean.get(0);
Note : Make sure the field names in the DTO class match the column name which your query is returning.
I see that you are using Hibernate so Yathish answer works fine.
But if you want to do it with JPA spec then you can use Result Set Mapping
Query q = em.createNativeQuery(
"SELECT c.id, c.name, COUNT(o) as orderCount, AVG(o.price) AS avgOrder " +
"FROM Customer c " +
"JOIN Orders o ON o.cid = c.id " +
"GROUP BY c.id, c.name",
"CustomerDetailsResult");
#SqlResultSetMapping(name="CustomerDetailsResult",
classes={
#ConstructorResult(targetClass=com.acme.CustomerDetails.class,
columns={
#ColumnResult(name="id"),
#ColumnResult(name="name"),
#ColumnResult(name="orderCount"),
#ColumnResult(name="avgOrder", type=Double.class)})
})
There you have to specifiy the mappin of the columns from the SQL result set to the DTO.
And if you think this is to complicated there is a open source project called QLRM (Query Lanaguage Result Mapper) that mapps any SQL statement to a POJO.
http://simasch.github.io/qlrm/
And last but not least if you will do extensive SQL processing why not have a look at jOOQ: https://www.jooq.org/
I've a class Lawsuit, that contains a List<Hearing>, each one with a Date attribute.
I need to select all the Lawsuits ordered by the date of their Hearings
I've a CriteriaQuery like
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Lawsuit> cq = cb.createQuery(Lawsuit.class);
Root<Lawsuit> root = cq.from(Lawsuit.class);
I use distinct to flatten the results:
cq.select(root).distinct(true);
I then join Lawsuit with Hearing
Join<Lawsuit, Hearing> hearing = root.join("hearings", JoinType.INNER);
to create Predicates
predicateList.add(cb.isNotNull(hearing.<Date>get("date")));
and Orders:
orderList.add(cb.asc(hearing.<Date>get("date")));
Everything works fine if I avoid distinct, but if I use it, it complains about not being able to order based on fields that are not in the SELECT:
Caused by: org.postgresql.util.PSQLException: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
The List<Hearing> is already accessible through the Lawsuit classes returned, so I'm confused: how should I add them to the select list ?
I've discovered the source of the problem somewhere else, and solving it has made unnecessary to do what asked in the question;
as described in other answers, it should be unnecessary to perform the distinct here.
The duplicate rows were originated by erroneous left joins that were performed on collections (attributes of the root object) even if the predicates were not been used:
Join<Lawsuit, Witness> witnesses = root.join("witnesses", JoinType.LEFT);
if (witnessToFilterWith!=null) {
predicateList.add(cb.equal(witnesses.<Long>get("id"),witnessToFilterWith.getId()));
}
The join should obviously be performed as inner and only if needed:
if (witnessToFilterWith!=null) {
Join<Lawsuit, Witness> witnesses = root.join("witnesses", JoinType.INNER);
predicateList.add(cb.equal(witnesses.<Long>get("id"),witnessToFilterWith.getId()));
}
So, if you're here because you're getting the same problem, search the problem in the joins.
You can also de-duplicate via group by based on primary key column of root table:
cq.groupBy(root.get("id")); // Assuming that Lawsuite.id is primary key column
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)));