JPA: ORDER BY before GROUP BY in JPARepository - java

This is the mysql query that I've:
SELECT * FROM (SELECT vev.* FROM vital_entry ve
INNER JOIN vital_entry_values vev ON ve.vital_entry_id=vev.vital_entry_id
WHERE ve.account_id=146
ORDER BY date_entered DESC) t1
GROUP BY vital_id ;
How do I do this in JPA?? I tried:
#Query("SELECT t1 FROM (SELECT ve FROM VitalEntry ve " +
"INNER JOIN ve.vitalEntryValues vev " +
"WHERE ve.accountId=:accountId " +
"ORDER BY ve.dateEntered DESC) t1")
List<VitalEntry> getRecentVitalValues(#Param("accountId") int accountId);
But IntelIj shows error expected identifer, got '('

This gave me the expected result
#Query("SELECT new com.v2.model.VitalValue(vev.vitalId, vev.value, max(ve.dateEntered), ve.vitalEntryType, vev.type) FROM VitalEntry ve " +
"INNER JOIN ve.vitalEntryValues vev " +
"WHERE ve.accountId=:accountId " +
"GROUP BY vev.vitalId " +
"ORDER BY max(ve.dateEntered) DESC")
List<VitalValue> getRecentVitalValues(#Param("accountId") int accountId);

Use native query instead:
#Query("SELECT * FROM (SELECT ve.* FROM vital_entry ve INNER JOIN vital_entry_values vev ON ve.vital_entry_id=vev.vital_entry_id WHERE ve.account_id=:accountId ORDER BY date_entered DESC) t1 GROUP BY vital_id", nativeQuery=true)
List<VitalEntry> getRecentVitalValues(#Param("accountId") int accountId);

Related

Spring native query with GROUP BY and ORDER BY also pagination

What if I have my native query (with PostgreSQL) in a more "complex" way, which I need to GROUP the data in order to apply an ORDER BY clause? Well, doing so with native query (as explained here) the ORDER BY attributes are placed at the end of the query, which raises a SQL syntax error (of course) cause the ORDER BY should stay BEFORE my pagination clauses.
#Query(
" SELECT * FROM (" +
"SELECT CAST(t.id AS VARCHAR) AS id, t.name, CAST(COUNT(p.id) AS SMALLINT) AS usedByProjectsCount " +
"FROM tag t " +
"LEFT JOIN project_tag pt ON pt.tag_id = t.id " +
"LEFT JOIN project p ON p.id = pt.project_id " +
"GROUP BY t.id, t.name) AS t " +
"ORDER BY #pageable " +
"LIMIT :limit OFFSET :offset ",
countQuery = " SELECT COUNT(*) FROM (" +
"SELECT CAST(t.id AS VARCHAR) AS id, t.name, CAST(COUNT(p.id) AS SMALLINT) AS usedByProjectsCount " +
"FROM tag t " +
"LEFT JOIN project_tag pt ON pt.tag_id = t.id " +
"LEFT JOIN project p ON p.id = pt.project_id " +
"GROUP BY t.id, t.name) AS t ",
nativeQuery = true
)
I already tried putting #pageable at the end, but I get the same error message.
Moving on, Spring generates my query like: SELECT * FROM (SELECT CAST(t.id AS VARCHAR) AS id, t.name, CAST(COUNT(p.id) AS SMALLINT) AS usedByProjectsCount FROM tag t LEFT JOIN project_tag pt ON pt.tag_id = t.id LEFT JOIN project p ON p.id = pt.project_id GROUP BY t.id, t.name) AS t ORDER BY {#pageable} LIMIT ? OFFSET ? , t.usedByProjectsCount desc
and I need like: SELECT * FROM (SELECT CAST(t.id AS VARCHAR) AS id, t.name, CAST(COUNT(p.id) AS SMALLINT) AS usedByProjectsCount FROM tag t LEFT JOIN project_tag pt ON pt.tag_id = t.id LEFT JOIN project p ON p.id = pt.project_id GROUP BY t.id, t.name) AS t ORDER BY t.usedByProjectsCount desc LIMIT ? OFFSET ? {#pageable}
I do know the {#pageable} attribute should stay there, just so my page values can be placed in the right variables.
Anyway, any help would be very appreciated.
In the one of my project I have a native query and just put the pageable inside.
#Query(
value = "SELECT * " +
" FROM (SELECT CAST(t.id AS VARCHAR) AS id, " +
" t.name, " +
" CAST(COUNT(p.id) AS SMALLINT) AS usedByProjectsCount " +
" FROM tag t " +
" LEFT JOIN project_tag pt ON pt.tag_id = t.id " +
" LEFT JOIN project p ON p.id = pt.project_id " +
" GROUP BY t.id, t.name) AS t ",
countQuery = " SELECT COUNT(*) " +
" FROM (SELECT 1 " +
" FROM tag t " +
" LEFT JOIN project_tag pt ON pt.tag_id = t.id " +
" LEFT JOIN project p ON p.id = pt.project_id " +
" GROUP BY t.id, t.name) AS t ",
nativeQuery = true)
Page<CustomProjection> customQuery(Pageable pageable)
and just call your method with the needed pageRequest.
Pageable pageRequest = PageRequest.of(0, 10, Sort.of(Order.desc("usedByProjectsCount"))

Hibernate select sub object into interface

To collect only the data i need i use following query for object Declaration:
#Query(value = "select d.id as id "
+ " , d.statusChanged as statusChanged "
+ " from Declaration d ",
countQuery="select count(id) from Declaration")
Page<DeclarationIDTO> findAllDeclarationListIDTO(Pageable pageable);
DeclarationIDTO has a getId() and a getStatusChanged().
This works.
Now i want to add the id of the freelancer like this:
#Query(value = "select d.id as id "
+ " , d.statusChanged as statusChanged "
+ " , f.id as 'freelancer.id' "
+ " from Declaration d join d.freelancer f",
countQuery="select count(id) from Declaration")
Page<DeclarationIDTO> findAllDeclarationListIDTO(Pageable pageable);
The DeclarationIDTO has a getFreelancer() which has a getId() but i get an error:
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: expecting IDENT, found ''freelancer.id'' near line 1, column 66 [select d.id as id , d.statusChanged as statusChanged , f.id as 'freelancer.id' from nl.tibi.sbys.domain.Declaration d join d.freelancer f]
Any idea how to work with object chain?
Possible work arounds:
1) flatten interface:
#Query(value = "select d.id as id "
+ " , d.statusChanged as statusChanged "
+ " , f.id as 'freelancer_id' "
+ " from Declaration d join d.freelancer f",
countQuery="select count(id) from Declaration")
Page<DeclarationIDTO> findAllDeclarationListIDTO(Pageable pageable);
DeclarationIDTO will have getId(), getStatusChanged() and getFreelancer_id()
Downside is the interface will have to copy all needed freelancer fields and i have to use a mapper to map the freelancer_id to a DTO object with a freelancer.id
2) work with constructor instead of interface:
#Query(value = "select new DeclarationDTO(d.id "
+ " , d.statusChanged "
+ " , f.id) "
+ " from Declaration d join d.freelancer f",
countQuery="select count(id) from Declaration")
Page<DeclarationDTO> findAllDeclarationListIDTO(Pageable pageable);
Downside is i will need many constructors for different pages or need to select null values cluttering my query
3) Multiple queries per object, this is a performance hit and a lot of work.
4) Select full sub objects:
#Query(value = "select d.id as id "
+ " , d.statusChanged as statusChanged "
+ " , f as freelancer "
+ " from Declaration d join d.freelancer f",
countQuery="select count(id) from Declaration")
Page findAllDeclarationListIDTO(Pageable pageable);
DeclarationIDTO has a getId() getStatusChanged() and getFreelancer()
This works there are still to many data collected which is a downside on performance.
If there is a simple solution for getting f.id it would solve all downsides.
There seems an answer here:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
When time i will look into it.
1) Your DeclarationIDTO must have constructor with id and statusChange params, in that order
2) Try adding the new operator in the query with fully qualified name:
"select new my.package.DeclarationIDTO(d.id as id, d.statusChanged as statusChanged "
+ " , f.id as 'freelancer.id' "
+ " from Declaration d join d.freelancer f",

