hibernate nested query HQL/criteria - java

I'm trying to express a somewhat complex query in hibernate either with HQL or Criteria but I didn't find the way. I'm aware that cannot expect the orm to solve everything but I'm not such an expert in hibernate and I got so closed that I'm hoping that I'm missing something silly.
I've been looking in SO for questions and there is this one and nobody answered except the author, who gave up and posted a solution using plain SQL. So maybe I'm wasting my time and I should do the same. And maybe you consider this is a duplicate... I'm not sure.
The query I'm trying to build is
select * from user_rank
where user_id in (select user_id
from (select user_id as user_id, row_number() over() as rownumber
from user_rank
where ... additional parameters ...
order by rank desc) as maxrows
where rownumber <= :number)
and ... additional parameters ...
The table stores user ranking and I'm getting the top 5. The row_number() over() is just a trick for postgresql to be able to filter the top 5 in the outer query.
My closest attempt was for Criteria and it looks like this,
ProjectionList pl = Projections.projectionList()
.add(Projections.property("id.userId"))
.add(Projections.sqlProjection("row_number() over() as rownum", new String[] {"rownum"}, new Type[] { new IntegerType() }));
DetachedCriteria subCriteria = DetachedCriteria.forClass(UserRank.class)
.addOrder(Property.forName("rank").desc())
.add(Property.forName("... additional ..."))
.add(Property.forName("... additional ..."))
.setProjection(pl)
Criteria criteria2 = session.createCriteria(UserRank.class)
.add(Property.forName("id.userId").in(subCriteria))
.add(Property.forName("... additional ..."))
which would work except for the fact that it does not select the top 5 users. I cannot add the following to subCriteria
.add(Restrictions.sqlRestriction("rownum <= 5"));
because the column rownum does not exist yet. And there is no way to add it in the in statement of criteria2. I guess I could paginate over the results and it would not be too slow?
I could also solve it like this surely it would be slower.
Criteria usersC = session.createCriteria(UserRank.class)
.addOrder(Property.forName("rank").desc())
.add(Property.forName("... additional ..."))
.add(Property.forName("... additional ..."))
.setProjection(pl);
// Java code that fetches from the query and creates the list `users`
Criteria criteria = session.createCriteria(UserPageRankEvolution.class)
.add(Restrictions.in("id.userId", users))
.add(Property.forName("... additional ..."))
With HDL I didn't get even closed.
Many thanks for your patience and for your help.

Related

QueryDsl-SQL: update with join (not using subqueries)

