Override column selection in jpa - jpa

I need to select an integer column (int) by left joining its entity.
When there are no rows, its value is "null", what I need is to have 0 instead of null.
I searched for an annotation or a way to override the select query with no result. Note that I'm using eclipselink.
I am searching for something that always makes the selection of the column "coalesce(columnName,0)" instead of columnName.
EDIT:
A code Sample:
public class parent{
#Id
private int id;
private String field1;
private String field2;
private List<Child> children;
}
public class Child{
#Id
private int id;
private int myNumber;
private String field;
}
The JPA Query is similar to
SELECT p FROM parent p LEFT JOIN p.children c ON c.field = 'aaa' WHERE p.field1 = 'bbb'
The translation of this query would be:
SELECT t0.id, t0.field1, t0.field2, t1.id, t1.myNumber, t1.field
FROM parent t0 LEFT OUTER JOIN child t1 on t0.id = t1.parent_id AND t1.field = 'aaaa
WHERE t0.field1 = 'bbb'
In the case where there are no rows in CHILD, the field: t1.myNumber will have the value null.
To fix it, in Sql statement, normally I will use coalesce(t1.myNumber,0). So the query will be:
SELECT t0.id, t0.field1, t0.field2, t1.id, coalesce(t1.myNumber,0), t1.field
FROM parent t0 LEFT OUTER JOIN child t1 on t0.id = t1.parent_id AND t1.field = 'aaaa
WHERE t0.field1 = 'bbb'
What I need is to accomplish this result using some annotation, or any other tool if exists, whithout changing the JPA query if possible.

Related

Add custom clause to HQL join query

I have the Entity AppVisit with ManytoOne relation with User entity. User entity has OneToOne relation with UserDetail Entity.
I need to get all users having number of visits on a specific date less than their maxDailyVisits
Below are the classes
public class AppVisit{
#ManyToOne(fetch = FetchType.LAZY)
private User user;
private LocalDate visitDate;
}
public class UserDetail{
private Long id;
private int maxDailyVisits;
#OneToOne
private User user;
}
public class User {
private Long id;
#OneToMany(mappedBy = "user")
private List<AppVisit> appVisits;
#OneToOne(mappedBy = "user")
private UserDetail userDetail;
}
Here the HQL query I tried to use in the repository
select ud from UserDetail as ud where u.id in
(select ud1.id from UserDetail as ud1
left join ud1.user.appVisits as av
with acav.visitDate=:visitDate or acav.visitDate is null
Group by u1.id having Count(ud1.id) < ud1.maxDailyVisits)
At the execution time, I got the following SQL query executed to database:
select * from "UserDetail" ud
cross join "User" u where ud."User_Id"=u."Id"
and (ud."Id" in
(select ud1."Id" from "UserDetail" ud1
left outer join "User" u1 on ud1."User_Id"=u1."Id"
and (av."VisitDate"=? or av."VisitDate" is null)
left outer join "AppVisit" av
on u1."Id"=av."User_Id"
group by ud1."Id"
having count(ud1."Id")<ud1."MaxDailyVisits"))
Also I got this error:
missing FROM-clause entry for table "av"
because the condition on VisitDate column was put before the join on AppVisit Table
Any solution for this issue?
When you are using the HQL please provide a direct entity class name like below.
left join appVisits as av
Another issue is you are missing the join condition too. Full query should be like below.
select ud from UserDetail as ud where u.id in
(select ud1.id from UserDetail as ud1
left join appVisits as av on ud1.id = av.user.id
where av.visitDate=:visitDate or av.visitDate is null
Group by u1.id having Count(ud1.id) < ud1.maxDailyVisits)

How to use string_agg with PostgreSQL in #Query annotation without nativeQuery flag?

