UNION to JPA Query - java

Is it possible to query "UNION" in JPA and even "Criteria Builder"?
I'm looking for examples, but so far i got no result.
Does anyone have any examples of how to use it?
Or would that be with native sql?

SQL supports UNION, but JPA 2.0 JPQL does not. Most unions can be done in terms of joins, but some can not, and some are more difficult to express using joins.
EclipseLink supports UNION.

Depending on the case, one could use sub queries, something like:
select e
from Entity e
where e.id in
(
select e.id
from Entity2 e2
join e2.entity e
where e2.someProperty = 'value'
)
or e.id in
(
select e.id
from Entity3 e3
join e3.entity e
where e3.someProperty = 'value2'
)

One thing just comes to my mind (searching for the exact same problem):
Perform two different JPA queries on the same entity mapping and simply add the objects of the second result to the list (or set to avoid duplicates) of the first result.
That way you get the same effect as with a UNION, the difference being that you use two SQL statements instead of one. But actually, I would expect that to perform just as good as issueing one UNION statement.

write native query (set it true , default its false) - ex.
String findQuery = "select xyz from abc union select abc from def"
#Query(value = findQuery, nativeQuery = true)
//method

using EntityManager.createNativeQuery(...);
It's allow you use UNION

There is no direct union for JPA, what I did was to build two specifications.
Specification<Task> specification = Specification.where(null);
Specification<Task> specification2 = Specification.where(null;
They belong to a single table but return different values
specification = specification.and((root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(criteriaBuilder.function(MONTH, Integer.class, root.get("deliveryExpirationDate")), month));
specification2 = specification2.and((root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.lessThan(criteriaBuilder.function(MONTH, Integer.class, root.get("deliveryExpirationDate")), month))
.and((root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(root.get("enable"), true));
for this example it is a table of tasks that in the first specification I need the tasks of the current month enabled and disabled, and in the second specification I only need the tasks enabled of the previous months.
Specification<Task> specificationFullJoin = Specification.where(specification).or(specification2);
Esto es muy útil para que la lista de tareas devueltas tenga paginación.
taskRepository.findAll(specificationFullJoin, pageable).map(TaskResponse::new); //Here you can continue adding filters, sort or search.
It helps me a lot, I hope it is what they are looking for or that it serves them something.

I have solved this in my project.
Union/Union All will work if you change it to native query and use like below
//In Your Entity class
#NamedNativeQuery(name="EntityClassName.functionName",
query="your_native_query")
//In your Repository class
#Query(native=true)
List<Result> functionName();
Below method of defining Native query in JPA repository will not solve this problem
#Query(value="your_native_query", native=true)
will not

You can directly use UNION in your query.
The following query returns id of friends from FRIENDSHIP table.
Eg:
TypedQuery<String> queryFriend = em.createQuery("SELECT f.Id FROM Friendship f WHERE f.frStuId = :stuId UNION "
+ "SELECT f.frStuId FROM Friendship f WHERE f.FriendId = :stuId", String.class);
queryFriend.setParameter("stuId", stuId);
List<String> futureFriends = queryFriend.getResultList();

Related

How to map result got from native query in hibernate?

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/

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)));

Join cartesian product

