JPA: Left join not working with "is null" in where clause - java

I am using EclipseLink (2.4, 2.5 and 2.6) on a very simple project where I have a Department entity and each Department links to an Employee entity which is the manager of the Department.
I am currently unable to make this simple query work:
select d from Department d where d.manager is null
Returns 1 row
select d from Department d left join fetch d.manager where d.manager is null
Returns 0 row
I am using Eclipselink over an H2 database. The SQL query generated does not seem to create a left join but rather an inner join which obviously will fail.
SELECT t1.ID, t1.MANAGER_ID, t0.ID, t0.NAME FROM EMPLOYEE t0, DEPARTMENT t1
WHERE ((t1.MANAGER_ID IS NULL) AND (t0.ID = t1.MANAGER_ID))
Is this a bug or is it something wanted? Or could someone help me fixing this?
Happy to provide the code and example if anyone wants it, or more information.

It seems unsafe to reference an entity that was returned in a query as a side effect by using a FETCH JOIN. The JPA specification tries to prohibit such queries at least for multi-valued associations (JSR 338, section 4.4.5.3):
It is not permitted to specify an identification variable for the objects referenced by the right side of the FETCH JOIN clause, and hence references to the implicitly fetched entities or elements cannot appear elsewhere in the query.
Your query executed on EclipseLink and Hibernate yields different results (EclipseLink 2.6.3: no results, Hibernate 4.3.11: all departments with no manager):
select d from Department d left join fetch d.manager where d.manager is null
To solve your problem a subquery could be used. A portable JPQL query could look like this (same results in EclipseLink and Hibernate):
select d1 from Department d1 left join fetch d1.manager where exists
(select d2 from Department d2 where d2.manager is null and d1 = d2)

Related

Spring Data JPA Specifications - Distinct + order by column in join

I have some specifications that I am combining with "and":
Specification.where(predicate1).and(predicate2).and(predicate3);
One of them has distinct set :
query.distinct(true);
Another one makes an order by on a column that is in a join.
query.orderBy(builder.desc(bJoin.get("orderbyColumn")));
This fails with a SQLGrammarException stating that order by column should be in distinct.
So basically we have entity A, the main entity, and some nested entity B, we select from A but want to order by B, and in generated sql it only selects columns from A. The only way I found to make it work (= making it select from B as well) is to replace the join by a fetch :
Fetch < A, B > bFetch = root.fetch("joinCol", JoinType.INNER);
Join < A, B > bJoin = (Join < A, B > ) bFetch;
that worked for some time, was testing locally in H2, but then after some time started getting another error :
org.hibernate.QueryException: query specified join fetching, but the
owner of the fetched association was not present in the select list
I solved it somehow in my local pointing to H2 by requiring some columns to not be null, but in real server using PostgreSQL, it's not working at all, getting that error for all cases when a fetch is present.
My question is : what is the right way to use distinct along with orderby on a nested entity that is not fetched? Is my solution with fetch ok and it just needs to be fixed (and if so how?) or I should go for another option entirely?
For the actual query I am using this method :
findAll(Specification<>, Pageable)
Isn't there a way to have distinct wrapping the whole query with order by (some sort of subquery?) and bypassing all this nightmare? Have it generate a query like this:
select distinct colA1, colA2, coAl3 from (select colA1, colA2, coAl3
from A inner join B b on ........ order by b.colB1)
Do I need to convert my specification to predicate manually and do something else with it to try to solve my issues (some kind of hybrid approach)?
Any pieces of advice will be greatly appreciated.
I encountered same error but actually it was not error :)
findAll(Specification<>, Pageable) this method throws 2 different queries.
First one is count query where you have to be careful.
Second is the rows query where you actually did it.
You can check the query type with code below
if (query.getResultType() != Long.class && query.getResultType() != long.class){
root.fetch("entity1");
}

#Query for joining two tables

I am trying to join two entities in a third one using #Query method.
#Query("SELECT new com.concretepage.entity.DeptEmpDto(d.departmentId,d.departmentName,d.managerId,d.locationId,e.employeeId,e.firstName,e.lastName,e.phoneNumber,e.hireDate,e.jobId,e.salary,e.commissionPct) FROM Employee e INNER JOIN Department d")
List <DeptEmpDto> fetchEmpDeptDataInnerJoin();
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '' at line 1.
I cannot understand where is my mistake.Any help will be appreciated :).
You missed the joining condition after joining tables using ON clause. So just change your query with this:
#Query("SELECT new com.concretepage.entity.DeptEmpDto(d.departmentId,d.departmentName,d.managerId,d.locationId,e.employeeId,e.firstName,e.lastName,e.phoneNumber,e.hireDate,e.jobId,e.salary,e.commissionPct) FROM Employee e INNER JOIN Department d on e.joining_column_from_table1=d.joining_column_from_table2")
Make sure to replace joining_column_from_table1 and
joining_column_from_table2 with your column names from table
Employee and Department respectively

HQL select query calls object constructor but object is not in result list

We have a HQL SELECT query that, as a result, returns Java objects. To our surprise, some objects are missing in the result list.
I've checked the db for one of the missing objects and could not find a problem. Furthermore, I set a break point in the constructor of 'ParsableObject' that watches for the values of the missing object and it is triggers. But yet, that object is not in the result list.
The result contains some other objects and there is no exception thrown. Are there any known issues that I should look out for?
What I did not do yet: Check the logs of hibernate. I've set hibernate.show_sql to true but did not get a log. But that's a different issue.
The prepared query:
<query name="ObjectDAOHibernate.getSessionObjects">
<query-param name="idSession" type="long"/>
<![CDATA[
select new de.smartshift.ucchecker.model.ParsableObject(
to.idObject, to.objectName, to.description,
tom.codeSize, tom.codeSizeModified,
tom.lineCount, tom.normalizedLineCount, tom.commentLineCount, tom.lineCountModified,
tot.objectTypeCode,
ocst.idObjectState,
odst.idObjectState,
opst.idObjectState,
otst.idObjectState,
oust.idObjectState,
sapr3data.devClass,
to.standard,
to.tblFolder.relevant)
from
de.smartshift.ucchecker.db.hib.TblObject to join to.tblFolder tf
join tf.tblSession ts
join to.tblObjectType tot
join to.tblObjectMetadata tom
join to.tblObjectProcessingState tops
join tops.tblObjectStateByRefidCustomerState ocst
join tops.tblObjectStateByRefidDownloadState odst
join tops.tblObjectStateByRefidParserState opst
join tops.tblObjectStateByRefidToolState otst
join tops.tblObjectStateByRefidUploadState oust
join to.tblSapr3Data sapr3data
where
ts.idSession=:idSession
order by
to.objectName ASC, tot.objectTypeCode ASC
]]>

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