Spring JPA row_to_json PostgreSQL

Instead of creating an entity with column annotation, is it possible to get query result with column name? Like:
#Query(value = "select emp.emp_id, emp_fname, emp_lname, emp_division_id, emp_role_id," +
"(select count(*) from cheers.ch_kudos where kudos_receiver_id = emp.emp_id) as received_kudos_num, " +
"(select count(*) from cheers.ch_kudos where kudos_sender_id = emp.emp_id) as sent_kudos_num " +
"from cheers.ch_employee as emp where emp.emp_active_flag = 'true'",
nativeQuery=true)
List<Object> getAllEmployeesReport();
The return result only has values, but doesn't have column name.
Otherwise, it there a way to get PostgreSQL row_to_json result by Spring JPA?
#Query(value = "select row_to_json(t) from (select emp.emp_id, emp_fname, emp_lname, emp_division_id, emp_role_id," +
"(select count(*) from cheers.ch_kudos where kudos_receiver_id = emp.emp_id) as received_kudos_num, " +
"(select count(*) from cheers.ch_kudos where kudos_sender_id = emp.emp_id) as sent_kudos_num " +
"from cheers.ch_employee as emp where emp.emp_active_flag = 'true') t",
nativeQuery=true)
List<Object> getAllEmployeesReport();
It will throw JDBC 111 exception.

Named Query to SELECT rows with MAX(column name), DISTINCT by another column

I have a case similar to the one described in this question, I wrote an identical query which works, but when I try to write it as a jpql named query, I'm getting an error.
My query:
#NamedQuery(
name = "findRankingsBetween",
query = "SELECT rt FROM Rankingtable rt " +
"INNER JOIN " +
"(SELECT teamId, MAX(lastupdate) as MaxDateTime " +
"FROM Rankingtable " +
"GROUP BY teamId) grouped " +
"ON rt.teamId = grouped.teamId " +
"AND rt.lastupdate = grouped.MaxDateTime " +
"WHERE rt.lastupdate BETWEEN :from AND :to"
)
Error:
Error in named query: findRankingsBetween: org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: ( near line 1, column 79
How to write the query properly in jpql?
As noted in this answer, a subquery in JPQL can only occur in select and where clauses.
Hibernate doc.
An equivalent query in JPQL is:
"SELECT rt FROM Rankingtable rt " +
"WHERE rt.lastupdate = (SELECT MAX(r2.lastupdate) " +
"FROM Rankingtable r2 " +
"WHERE r2.teamid = rt.teamid) " +
"AND rt.lastupdate BETWEEN :from AND :to"

HQL query takes way too long to execute

I have this HQL query:
String hql = "from "+Patient.class.getName() + " p "
+ " where p.medicalRoom.id = "+medicalRoom.getId()
+ " and exists (select tg.id from "+TherapyGroup.class.getName()+" tg where tg.treatment.patient.id = p.id "
+ " and not exists (select ta.id from "+TherapyEnd.class.getName()+" ta where ta.therapyGroup.id = tg.id))";
All the properties that are involved in the query are indexed... Though it takes more than one whole minute to execute..

Categories