Context
I send some files to some enterprises every week. I need to restitute for each week and each enterprise whether the file is sent or not.
Tables
ent (enterprise)
wek (week)
fil (file : references wek and ent)
Solution with pure SQL
Make a cartesian product between ent and wek then left outer join fil. This works :
select * from
(
select * from wek, ent e
) as t1
left join fil f
on f.ent_id = t1.ent_id
and f.wek_id = t1.wek_id
Problem :
How to translate this into JPA (in the CriteriaBuilder way)?
For example, if I try :
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<ResultClass> query = cb.createQuery(ResultClass.class);
Root<Week> week = query.from(Week.class);
Root<Enterprise> query.from(Enterprise.class);
Expression<???> cartesianProduct = ??? //How?
cartesianProduct.leftJoin(???_.file);
query.where(
cb.equal(file.get(File_.wek_id), week.get(Week.week_id));
)
Using 2 "from" clauses gives me the cartesian product but how do I left join this result?
Unstatisfaying solution :
Create a view :
CREATE VIEW view_ent_wek AS
SELECT ent_id as ent_id, wek_id as wek_id, ent_id || '-' || ent_id as id
FROM ent, wek;
Map it to an entity :
#Entity
#Table(name = "view_ent_wek")
public class WeekEnterprise {
#Id
#Column(name = "id")
private String id;
#ManyToOne
#JoinColumn(name = "ent_id")
private Enterprise enterprise;
#ManyToOne
#JoinColumn(name = "wek_id")
private Week week;
[...]
Then I can use it in a query :
Root<WeekEnterprise> weekEnterprise = query.from(WeekEnterprise.class);
weekEnterprise.join(...)
I don't like this solution because it makes me create a view that is obviously not necessary. Any idea?
I don't see the point of doing the reporting-style SQL query with JPA, let alone with Criteria Query.
Of course, JPA (and its implementations) do support tuple queries (as opposed to entity queries, see also: Hibernate tuple criteria queries), but tuple queries is not what JPA is good at. Imagine you want to change that report and add more calculations. Wouldn't it be much easier to do that with SQL?
Using JPA for mapping to Java objects and for transaction modelling still makes sense, and thus, your SQL view solution is already quite good, or if you prefer not to store the SQL in a view, use JPA's native query API:
List<Object[]> list = em.createNativeQuery(sql).getResultList();
Or:
List<WeekEnterprise> list = em.createNativeQuery(sql, WeekEnterprise.class)
.getResultList();
I have some ideas:
With 2 JPA queries. First: fetch the cartesian product of wek and enterprises. Second: take an inner join between wek, enterprises and files. Uses a map (wek_id+ent_id => Tuple(Wek, Ent, File)) to quickly identify where to put the file.
Write plain SQL queries and execute them with the JPA API.
(Didn't think much about this one) Create a back reference from Wek and Ent to File (lazy loaded) and then you should be able to continue your first idea.
Don't know much about JPA, but about Postgres. And I would suggest this superior query:
SELECT *
FROM wek CROSS JOIN ent
LEFT JOIN fil USING (ent_id, wek_id);
You don't need the subselect.
The only difference in the result (besides being shorter and faster): You get the columns ent_id and wek_id once in the output (which might solve a problem with duplicated column names).
Should be easy to translate to JPA now.

Union is not working in HQL [duplicate]

What alternatives do I have to implement a union query using hibernate? I know hibernate does not support union queries at the moment, right now the only way I see to make a union is to use a view table.
The other option is to use plain jdbc, but this way I would loose all my example/criteria queries goodies, as well as the hibernate mapping validation that hibernate performs against the tables/columns.
You could use id in (select id from ...) or id in (select id from ...)
e.g. instead of non-working
from Person p where p.name="Joe"
union
from Person p join p.children c where c.name="Joe"
you could do
from Person p
where p.id in (select p1.id from Person p1 where p1.name="Joe")
or p.id in (select p2.id from Person p2 join p2.children c where c.name="Joe");
At least using MySQL, you will run into performance problems with it later, though. It's sometimes easier to do a poor man's join on two queries instead:
// use set for uniqueness
Set<Person> people = new HashSet<Person>((List<Person>) query1.list());
people.addAll((List<Person>) query2.list());
return new ArrayList<Person>(people);
It's often better to do two simple queries than one complex one.
EDIT:
to give an example, here is the EXPLAIN output of the resulting MySQL query from the subselect solution:
mysql> explain
select p.* from PERSON p
where p.id in (select p1.id from PERSON p1 where p1.name = "Joe")
or p.id in (select p2.id from PERSON p2
join CHILDREN c on p2.id = c.parent where c.name="Joe") \G
*************************** 1. row ***************************
id: 1
select_type: PRIMARY
table: a
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 247554
Extra: Using where
*************************** 2. row ***************************
id: 3
select_type: DEPENDENT SUBQUERY
table: NULL
type: NULL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: NULL
Extra: Impossible WHERE noticed after reading const tables
*************************** 3. row ***************************
id: 2
select_type: DEPENDENT SUBQUERY
table: a1
type: unique_subquery
possible_keys: PRIMARY,name,sortname
key: PRIMARY
key_len: 4
ref: func
rows: 1
Extra: Using where
3 rows in set (0.00 sec)
Most importantly, 1. row doesn't use any index and considers 200k+ rows. Bad! Execution of this query took 0.7s wheres both subqueries are in the milliseconds.
Use VIEW. The same classes can be mapped to different tables/views using entity name, so you won't even have much of a duplication. Being there, done that, works OK.
Plain JDBC has another hidden problem: it's unaware of Hibernate session cache, so if something got cached till the end of the transaction and not flushed from Hibernate session, JDBC query won't find it. Could be very puzzling sometimes.
I have to agree with Vladimir. I too looked into using UNION in HQL and couldn't find a way around it. The odd thing was that I could find (in the Hibernate FAQ) that UNION is unsupported, bug reports pertaining to UNION marked 'fixed', newsgroups of people saying that the statements would be truncated at UNION, and other newsgroups of people reporting it works fine...
After a day of mucking with it, I ended up porting my HQL back to plain SQL, but doing it in a View in the database would be a good option. In my case, parts of the query were dynamically generated, so I had to build the SQL in the code instead.
I have a solution for one critical scenario (for which I struggled a lot )with union in HQL .
e.g. Instead of not working :-
select i , j from A a , (select i , j from B union select i , j from C) d where a.i = d.i
OR
select i , j from A a JOIN (select i , j from B union select i , j from C) d on a.i = d.i
YOU could do in Hibernate HQL ->
Query q1 =session.createQuery(select i , j from A a JOIN B b on a.i = b.i)
List l1 = q1.list();
Query q2 = session.createQuery(select i , j from A a JOIN C b on a.i = b.i)
List l2 = q2.list();
then u can add both list ->
l1.addAll(l2);
A view is a better approach but since hql typically returns a List or Set... you can do list_1.addAll(list_2). Totally sucks compared to a union but should work.
Perhaps I had a more straight-forward problem to solve. My 'for instance' was in JPA with Hibernate as the JPA provider.
I split the three selects (two in a second case) into multiple select and combined the collections returned myself, effectively replacing a 'union all'.
Hibernate 6 added support for UNION.
So, you can now use UNION in JPQL queries like this:
List<String> topics = entityManager.createQuery("""
select c.name as name
from Category c
union
select t.name as name
from Tag t
""", String.class)
.getResultList();
And you can also also use UNION ALL if there are no duplicates to be removed:
List<String> topics = entityManager.createQuery("""
select c.name as name
from Category c
union all
select t.name as name
from Tag t
""", String.class)
.getResultList();
Besides UNION, you can also use EXCEPT and INTERSECT.
I too have been through this pain - if the query is dynamically generated (e.g. Hibernate Criteria) then I couldn't find a practical way to do it.
The good news for me was that I was only investigating union to solve a performance problem when using an 'or' in an Oracle database.
The solution Patrick posted (combining the results programmatically using a set) while ugly (especially since I wanted to do results paging as well) was adequate for me.
Here is a special case, but might inspire you to create your own work around. The goal here is to count the total number of records from two different tables where records meet a particular criteria. I believe this technique will work for any case where you need to aggregate data from across multiple tables/sources.
I have some special intermediate classes setup, so the code which calls the named query is short and sweet, but you can use whatever method you normally use in conjunction with named queries to execute your query.
QueryParms parms=new QueryParms();
parms.put("PROCDATE",PROCDATE);
Long pixelAll = ((SourceCount)Fetch.row("PIXEL_ALL",parms,logger)).getCOUNT();
As you can see here, the named query begins to look an aweful lot like a union statement:
#Entity
#NamedQueries({
#NamedQuery(
name ="PIXEL_ALL",
query = "" +
" SELECT new SourceCount(" +
" (select count(a) from PIXEL_LOG_CURR1 a " +
" where to_char(a.TIMESTAMP, 'YYYYMMDD') = :PROCDATE " +
" )," +
" (select count(b) from PIXEL_LOG_CURR2 b" +
" where to_char(b.TIMESTAMP, 'YYYYMMDD') = :PROCDATE " +
" )" +
") from Dual1" +
""
)
})
public class SourceCount {
#Id
private Long COUNT;
public SourceCount(Long COUNT1, Long COUNT2) {
this.COUNT = COUNT1+COUNT2;
}
public Long getCOUNT() {
return COUNT;
}
public void setCOUNT(Long COUNT) {
this.COUNT = COUNT;
}
}
Part of the magic here is to create a dummy table and insert one record into it. In my case, I named it dual1 because my database is Oracle, but I don't think it matters what you call the dummy table.
#Entity
#Table(name="DUAL1")
public class Dual1 {
#Id
Long ID;
}
Don't forget to insert your dummy record:
SQL> insert into dual1 values (1);
As Patrick said, appending the LISTs from each SELECT would be a good idea but remember that it acts like UNION ALL. To avoid this side effect, just control if the object is already added in final collection or not. If no, then add it.
Something else that you should care about is that if you have any JOIN in each SELECT, the result would be a list of object array(List<Object[]>) so you have to iterate over it to only keep the object that you need.
Hope it works.