I need to remove the nativeQuery flag from the #Query annotation.
The table structure may change in the future, and code without nativeQuery will make it easier to maintain later.
I have a class Parent that is linked to class Child with the #ManyToMany annotation.
Class Child has a field pseudonym that is a value of the String type.
The result of the query needs to be sorted by the String value from class Child, which I have to sort and then concatenate into one String value.
The query without the nativeQuery flag in the #Query annotation works if I do not add an additional sort in the string_agg function:
order by string_agg(c.pseudonym, ',')
If I add additional required sorting as below, an exception occurs
order by string_agg(c.pseudonym, ',' order by c.pseudonym)
org.hibernate.hql.internal.ast.QuerySyntaxException: expecting CLOSE, found 'order' near line 1, column ...
#Entity
#Getter
#Setter
#Table(name = "parent")
public class Parent {
#Id
private Long id;
private String name;
#ManyToMany
#JoinTable(
name = "parent_child_link",
joinColumns = {#JoinColumn(name = "parent_id")},
inverseJoinColumns = {#JoinColumn(name = "child_id")}
)
#OrderBy("pseudonym ASC")
private List<Child> childs = new ArrayList<>();
}
#Entity
#Getter
#Setter
#Table(name = "child")
public class Child {
#Id
private Long id;
private String pseudonym;
#ManyToMany(mappedBy = "childs")
private Set<Parent> parents = new HashSet<>();
}
public interface ParentRepository extends JpaRepository<Parent, Long> {
#Query(nativeQuery = true, value =
"select p.*" +
" from parent p" +
" left join parent_child_link link on p.id = link.parent_id" +
" left join child c on link.child_id = c.id" +
" where p.name = :name" +
" group by (p.id)" +
" order by string_agg(c.pseudonym, ',' order by c.pseudonym)")
Page<Parent> find(#Param("name") String name, Pageable pageable);
}
Please try with nested query:
select p.*
from (
select p, string_agg(c.pseudonym, ',' order by c.pseudonym) ord
from parent p
left join parent_child_link link on p.id = link.parent_id
left join child c on link.child_id = c.id
where p.name = :name
group by (p.id)
) inn(p, ord)
order by ord
or:
select p.*
from(
select p, c.pseudonym
from parent p
left join parent_child_link link on p.id = link.parent_id
left join child c on link.child_id = c.id
where p.name = :name
order by p, pseudonym
) inn(p, pseudonym)
group by p.id
order by string_agg(pseudonym, ',')

Eclipselink problem with subquery in where clause

I have the following structure: (I am using eclipselink 2.6.0)
#Entity
public class A {
#Id
private double id;
...
#OneToMany
private List<B> b;
#OneToMany(mappedBy = "a")
private List<C> c;
...
}
#Entity
public class B {
#Id
private double id;
private boolean hidden;
}
#Entity
public class C {
#Id
private double id;
private String label;
private String value;
#ManyToOne
private A a;
}
I need to find A that are not hidden, and has s1 = value1, s2 = value 2, this is the implementation using criteria:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<A> cq = cb.createQuery(A.class);
Root<A> root = cq.from(A.class);
Join<A, B> property = root.join(A_.b, JoinType.LEFT);
String currentUser = ctx.getCallerPrincipal().getName();
property.on(cb.equal(property.get(B_.creator), currentUser));
C af = new C("value1", "value2");
Subquery<B> sq = cq.subquery(B.class);
Root<C> sqRoot = sq.from(C.class);
sq.where(cb.and(cb.equal(sqRoot.get(C_.A), root),
cb.equal(sqRoot.get(C_.label), af.getLabel()),
cb.like(sqRoot.get(C_.value), "%" + af.getValue() + "%")));
sq.select(sqRoot);
cq.where(cb.and(
cb.exists(sq),
cb.or(cb.isNull(property.get(B_.id)),
cb.equal(property.get(B_.hidden), false))));
cq.select(root).distinct(true);
The generated query is:
SELECT DISTINCT t1.ID
FROM B t0, A t1
WHERE (EXISTS (SELECT 1 FROM C t2 WHERE (((t2.A_ID = t1.ID) AND (t2.LABEL = "value1")) AND t2.VALUE LIKE "%value2%")) AND ((t0.ID IS NULL) OR (t0.HIDDEN = false)))
As you can see, the provider ignores the left Join totally as if it doesn't exist! It doesn't do any join between tow tables A and B..
Note that in the JPA JSR, the query should be correct. It also worked as a named query. But I can't use it here since I have lots of changing conditions...
The query should be:
SELECT DISTINCT t1.ID
FROM A t1 LEFT JOIN B t0 ON t1.ID=t0.A_ID AND t0.creator="currentUserValue"
WHERE (EXISTS (SELECT 1 FROM C t2 WHERE (((t2.A_ID = t1.ID) AND (t2.LABEL = "value1")) AND t2.VALUE LIKE "%value2%")) AND ((t0.ID IS NULL) OR (t0.HIDDEN = false)))
I tried to change eclipseLink to the 2.7.7 release...The query changed a bit, but it still wrong, with no left join between A and B.
I appreciate any help with this problem.

Querying EXISTS in many to many relations in Criteria API

Having two entity classes with a many to many relation, I am trying to create query which tests the existence of any relationship for all entities from one table. I am stuck because it seems that there is no way to refer to the JoinTable through the Criteria API.
Example entities:
#Entity
#Table(name="man")
public class Man {
#Id
#GeneratedValue
private Long id;
}
#Entity
#Table(name="woman")
public class Woman {
#Id
#GeneratedValue
private Long id;
#ManyToMany
#JoinTable(
name="man_woman",
joinColumns=
#JoinColumn(name="woman_id", referencedColumnName="id"),
inverseJoinColumns=
#JoinColumn(name="man_id", referencedColumnName="id")
)
private Set<Man> men;
}
I would like to create a query using criteria API which would result in SQL such as:
select m.id,
case when exists(select * from man_woman mw where mw.man_id=m.id) then 1 else 0
from man;
The best I have come up with so far is the following:
CriteriaQuery<Tuple> criteriaQuery = criteriaBuilder.createTupleQuery();
Root<Man> from = criteriaQuery.from(Man.class);
Subquery<Long> subquery = criteriaQuery.subquery(Long.class);
Root<Woman> sub_from = subquery.from(Woman.class);
SetJoin<Woman, Man> setJoin = sub_from.join(Woman_.men);
subquery.select(sub_from.get(Woman_.id));
subquery.where(from.in(setJoin.get(Man_.id)));
criteriaQuery.multiselect(from.alias("man_entity"),
criteriaBuilder.selectCase()
.when(
criteriaBuilder.exists(subquery)
, true)
.otherwise(false)
.alias("knows_any_women")
);
return em.createQuery(criteriaQuery).getResultList()
which results in SQL containing extra joins:
select m.id,
case when exists(select w.id
from woman w
inner join man_woman mw on w.id = mw.woman_id
inner join man m2 on m2.id = mw.man_id
where m.id in (m2.id)
)
then 1 else 0
from man;
I guess this statement would eventually be optimized to look like my desired one - but is there a way to make it simpler from the beginning?

