Spring native query with GROUP BY and ORDER BY also pagination - java

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

Related

HQL Hibernate query search check if list contains all elements of another list

I came across a problem that I cannot think of a solution.
So I got this piece of code:
public List<Post> getPosts(List<PostStrategy> allowedStrategyList, Set<Tag> allowedTags, int page, int resultsPerPage) {
return entityManager.createQuery("select post from Post post join post.tags tag where post.postStrategy in (:postStrategyList) and (:allowedTagsSize = 0 or tag in (:allowedTags))", Post.class)
.setParameter("postStrategyList", allowedStrategyList)
.setParameter("allowedTags", allowedTags)
.setParameter("allowedTagsSize", allowedTags.size())
.setFirstResult((page - 1) * resultsPerPage)
.setMaxResults(resultsPerPage)
.getResultList();
}
The problem with this piece of code is that when someone searches with more than one tag (for example: #video, #image), it returns both posts with two tags and one tag.
I would like it to return a post with both #video, and #image in its tags. For it to work, I somehow need to check if the list contains all elements of another list.
I searched for a solution for a while and tried different approaches so far, nothing.
I tried replacing "tag in (:allowedTags)" with "post.tags in (:allowedTags)" but that throws that my SQL is invalid.
Two years later I got my answer. I am now using Spring Boot and SpEL expressions so bear with me. This query does a little more than just my original idea but you can extract what you need from it.
#Override
#Query("select p from PostSnapshot p where " +
// Tags
"(:#{#query.withTags.size()} = 0 or p.id in (" +
"select post.id from PostSnapshot post inner join post.tags tag " +
"where tag.value in (:#{#query.withTags}) " +
"group by post.id " +
"having count(distinct tag.value) = :#{#query.withTags.size() * 1L}" +
")) and (:#{#query.withoutTags.size()} = 0 or p.id not in (" +
"select post.id from PostSnapshot post inner join post.tags tag " +
"where tag.value in (:#{#query.withoutTags}) " +
"group by post.id " +
"having count(distinct tag.value) = :#{#query.withoutTags.size() * 1L}" +
")) " +
// Artists
"and (:#{#query.withArtists.size()} = 0 or p.id in (" +
"select post.id from PostSnapshot post inner join post.artists artist " +
"where artist.preferredNickname in (:#{#query.withArtists}) " +
"group by post.id " +
"having count(distinct artist.preferredNickname) = :#{#query.withArtists.size() * 1L}" +
")) and (:#{#query.withoutArtists.size()} = 0 or p.id not in (" +
"select post.id from PostSnapshot post inner join post.artists artist " +
"where artist.preferredNickname in (:#{#query.withoutArtists}) " +
"group by post.id " +
"having count(distinct artist.preferredNickname) = :#{#query.withoutArtists.size() * 1L}" +
")) " +
// Title like words
"and lower(p.title) like concat('%', lower(:#{(#query.words.isEmpty()) ? '' : #query.words.toArray()[0]}) ,'%')" +
// OwnerId
"and p.ownerId = :ownerId")
Page<PostSnapshot> findAllByOwnerIdAndQuery(#Param("ownerId") UUID ownerId, #Param("query") PostQuerySearchDTO query, Pageable pageable);

Pageable in JPA native query

I want to use pagination with a native query. I use for this this syntaxe as in this example : Spring Data and Native Query with pagination
and it's my query:
#Query(value="SELECT rownum() as RN, users.num, users .l_service,service.type_service, users.date, " +
"chambre.price," +
"price* ( case when(datediff(day,date_d,date_f)=0) then 1 " +
"else datediff(day,date_d,date_f) end ) as Montant," +
"case when (service.type_service='R') and datediff(day,date_d,date_f) >=21 " +
"then (21300+(datediff(day,date_d,date_f)-21)*200)" +
"else price*(case when(datediff(day,date_d,date_f)=0) then 1 else datediff(day,date_d,date_f)end) end AS Montant_final " +
" users.year, users.Etat, " +
" from chambre JOIN users ON chambre.code = users.type " +
"JOIN service on service.code = users.l_service " +
" WHERE users.Etat='V' and RN between ?#{ #pageable.offset -1} and ?#{#pageable.offset + #pageable.pageSize order by users.num",
countQuery ="select count(*) from users ",nativeQuery = true)
Page<Object> getStatistiques(Pageable pageable);
I get this error :
Cannot mix JPA positional parameters and native Hibernate positional/ordinal parameters
This is the solution I found to my problem:
#Query(value="SELECT * from (SELECT ROW_NUMBER() OVER (ORDER BY users.num) as RN, users.num, users .l_service,service.type_service, users.date, " +
"chambre.price," +
"price* ( case when(datediff(day,date_d,date_f)=0) then 1 " +
"else datediff(day,date_d,date_f) end ) as Montant," +
"case when (service.type_service='R') and datediff(day,date_d,date_f) >=21 " +
"then (21300+(datediff(day,date_d,date_f)-21)*200)" +
"else price*(case when(datediff(day,date_d,date_f)=0) then 1 else datediff(day,date_d,date_f)end) end AS Montant_final " +
" users.year, users.Etat, " +
" from chambre JOIN users ON chambre.code = users.type " +
"JOIN service on service.code = users.l_service " +
" WHERE users.Etat='V') AS STA where RN between ?#{ #pageable.offset -1} and ?#{#pageable.offset + #pageable.pageSize} order by STA.num",
countQuery ="select count(*) from users ",nativeQuery = true)
Page<Object> getStatistiques(Pageable pageable);
I share it with you perhaps it can help someone else!

JPA: ORDER BY before GROUP BY in JPARepository

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

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