I am using queryDsl-Sql I need to do an update statement with a join clause like:
update MY_TABLE t1 set t1.MY_FIELD = 'SOME_VALUE'
JOIN MY_TABLE_2 t2 ON t1.FIELD_WITH_FK = t2.ID
where t2.OTHER_FIELD=12324556789 AND t2.OTHER_FIELD like '%something%'
Unfortunately, on queryDsl I was only able to do subqueries during update but not joins:
dsl.update(table1)
.set(my_field, "SOME_VALUE")
.where(
field_with_fk.in(
dsl.select(id).from(table2).where(other_field.eq(12324556789))
, fieldName.like("%something%"));
Which is translated to a subquery (as expected) and happens to take much much more time (difference between 10 seconds using joins and more than 1h using subselects)
I have seen that on JPAUpdateClause is possible to do so but I am not using QueryDsl-jpa here. QueryDSL-SQL has only SqlUpdateClause and I was not able to find how to join different tables when using It.
I think this is more a workaround than a solution, but maybe helps
also others:
dsl.update(table1)
.set(field1, myValue)
.addFlag(
QueryFlag.Position.START_OVERRIDE, //it replaces update by update with join
Expressions.stringTemplate("update {0} join {1} on {2} = {3} #", // I need # to ignore table1 otherwise incorrect SQL
table1, table2, table1FieldWithFkToTable2, table2Id))
.where(other_field.eq(12324556789), fieldName.like("%something%"));
this generates the following SQL:
update table1 join table2 on table1FieldWithFkToTable2 = table2Id #table1
set field1 = myValue
where other_field=12324556789 and fieldName like '%something%'

Is this query possible using Criteria or DetachedCriteria Hibernate

The question is simple can this query be done in Hibernate using Criteria or DetachedCriteria? i guess not but i wanted to do this question maybe exist a workaround.
SELECT
COLUMNS
FROM table
WHERE id not in (
SELECT * FROM (
SELECT id
FROM table
WHERE
SOMECONDITIONS
ORDER BY timestamp desc limit 0, 15
)
as t);
I will mark the answer by #Dean Clark as correct but another question arises is the following
where can i find the findByCriteria from SessionFactory we are not using Spring
To exactly match you query, you really need to do it with 2 steps, but I'd avoid this if possible:
final Criteria innerCriteria = getSession().createCriteria(YourEntity.class);
// SOME CONDITIONS
innerCriteria.add(Restrictions.eq("someColumn", "someValue"));
innerCriteria.addOrder(Order.desc("timestamp"));
innerCriteria.setMaxResults(15);
innerCriteria.setProjection(Projections.id());
List<YourIdClass> ids = innerCriteria.list();
final Criteria criteria = getSession().createCriteria(YourEntity.class);
criteria.add(Restrictions.not(Restrictions.in("id", ids)));
List<YourEntity> results = criteria.list();
Would the objects you're trying to identify have the same "SOMECONDITIONS"? If so, this would functionally accomplish what you're looking for:
final DetachedCriteria criteria = DetachedCriteria.forClass(YourEntity.class);
// SOME CONDITIONS
criteria.add(Restrictions.eq("someColumn", "someValue"));
criteria.addOrder(Order.desc("timestamp"));
getHibernateTemplate().findByCriteria(criteria, 16, 9999999);

Hibernate 2 with MSSQL for ORDER BY

I have been working with Oracle and Postgre and recently switched to MS SQL 2012.
I use hibernate in my application and wherever I have used the Order by Criteria:
(criteria.addOrder(Order.asc("applicationId")));
It causes an error saying:
aggregate functions dont work.
Once I comment that line out my program works and data can be retrieved.
I'm using Hibernate 3.
Is there any way to order it through hibernate without this error?
edit..
This is one error I get,
Column "SKY.tcrent.RENTNO" is invalid in the ORDER BY clause because
it is not contained in either an aggregate function or the GROUP BY
clause.
Edit 2..
MY query
Query tcSchaduleQ = getSession().createQuery("SELECT SUM(tcs.dueAmount) FROM TrialCalculationSchedule tcs WHERE tcs.facilityId=:facilityId AND tcs.rentalNumber>:rentalNumber AND tcs.dueDate>:dueDate AND dueTypeId IN(:dueTypeId) ORDER BY tcs.rentalNumber ").setInteger("rentalNumber", facility.getPeriod() - noOfprePayments).setInteger("facilityId",facility.getFacilityId()).setDate("dueDate", date).setParameterList("dueTypeId", plist);
Number tcsAmt = (Number) tcSchaduleQ.uniqueResult();
and this is what hibernate generates in HQL
SELECT
SUM(tcs.dueAmount)
FROM
TrialCalculationSchedule tcs
WHERE
tcs.facilityId=:facilityId
AND tcs.rentalNumber>:rentalNumber
AND tcs.dueDate>:dueDate
AND dueTypeId IN(
:dueTypeId
)
ORDER BY
tcs.rentalNumber
and this is the SQL
select
SUM(trialcalcu0_.DUEAMT) as col_0_0_
from
SKYBANKSLFHP.tcrent trialcalcu0_
where
trialcalcu0_.FACID=?
and trialcalcu0_.RENTNO>?
and trialcalcu0_.DUEDATE>?
and (
trialcalcu0_.DUETYPEID in (
? , ?
)
)
order by
trialcalcu0_.RENTNO
Look Like you mix aggregate and non-aggregate expressions .If you are using any aggregate function like AVG() in Select query with some other non-aggregate then you must use Group By ..
Try something like this
createQuery("SELECT SUM(tcs.dueAmount) As DueAmount ...
If you are using Criteria then it should be like this
Criteria crit = sess.createCriteria(Insurance.class);
ProjectionList proList = Projections.projectionList();
proList.add(Projections.sum("investementAmount"));
crit.setProjection(proList);
List sumResult = crit.list();

Is it allowed to use the same DetachedCriteria within one Criteria multiple times?

I am just wondering if it is allowed in Hibernate to use the same DetachedCriteria object within one Criteria multiple times. Imagine the following case:
DetachedCriteria dCriteria = DetachedCriteria.forClass(A.class)
.add(Restrictions.eq("id", 1))
.setProjection(Projections.property("id"));
Criteria criteria = session.createCriteria(B.class)
.add(
Restrictions.or(
Restrictions.and(
Subqueries.exists(dCriteria),
Restrictions.eq("id", 1)
),
Restrictions.and(
Subqueries.notExists(dCriteria),
Restrictions.eq("id", 2)
)
)
.setProjection(Projections.property("id"));
Is the usage of dCriteria twice within the this criteria allowed? It seems to work but i am not sure if it might lead to problems in more complex cases (maybe the DetachedCriteria saves same state information during query generation?). I already did some reasearches but i couldn't find an explicit answer.
No it isn't (always) safe to re-use DetachedCriteria. For example: get a list and a rowcount, reusing the DetachedCriteria:
DetachedCriteria dc = DetachedCriteria.forClass(A.class);
Criteria c1 = dc.getExecutableCriteria(session);
c1.setProjection(Projections.rowCount());
long count = ((Number) c1.uniqueResult()).longValue();
System.out.println(count + " result(s) found:");
Criteria c2 = dc.getExecutableCriteria(session);
System.out.println(c2.list());
This prints:
Hibernate: select count(*) as y0_ from A this_
4 result(s) found:
Hibernate: select count(*) as y0_ from A this_ <-- whoops
[4] <-- whoops again
Really simple things that don't alter the DetachedCriteria might be safe, but in general wrap the generation in some kind of factory and re-generate them each time you need them.
Officially, cloning DetachedCriteria on each call to getExecutableCriteria will never happen. See their issues, particularly HHH-635 and HHH-1046 where Brett Meyer states: "The Criteria API is considered deprecated", and the developers guide (v4.3 §12) which states:
Hibernate offers an older, legacy org.hibernate.Criteria API which should be considered deprecated. No feature development will target those APIs. Eventually, Hibernate-specific criteria features will be ported as extensions to the JPA javax.persistence.criteria.CriteriaQuery.
EDIT: In your example you re-use the same DetachedCriteria inside the same query. The same caveats therefore apply - if you, for instance, use setProjection with one of the uses, things go wrong with the second use. For example:
DetachedCriteria dCriteria = DetachedCriteria.forClass(A.class)
.add(Restrictions.eq("id", 1))
.setProjection(Projections.property("id"));
Criteria criteria = session.createCriteria(B.class)
.add(
Restrictions.or(
Restrictions.and(
Subqueries.exists(dCriteria
.add(Restrictions.eq("text", "a1")) // <-- Note extra restriction
.setProjection(Projections.property("text"))), // <-- and projection
Restrictions.eq("idx", 1)
),
Restrictions.and(
Subqueries.notExists(dCriteria),
Restrictions.eq("idx", 2)
)
))
.setProjection(Projections.property("id"));
Object o = criteria.list();
This yields the SQL:
select this_.idx as y0_ from B this_
where (
(exists
(select this_.text as y0_ from A this_ where this_.id=? and this_.text=?) and this_.idx=?)
or (not exists
(select this_.text as y0_ from A this_ where this_.id=? and this_.text=?) and this_.idx=?))
We didn't ask for the text=? part of the not exists, but we got it due to the re-use of the DetachedCriteria †
† This leads to bad situations where, if you applied the .add(Restrictions.eq("text" ... to both uses of dCriteria, it would appear twice in both the exists and not exists in the SQL

How to convert nested SQL to HQL

I am new to the Hibernate and HQL. I want to write an update query in HQL, whose SQL equivalent is as follows:
update patient set
`last_name` = "new_last",
`first_name` = "new_first"
where id = (select doctor_id from doctor
where clinic_id = 22 and city = 'abc_city');
doctor_id is PK for doctor and is FK and PK in patient. There is one-to-one mapping.
The corresponding Java classes are Patient (with fields lastName, firstName, doctorId) and Doctor (with fields doctorId).
Can anyone please tell what will be the HQL equivalent of the above SQL query?
Thanks a lot.
String update = "update Patient p set p.last_name = :new_last, p.first_name = :new_first where p.id = some (select doctor.id from Doctor doctor where doctor.clinic_id = 22 and city = 'abc_city')";
You can work out how to phrase hql queries if you check the specification. You can find a section about subqueries there.
I don't think you need HQL (I know, you ask that explicitly, but since you say you're new to Hibernate, let me offer a Hibernate-style alternative). I am not a favor of HQL, because you are still dealing with strings, which can become hard to maintain, just like SQL, and you loose type safety.
Instead, use Hibernate criteria queries and methods to query your data. Depending on your class mapping, you could do something like this:
List patients = session.CreateCriteria(typeof(Patient.class))
.createAlias("doctor", "dr")
.add(Restrictions.Eq("dr.clinic_id", 22))
.add(Restrictions.Eq("dr.city", "abc_city"))
.list();
// go through the patients and set the properties something like this:
for(Patient p : patients)
{
p.lastName = "new lastname";
p.firstName = "new firstname";
}
Some people argue that using CreateCriteria is difficult. It takes a little getting used to, true, but it has the advantage of type safety and complexities can easily be hidden behind generic classes. Google for "Hibernate java GetByProperty" and you see what I mean.
update Patient set last_name = :new_last , first_name = :new_first where patient.id = some(select doctor_id from Doctor as doctor where clinic_id = 22 and city = abc_city)
There is a significant difference between executing update with select and actually fetching the records to the client, updating them and posting them back:
UPDATE x SET a=:a WHERE b in (SELECT ...)
works in the database, no data is transferred to the client.
list=CreateCriteria().add(Restriction).list();
brings all the records to be updated to the client, updates them, then posts them back to the database, probably with one UPDATE per record.
Using UPDATE is much, much faster than using criteria (think thousands of times).
Since the question title can be interpreted generally as "How to use nested selects in hibernate", and the HQL syntax restricts nested selects only to be in the select- and the where-clause, I would like to add here the possibility to use native SQL as well. In Oracle - for instance - you may also use a nested select in the from-clause.
Following query with two nested inner selects cannot be expressed by HQL:
select ext, count(ext)
from (
select substr(s, nullif( instr(s,'.', -1) +1, 1) ) as ext
from (
select b.FILE_NAME as s from ATTACHMENT_B b
union select att.FILE_NAME as s from ATTACHEMENT_FOR_MAIL att
)
)
GROUP BY ext
order by ext;
(which counts, BTW, the occurences of each distinct file name extension in two different tables).
You can use such an sql string as native sql like this:
#Autowired
private SessionFactory sessionFactory;
String sql = ...
SQLQuery qry = sessionFactory.getCurrentSession().createSQLQuery(sql);
// provide an appropriate ResultTransformer
return qry.list();

Categories