JPA and JPQL LIMIT when used in subquery - sql

First I would like to point that I know how I could do this in SQL by using the LIMIT but because JPQL doesn't allow LIMIT keyword hence my question:
I have a JPQL query that return places and their linked tags:
List<Object[]> p = entityManager.createQuery("
SELECT p, t.tagName
FROM Place p LEFT JOIN .... ...
for example this could return
place1 | tag1
place1 | tag2
place1 | tag3
place2 | tag1
place2 | tag4
Now.. I obviously cannot simply return the full database places.. and i cannot simply put a LIMIT at the end of the quest because im returning tags so I don't how how many row etc..
In SQL I would do something like this
SELECT p.name, t.tag_name FROM ... WHERE p.idPlace IN (SELECT p.idPlace FROM ... WHERE... LIMTI 10);
I would make a sub query and use the LIMIT in there to only select the place I want.
But because JPQL doesn't allow LIMIT, for now what I'm doing is a 2nd Database call to retrieve my desired placeIds and using the entityManager setMaxResult. to limit. and then I feed these Ids into the other query.
Could someone more experienced with JPA tell me if this a good practice? Is there a other way of doing the things without having to make 2 separated database call ?
Ps: the reason i'm selecting t.tagName directly in the query is because the collection PlaceTag (inside the Place entity) is LAZILY initialised and if I access it in java it will trigger an extra database call.. I DID i have tried to use JOIN FETCH like this:
List<Place> places = entityManager.createQuery("" +
"SELECT p FROM Place p " +
"JOIN FETCH p.PlaceTag pt " +
"JOIN FETCH pt.tag t").setMaxResults(10).getResultList();
So I could do something like this in java (without triggering a new database when accessing p.getPlaceTag() and getTag()
for (Place p : places) {
for (PlaceTag pt: p.getPlaceTag())
System.out.println(pt.getTag().getTagName());
}
But I get this error about the query:
Query specified join fetching, but the owner of the fetched
association was not present in the select list
Thank you!

String query = "SELECT p FROM Place p "
+ "JOIN p.PlaceTag pt "
+ "JOIN pt.tag t";
List<Place> places = entityManager.createQuery(query)
.setMaxResults(10)
.setFirstResult((pageNumber-1) * pageSize)
.getResultList();

Related

How to call postgres position() function in jpa query

I'm want to retreive posts from database ordered by ids given in IN operator
I'm using below spring data JPA query to get posts from database, but postgres is not respecting order of rows specified in given postIds list in result and returning result ordered by postId descending (or ascending, doesn't matter)
#Query("SELECT p from Post p where p.postId in (:postIds))
fun findPostByIdIn(postIds: List<Long>): List<Post>
To maintain order of result, i could use this query
SELECT p from posts p where p.post_id in (10000442, 10000436, 10000440) ORDER BY POSITION(post_id::text IN '10000442, 10000436, 10000440');
My question is, how can i use position() function (which is standard postgresql function) from jpa query with given params? Do i must need to use native query? If so, how can i use native query in spring boot ?
I've tried below query but both of them are not working
#Query(
"SELECT p from posts p where p.post_id in (:postIds) ORDER BY POSITION(p.post_id::text IN ':postIds')",
nativeQuery = true
)
#Query(
"SELECT p from posts p where p.post_id in (:postIds) ORDER BY POSITION(p.post_id\\:\\:text IN ':postIds')",
nativeQuery = true
)
It says ` The column name post_id was not found in this ResultSet.`
Try to make List<Object[]> as return type instead of List then check result content to analyze the issue.

Why Spring Data JPA is not support "limit" statement? [duplicate]