JPQL left outer join on many to many relationship

I'm trying to write a query (using JPQL) to get all tags and how often each has been referenced by all items. The JPQL is:
SELECT t.id, t.name, SIZE(t.items) FROM Tag t GROUP BY t.id, t.name ORDER BY t.name ASC
Note that some tags are not referenced by any items, but I still want them included in the results (and expecting SIZE(t.items) would be zero).
However, when this query is executed, it returns only the tags that have items associated with it, ignoring tags that are not referenced by any items. The generated SQL from JPA is:
SELECT t0.id as a1, t0.NAME AS a2, COUNT(t1.id) FROM tag t0, item_tag_map t2, item t1 WHERE ((t2.tag_id = t0.id) AND (t1.id = t2.item_id)) GROUP BY t0.id, t0.NAME ORDER BY t0.NAME ASC;
It is not performing a LEFT OUTER JOIN which is required to get the results I want. If I was writing this in SQL, I would have done something like
select t.id, t.name, count(map.item_id) from tag as t left join item_tag_map as map on map.tag_id = t.id group by t.id, t.name order by t.name ASC;
Is there any way to accomplish this in JPQL? Am I doing something wrong or is it a limitation of JPQL (or a bug)? I'm using PostgreSQL v9.2 and EclipseLink v2.4.2
To provide more details... I have 3 SQL tables: item, tag, and item_tag_map. And here are the snippet of the related Java classes:
#Entity
#Table(name="item")
public class Item implements Serializable {
#Id
#Column(name="id", updatable=false)
private String id;
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(
name = "item_tag_map",
joinColumns = { #JoinColumn(name = "item_id", referencedColumnName = "id", nullable=false) },
inverseJoinColumns = { #JoinColumn(name = "tag_id", referencedColumnName = "id", nullable=false) })
private List<Tag> tags;
...
#Entity
#Table(name="tag")
#NamedQueries({
#NamedQuery(name="Tag.findAllStats", query="SELECT t.id, t.name, SIZE(t.items) FROM Tag t GROUP BY t.id, t.name ORDER BY t.name ASC"),
})
public class Tag implements Serializable {
#Id
#Column(name="id", updatable=false)
private long id;
private String name;
#ManyToMany(mappedBy="tags", fetch=FetchType.LAZY)
private List<Item> items;
...
I'm not sure if it's an EclipseLink bug or not (it looks like it is one to me, though), but you could probably use the following query to solve the problem (not tested though):
select t.id, t.name, count(i.id) from Tag t
left join t.items i
group by t.id, t.name
order by t.name asc