HQL/SQL/Criteria to join-match all records in a given list while selecting all fields

I'm trying to write a HQL/Criteria/Native SQL query that will return all Employees that are assigned to a list of Projects. They must be assigned to all Projects in order to be selected.
An acceptable way of achieving this with native SQL can be found in the answer to this question: T-SQL - How to write query to get records that match ALL records in a many to many join:
SELECT e.id
FROM employee e
INNER JOIN proj_assignment a
ON e.id = a.emp_id and a.proj_id IN ([list of project ids])
GROUP BY e.id
HAVING COUNT(*) = [size of list of project ids]
However, I want to select all fields of Employee (e.*). It's not possible to define SQL grouping by all the columns(GROUP BY e.*), DISTINCT should be used instead. Is there a way to use DISTINCT altogether with COUNT(*) to achieve what I want?
I've also tried using HQL to perform this query. The Employee and ProjectAssignment classes don't have an association, so it's not possible to use Criteria to join them. I use a cross join because it's the way to perform a Join without association in HQL. So, my HQL looks like
select emp from Employee emp, ProjectAssignment pa
where emp.id = pa.empId and pa.paId IN :list
group by emp having count(*) = :listSize
However, due to a bug in Hibernate, GROUP BY entity does not work. The SQL it outputs is something like group by (emptable.id).
Subquerying the assignment table for each project (dynamically adding and exists (select 1 from proj_assignment pa where pa.emp_id=e.id and pa.proj_id = [anId]) for each project in the list) is not an acceptable option.
Is there a way to write this query properly, preferrably in HQL (in the end I want a List<Employee>), without modifying mappings and without explicitly selecting all columns in the native SQL ?
EDIT: I'm using Oracle 10g and hibernate-annotations-3.3.1.GA
How about:
select * from employee x where x.id in(
SELECT e.id
FROM employee e
INNER JOIN proj_assignment a
ON e.id = a.emp_id and a.proj_id IN ([list of project ids])
GROUP BY e.id
HAVING COUNT(*) = [size of list of project ids]
)
I've found an alternative way to achieve this in HQL, it's far more inefficient than what I'd like, (and than what is really possible without that nasty bug) but at least it works. It's better than repeating subselects for each project like
and exists (select 1 from project_assignment pa where pa.id = someId and pa.emp_id = e.id)
It consists of performing a self-join subquery in order to find out, for each of the Employees, how many of the projects in the list they are assigned to, and restrict results to only those that are in all of them.
select e
from Employee
where :listSize =
(select distinct count(*)
from Employee e2, ProjectAssignment pa
where
e2.id = pa.id_emp and
e.id = e2.id
and pa.proj_id IN :projectIdList
)

Categories