In Hibernate 3, is there a way to do the equivalent of the following MySQL limit in HQL?
select * from a_table order by a_table_column desc limit 0, 20;
I don't want to use setMaxResults if possible. This definitely was possible in the older version of Hibernate/HQL, but it seems to have disappeared.
This was posted on the Hibernate forum a few years back when asked about why this worked in Hibernate 2 but not in Hibernate 3:
Limit was never a supported clause
in HQL. You are meant to use
setMaxResults().
So if it worked in Hibernate 2, it seems that was by coincidence, rather than by design. I think this was because the Hibernate 2 HQL parser would replace the bits of the query that it recognised as HQL, and leave the rest as it was, so you could sneak in some native SQL. Hibernate 3, however, has a proper AST HQL Parser, and it's a lot less forgiving.
I think Query.setMaxResults() really is your only option.
// SQL: SELECT * FROM table LIMIT start, maxRows;
Query q = session.createQuery("FROM table");
q.setFirstResult(start);
q.setMaxResults(maxRows);
If you don't want to use setMaxResults() on the Query object then you could always revert back to using normal SQL.
The setFirstResult and setMaxResults Query methods
For a JPA and Hibernate Query, the setFirstResult method is the equivalent of OFFSET, and the setMaxResults method is the equivalent of LIMIT:
List<Post> posts = entityManager.createQuery("""
select p
from Post p
order by p.createdOn
""")
.setFirstResult(10)
.setMaxResults(10)
.getResultList();
The LimitHandler abstraction
The Hibernate LimitHandler defines the database-specific pagination logic, and as illustrated by the following diagram, Hibernate supports many database-specific pagination options:
Now, depending on the underlying relational database system you are using, the above JPQL query will use the proper pagination syntax.
MySQL
SELECT p.id AS id1_0_,
p.created_on AS created_2_0_,
p.title AS title3_0_
FROM post p
ORDER BY p.created_on
LIMIT ?, ?
PostgreSQL
SELECT p.id AS id1_0_,
p.created_on AS created_2_0_,
p.title AS title3_0_
FROM post p
ORDER BY p.created_on
LIMIT ?
OFFSET ?
SQL Server
SELECT p.id AS id1_0_,
p.created_on AS created_on2_0_,
p.title AS title3_0_
FROM post p
ORDER BY p.created_on
OFFSET ? ROWS
FETCH NEXT ? ROWS ONLY
Oracle
SELECT *
FROM (
SELECT
row_.*, rownum rownum_
FROM (
SELECT
p.id AS id1_0_,
p.created_on AS created_on2_0_,
p.title AS title3_0_
FROM post p
ORDER BY p.created_on
) row_
WHERE rownum <= ?
)
WHERE rownum_ > ?
The advantage of using setFirstResult and setMaxResults is that Hibernate can generate the database-specific pagination syntax for any supported relational databases.
And, you are not limited to JPQL queries only. You can use the setFirstResult and setMaxResults method seven for native SQL queries.
Native SQL queries
You don't have to hardcode the database-specific pagination when using native SQL queries. Hibernate can add that to your queries.
So, if you're executing this SQL query on PostgreSQL:
List<Tuple> posts = entityManager.createNativeQuery(
SELECT
p.id AS id,
p.title AS title
from post p
ORDER BY p.created_on
""", Tuple.class)
.setFirstResult(10)
.setMaxResults(10)
.getResultList();
Hibernate will transform it as follows:
SELECT p.id AS id,
p.title AS title
FROM post p
ORDER BY p.created_on
LIMIT ?
OFFSET ?
Cool, right?
Beyond SQL-based pagination
Pagination is good when you can index the filtering and sorting criteria. If your pagination requirements imply dynamic filtering, it's a much better approach to use an inverted-index solution, like ElasticSearch.
If you don't want to use setMaxResults, you can also use Query.scroll instead of list, and fetch the rows you desire. Useful for paging for instance.
You can easily use pagination for this.
#QueryHints({ #QueryHint(name = "org.hibernate.cacheable", value = "true") })
#Query("select * from a_table order by a_table_column desc")
List<String> getStringValue(Pageable pageable);
you have to pass new PageRequest(0, 1)to fetch records and from the list fetch the first record.
You need to write a native query, refer this.
#Query(value =
"SELECT * FROM user_metric UM WHERE UM.user_id = :userId AND UM.metric_id = :metricId LIMIT :limit", nativeQuery = true)
List<UserMetricValue> findTopNByUserIdAndMetricId(
#Param("userId") String userId, #Param("metricId") Long metricId,
#Param("limit") int limit);
String hql = "select userName from AccountInfo order by points desc 5";
This worked for me without using setmaxResults();
Just provide the max value in the last (in this case 5) without using the keyword limit.
:P
My observation is that even you have limit in the HQL (hibernate 3.x), it will be either causing parsing error or just ignored. (if you have order by + desc/asc before limit, it will be ignored, if you don't have desc/asc before limit, it will cause parsing error)
If can manage a limit in this mode
public List<ExampleModel> listExampleModel() {
return listExampleModel(null, null);
}
public List<ExampleModel> listExampleModel(Integer first, Integer count) {
Query tmp = getSession().createQuery("from ExampleModel");
if (first != null)
tmp.setFirstResult(first);
if (count != null)
tmp.setMaxResults(count);
return (List<ExampleModel>)tmp.list();
}
This is a really simple code to handle a limit or a list.
Criteria criteria=curdSession.createCriteria(DTOCLASS.class).addOrder(Order.desc("feild_name"));
criteria.setMaxResults(3);
List<DTOCLASS> users = (List<DTOCLASS>) criteria.list();
for (DTOCLASS user : users) {
System.out.println(user.getStart());
}
Below snippet is used to perform limit query using HQL.
Query query = session.createQuery("....");
query.setFirstResult(startPosition);
query.setMaxResults(maxRows);
You can get demo application at this link.
You can use below query
NativeQuery<Object[]> query = session.createNativeQuery("select * from employee limit ?");
query.setparameter(1,1);
#Query(nativeQuery = true,
value = "select from otp u where u.email =:email order by u.dateTime desc limit 1")
public List<otp> findOtp(#Param("email") String email);

Can nested query in JPQL access outer query

So I'm curious if a nested SELECT can reference it's outer SELECT in order to compare values. I haven't been able to test or see many examples on this topic.
As an example, I'm trying to write a query to select all Clothes rows that has a tag (some number) that is within a given list and has the highest time that is prior to given time (which is total number of seconds). The query in question is below:
SELECT c FROM Clothes c WHERE c.tag IN :tagList
AND (c.timeOfSale = (SELECT MAX(n.timeOfSale) FROM Clothes k
WHERE (c.tag = k.tag) AND (k.timeOfSale) < (:time))) GROUP BY c.tag
Is the comparison c.tag = k.tag valid? If not, is there an alternative?
#Query("SELECT b FROM Business b WHERE b <> :currentBusiness "
+ "and exists "
+ "(Select i from InterestMaster i, BusinessInterest bI where bI.interestMaster = i and bI.business = b"
+ "and i in (:userInterests))")
Page<Business> getCommunityBusiness(#Param("currentBusiness") Business currentBusiness, #Param("userInterests") List<InterestMaster> userInterests,Pageable pageable);
I am using the above JPQL and its working fine. So yes nested query can access outer query.
Yes. They're called correlated queries, where the subquery is evaluated for each row of outer query.

getting count(*) using createSQLQuery in hibernate?

I have several sql queries that I simply want to fire at the database.
I am using hibernate throughout the whole application, so i would prefer to use hibernate to call this sql queries.
In the example below i want to get count + name, but cant figure out how to get that info when i use createSQLQuery().
I have seen workarounds where people only need to get out a single "count()" from the result, but in this case I am using count() + a column as ouput
SELECT count(*), a.name as count FROM user a
WHERE a.user_id IN (SELECT b.user_id FROM user b)
GROUP BY a.name
HAVING COUNT(*) BETWEEN 2 AND 5;
fyi, the above query would deliver a result like this if i call it directly on the database:
1, John
2, Donald
1, Ralph
...
Alternatively, you can use
SQLQuery query = session.createSQLQuery("SELECT count(*) as num, a.name as name FROM user a WHERE a.user_id IN (SELECT b.user_id FROM user b) GROUP BY a.name HAVING COUNT(*) BETWEEN 2 AND 5;";
query.addScalar("num", Hibernate.INTEGER).addScalar("name", Hibernate.STRING);
// you might need to use org.hibernate.type.StandardBasicTypes.INTEGER / STRING
// for Hibernate v3.6+,
// see https://hibernate.onjira.com/browse/HHH-5138
List<Object> result = query.list();
// result.get(2*i + 0) -> i-th row num
// result.get(2*i + 1) -> i-th row name
I'm using this in case of time-pressure, imo much faster to code then creating your own beans & transformers.
Cheers!
Jakub
cheers for the info Thomas, worked wonderful for generating objects
the problem i had with my initial query was that "count" was a reserved word :P
when i changed the name to something else it worked.
If your SQL statement looks like this SELECT count(*) as count, a.name as name... you could use setResultTransformer(new AliasToBeanResultTransformer(YourSimpleBean.class)) on your Query.
Where YourSimpleBean has the fields Integer count and String name respectively the setters setCount and setName.
On execution of the query with query.list() hibernate will return a List of YourSimpleBeans.

LINQ To SQL Paging

I've been using .Skip() and .Take() extension methods with LINQ To SQL for a while now with no problems, but in all the situations I've used them it has always been for a single table - such as:
database.Users.Select(c => c).Skip(10).Take(10);
My problem is that I am now projecting a set of results from multiple tables and I want to page on the overall set (and still get the benefit of paging at the DB).
My entity model looks like this:
A campaign [has many] groups, a group [has many] contacts
this is modelled through a relationship in the database like
Campaign -> CampaignToGroupMapping -> Group -> GroupToContactMapping -> Contact
I need to generate a data structure holding the details of a campaign and also a list of each contact associated to the campaign through the CampaignToGroupMapping, i.e.
Campaign
CampaignName
CampaignFrom
CampaignDate
Recipients
Recipient 1
Recipient 2
Recipient n...
I had tried to write a LINQ query using .SelectMany to project the set of contacts from each group into one linear data set, in the hope I could .Skip() .Take() from that.
My attempt was:
var schedule = (from c in database.Campaigns
where c.ID == highestPriority.CampaignID
select new PieceOfCampaignSchedule
{
ID = c.ID,
UserID = c.UserID,
Name = c.Name,
Recipients = c.CampaignGroupsMappings.SelectMany(d => d.ContactGroup.ContactGroupMappings.Select(e => new ContactData() { /*Contact Data*/ }).Skip(c.TotalSent).Take(totalRequired)).ToList()
}).SingleOrDefault();
The problem is that the paging (with regards to Skip() and Take()) is happening for each group, not the entire data set.
This means if I use the value 200 for the parameter totalRequired (passed to .Take()) and I have 3 groups associated with this campaign, it will take 200 from each group - not 200 from the total data from each group associated with the campaign.
In SQL, I could achieve this with a query such as:
select * from
(
select [t1].EmailAddress, ROW_NUMBER() over(order by CampaignID desc) as [RowNumber] from contacts as [t1]
inner join contactgroupmapping as [t2] on [t1].ID = [t2].ContactID
inner join campaigngroupsmapping as [t3] on [t3].ContactGroupID = [t2].GroupID
where [t3].CampaignID = #HighestPriorityCampaignID
) as [Results] where [Results].[RowNumber] between 500 and 3000
With this query, I'm paging over the combined set of contacts from each group associated with the particular campaign. So my question is, how can I achieve this using LINQ To SQL syntax instead?
To mimic the SQL query you provided you would do this:
var schedule = (from t1 in contacts
join t2 in contactgroupmapping on t1.ID equals t2.GroupID
join t3 in campaigngroupsmapping on t3.ContactGroupID = t2.GroupID
where t3.CampaignID = highestPriority.CampaignID
select new PieceOfCampaignSchedule
{
Email = t1.EmailAddress
}).Skip(500).Take(2500).ToList()
Are you trying to page over campaigns, recipients, or both?
Use a view to aggregate the results from the multiple tables and then use LINQ over the view
I think your attempt is really close; Maybe I'm missing something, but I think you just need to close your SelectMany() before the Skip/Take:
Recipients = c.CampaignGroupsMappings.SelectMany(d => d.ContactGroup.ContactGroupMappings.Select(e => new ContactData() { /*Contact Data*/ })).Skip(c.TotalSent).Take(totalRequired).ToList()
Note: added ")" after "/* Contact Data */ })" and removed ")" from after ".Take(totalRequired)"