Sorting by relation property - java

Here's my working database setup:
#Entity
class Foo {
#Id
#Column(name = "ID")
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "FOO_ID")
private Set<Bar> bars;
//...
}
#Entity
class Bar {
#Id
#Column(name = "ID")
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
#Column(name = "STATUS")
private String status;
//...
}
FooRepository extends CrudRepository {
#Query("select distinct f from Foo f left join f.bars b where b.status = :status ")
public Page<Bar> findByBarStatus(#Param("status") BarStatus status, Pageable pageable);
}
I'd like to be able to sort this query by Bar.status, here's how I tried to change the query:
#Query("select distinct f from Foo f left join f.bars b where b.status = :status order by b.status desc")
public Set<Bar> findByBarStatus(#Param("status") BarStatus status);
However that causes sql syntax error:
org.h2.jdbc.JdbcSQLException: Order by expression "BARS1_.STATUS" must be in the result list in this case;

Here you have applied distinct on f and thus you can not have some other column in order by. Actually, order by item must be there in select list.
So, the problem is in query, you can remove distinct if you are sure that f will be unique (but that's not the case I guess) or you can try with clause,
with temp as
(select distinct f, b.status
from Foo f left join f.bars b
where b.status = :status order by b.status desc)
select f from temp

you have to call b.status in your query select statement like below query.
select DISTINCT(f), b.status from Foo f
LEFT JOIN f.bars b where b.status = :status order by b.status desc

Related

Operand should contain 2 column(s) when fetching data through JPQL using IN & MEMBER OF clause

I am trying to fetch data of employee availability creating a query. I have used #ElementCollection annotation to map skills and daysAvailable, which stores data into other table.
TypedQuery<Employee> query = entityManager.createQuery("select e from Employee e where :day MEMBER OF e.daysAvailable and :list MEMBER OF e.skills ", Employee.class);//
query.setParameter("day", dayOfWeek);
query.setParameter("list", employeeRequestDTO.getSkills());
List<Employee> list = query.getResultList();
I am trying to pass a set in my query. If there is only one element in set then the query works but if more then 1, then it is not able to fetch. I have tried using e.skills in (:list) instead of
:list MEMBER OF e.skills. But still no luck!!
#Entity
public class Employee {
#Id
#GeneratedValue
private long id;
#Nationalized
private String name;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "skills", joinColumns = #JoinColumn(name = "emp_id"))
#Enumerated(EnumType.STRING)
private Set<EmployeeSkill> skills;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "days", joinColumns = #JoinColumn(name = "emp_id"))
#Enumerated(EnumType.STRING)
private Set<DayOfWeek> daysAvailable;
// Getters & Setters
This is the domain i have created and now want to fetch all employees who are available on a particular day and have particular skills. I have created query for this but it is showing error - java.sql.SQLException: Operand should contain 2 column(s)
This is the query generated-
select * from employee e where ( 'TUESDAY' in ( select d.days_available from days d where e.id=d.emp_id)) and (( 'PETTING','FEEDING' ) in (select s.skills from skills s where e.id=s.emp_id));
Any suggestions?
In JPQL, MEMBER OF takes a single argument and checks if that's contained in the provided collection.
Check out the Hibernate User Guide for more details.
Try this query instead:
select e
from Employee e
join e.skills s
where
:day MEMBER OF e.daysAvailable and
s in :list

Find next Date/Time in JPA query

I need to return the next meeting for a user. For example, to userId 1, the next meeting will be today (MeetingId 1), because today is 02-12-2019 13:12PM. If I search for the next meeting today at 15:00PM, the next meeting will be at 09-12-2019 (MeetingiD 2). If I search for the next meeting tomorrow, the next meeting will be at 09-12-2019 (MeetingiD 2).
I'm using MYSQL database and the query (but I don't know how to search exactly):
#Query(select m from meeting m inner join m.group g inner join g.user u where u.id = :userId order by m.meetingDate desc)
Meeting getNextMeeting(#Param(userId) long userId)
public class Meeting {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private long id;
#OneToMany()
private Group group;
private Date meetingDate;
private Time meeting;
}
public class Group {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private long id;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "userId",nullable = false)
private User user;
}
public class User {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private long id;
}
To get only one next meeting, You need to filter out all the future event using the below condition and limit 1.
Below query works, if you are using Postgres
(date m.date + time m.time) > CURRENT_TIMESTAMP and limit 1
Mysql has now() function that returns current date.
Ref: https://www.w3schools.com/sql/func_mysql_now.asp
Try this query:
#Query("select m from meeting m inner join m.group g inner join g.user u where u.id = :userId and m.meetingDate > now() order by m.meetingDate asc")
List<Meeting> getNextMeetings(#Param(userId) long userId)
You sorting should be ascending(from the closest date to now to the farthest). Query will return list of entities. I would suggest you to check the order in result query and take the first entity from the list -> list.get(0). Otherwise, you can sort it by yourself and take the first.

How to return an object with the biggest collections size in JPA?

For example, I have this code:
#Entity
#Table(name = "Foo")
public class FooImpl implements Foo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", nullable = false)
private Long id;
#Column(name = "name", nullable = false)
private String fooName;
#ElementCollection
private Set<Long> fooSet = new HashSet<Long>();
// constructors + getters
}
I want to create a method in my service class which will return an object with the greatest fooSet size. How can I achieve this with JPQL? The below code is incorrect but this is what I've tried to do:
#Override
public Foo getTopFoo() {
return (Foo)entityManager.createQuery("select c from Foo where max(size(c.fooSet)) ").getSingleResult();
}
You can try that, because I know you can do subquery like in/= in JPQL, but I have big doubt that it works or that it's a valid syntax (notice the use of Foo.class to avoid the cast!):
#Override
public Foo getTopFoo() {
return entityManager.createQuery("select c from Foo c where size(c.fooSet) = (select max(size(c.fooSet)) from Foo c)", Foo.class).getSingleResult();
}
I don't think that your JPA implementation will correctly translate the max(size()) as it is a computed value: in pseudo SQL, it would give this:
select f.*, count(ff.*) as cu
from Foo f
left join Foo_fooSet ff on [...]
group by f.*
Of course, you need to enumerate all the column of table Foo (or f).
You would need to return a subquery based on that count, something like:
select max(cu) from (
select f.*, count(ff.*) as cu
from Foo f
left join Foo_fooSet ff on [...]
group by f.*
)
But I don't remember JPQL allowing a from that accepts a subquery.
However, you can use native query and this would work:
select f.*
from Foo f
left join Foo_fooSet ff on [...]
group by f.*
having count(ff.*) = (
select max(cu) from (
select f.id, count(ff.*) as cu
from Foo f
left join Foo_fooSet ff on [...]
group by f.id
)
)
In the the subquery (which count), you need only the id.
If the first select (f.*), you have to put ALL the column corresponding to the fields of your entity. Have fun!
Or: returns the id of your entity, then load them using your entityManager.
The easiest and most readable way to do this is using two queries.
select id, count(*) from Bar group by id order by count(*) desc, id asc
This query will give you the ids and the collection size (assuming Bar is the name of the collection table.
With JPA you can easily select only the first result. With that id, selecting the Foo object is easy.
Another option is to limit the result in SQL and join with the Foo table.
from Foo where id = (select top 1 id from (select id, count(*) from Bar group by id, order by count(*) desc, id asc))
This cannot be done in a db agnostic way, however.
You seemed to be on the right track, you just need subqueries to return the max value and compare it to the size of the current c's list. Something like:
So if "Select max(size(c.fooSet)) from Foo c" gives you the max value,
"select f from Foo f where size(f.fooSet) = (select max(size(c.fooSet)) from Foo c)"
Should give you the Foos with the size equal to the max size.

How to get root by recursive oracle query in Hibernate

I'm a little curious, is there a way to get result of connect by sql query like root Entity with already mapped descendants.
So if I'm insert in base something like this:
insert into table test (id, parent_id, some_text) values
(1, null, 'a'),
(2, 1, 'b'),
(3, 1, 'c'),
(4, 2, 'd');
then by sql query
select *
from test t
start with t.id = 1
connect by prior t.id = t.parent_id
order siblings by t.some_text
I will get
id | parent_id | some_text
1 null a
2 1 b
4 2 d
3 1 c
and by entity
#Entity
#Table(name = "test")
public class Test {
#Id
#Column(name = "id")
#GeneratedValue(generator = "increment")
#GenericGenerator(name = "increment", strategy = "increment")
private BigInteger id;
#Column(name = "parent_id")
private BigInteger parent_id;
#Column(name = "some_text")
private String someText;
#OneToMany(mappedBy = "parent")
private Set<Test> descendants;
#ManyToOne
#JoinColumn(name = "parent_id")
private Test parent;
// getters and setters
}
it will back to me as list of Test. It possible to get root and full tree by recursive function, but it will get new query on iteration (it will very long if I have a big tree).
So is there a possible good way to get root of this tree with already mapped descendants by this query (maybe extend/implement some class/interface which will process mapping from jdbc to entity)?
you can use CONNECT_BY_ROOT unary operator.
See docs
select t.*, connect_by_root id
from test t
start with t.id = 1
connect by prior t.id = t.parent_id
order siblings by t.some_text
BTW: this has nothing to do with Hibernace. This is purely Oracle specific solution.

Criteria query, find all groups that I'm not member of

I have two entities, Group and GroupMember. Group is like it sounds a group that has name and some other properties. Members of the group are mapped with GroupMember entity, which has an entry with a User and a Group for every group the user is member of. They look like the following:
#Entity
#Table(name = EntityTokens.GROUP_TABLE)
public class Group
{
#Id
#Column(name = EntityTokens.GROUP_ID_COLUMN)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long groupId;
...
// Group members
#OneToMany(orphanRemoval = true, fetch = FetchType.LAZY, mappedBy = "group", cascade = {CascadeType.ALL})
private Collection<GroupMember> groupMembers;
}
#Entity
#Table(name = EntityTokens.GROUP_MEMBER_TABLE)
public class GroupMember
{
#Id
#Column(name = EntityTokens.GROUP_MEMBER_ID_COLUMN)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long memberId;
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = EntityTokens.GROUP_MEMBER_ID_COLUMN)
private Group group;
}
I'm trying to write a criteria query that returns all groups that have some predefined properties and that the current user is not part of. My query looks like this:
CriteriaQuery<Group> q = cb.createQuery(Group.class);
Root<Group> root = q.from(Group.class);
Join<Group, GroupMember> groups = root.join(Group_.groupMembers);
q.select(root);
q.where(cb.notEqual(groups.get(GroupMember_.user), user),
cb.equal(root.get(Group_.global), false),
cb.equal(root.get(Group_.personal), false),
cb.equal(root.get(Group_.privacy), GroupPrivacy.PUBLIC));
q.distinct(true);
The user here represents the current user. This query doesn't work because if there are other members that are part of the same group as me they will be included in the query result due to the join. How should the correct query look like? I'm not that familiar with the criteria query API yet.
join for a ToMany relationship is conceptually an "anyOf" operation, meaning if any of the many is true then the expression is true.
What you want is an "allOf", criteria does not have this, you need to use a sub select for this in SQL.
The JPQL would be,
Select g from Group g where not exists (select m from g.members m where m.user = :user)
The criteria would be the same, using a sub criteria query for the exists.

Categories