Join cartesian product - java

Context
I send some files to some enterprises every week. I need to restitute for each week and each enterprise whether the file is sent or not.
Tables
ent (enterprise)
wek (week)
fil (file : references wek and ent)
Solution with pure SQL
Make a cartesian product between ent and wek then left outer join fil. This works :
select * from
(
select * from wek, ent e
) as t1
left join fil f
on f.ent_id = t1.ent_id
and f.wek_id = t1.wek_id
Problem :
How to translate this into JPA (in the CriteriaBuilder way)?
For example, if I try :
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<ResultClass> query = cb.createQuery(ResultClass.class);
Root<Week> week = query.from(Week.class);
Root<Enterprise> query.from(Enterprise.class);
Expression<???> cartesianProduct = ??? //How?
cartesianProduct.leftJoin(???_.file);
query.where(
cb.equal(file.get(File_.wek_id), week.get(Week.week_id));
)
Using 2 "from" clauses gives me the cartesian product but how do I left join this result?
Unstatisfaying solution :
Create a view :
CREATE VIEW view_ent_wek AS
SELECT ent_id as ent_id, wek_id as wek_id, ent_id || '-' || ent_id as id
FROM ent, wek;
Map it to an entity :
#Entity
#Table(name = "view_ent_wek")
public class WeekEnterprise {
#Id
#Column(name = "id")
private String id;
#ManyToOne
#JoinColumn(name = "ent_id")
private Enterprise enterprise;
#ManyToOne
#JoinColumn(name = "wek_id")
private Week week;
[...]
Then I can use it in a query :
Root<WeekEnterprise> weekEnterprise = query.from(WeekEnterprise.class);
weekEnterprise.join(...)
I don't like this solution because it makes me create a view that is obviously not necessary. Any idea?

I don't see the point of doing the reporting-style SQL query with JPA, let alone with Criteria Query.
Of course, JPA (and its implementations) do support tuple queries (as opposed to entity queries, see also: Hibernate tuple criteria queries), but tuple queries is not what JPA is good at. Imagine you want to change that report and add more calculations. Wouldn't it be much easier to do that with SQL?
Using JPA for mapping to Java objects and for transaction modelling still makes sense, and thus, your SQL view solution is already quite good, or if you prefer not to store the SQL in a view, use JPA's native query API:
List<Object[]> list = em.createNativeQuery(sql).getResultList();
Or:
List<WeekEnterprise> list = em.createNativeQuery(sql, WeekEnterprise.class)
.getResultList();

I have some ideas:
With 2 JPA queries. First: fetch the cartesian product of wek and enterprises. Second: take an inner join between wek, enterprises and files. Uses a map (wek_id+ent_id => Tuple(Wek, Ent, File)) to quickly identify where to put the file.
Write plain SQL queries and execute them with the JPA API.
(Didn't think much about this one) Create a back reference from Wek and Ent to File (lazy loaded) and then you should be able to continue your first idea.

Don't know much about JPA, but about Postgres. And I would suggest this superior query:
SELECT *
FROM wek CROSS JOIN ent
LEFT JOIN fil USING (ent_id, wek_id);
You don't need the subselect.
The only difference in the result (besides being shorter and faster): You get the columns ent_id and wek_id once in the output (which might solve a problem with duplicated column names).
Should be easy to translate to JPA now.

Related

JPA CriteriaQuery with LEAST and GREATEST functions

I am have a problem where i need to join two tables using the LEAST and GREATEST functions, but using JPA CriteriaQuery. Here is the SQL that i am trying to duplicate...
select * from TABLE_A a
inner join TABLE_X x on
(
a.COL_1 = least(x.COL_Y, x.COL_Z)
and
a.COL_2 = greatest(x.COL_Y, x.COL_Z)
);
I have looked at CriteriaBuilder.least(..) and greatest(..), but am having a difficult time trying to understand how to create the Expression<T> to pass to either function.
The simplest way to compare two columns and get the least/greatest value is to use the CASE statement.
In JPQL, the query would look like
select a from EntityA a join a.entityXList x
where a.numValueA=CASE WHEN x.numValueY <= x.numValueZ THEN x.numValueY ELSE x.numValueZ END
and a.numValueB=CASE WHEN x.numValueY >= x.numValueZ THEN x.numValueY ELSE x.numValueZ END
You can code the equivalent using CriteriaBuilder.selectCase() but I've never been a big fan of CriteriaBuilder. If requirements forces you to use CriteriaBuilder then please let me know and I can try to code the equivalent.
CriteriaBuilder least/greatest is meant to get the min/max value of all the entries in one column. Let's say you want to get the Entity that had the alphabetically greatest String name. The code would look like
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery query = cb.createQuery(EntityX.class);
Root<EntityX> root = query.from(EntityX.class);
Subquery<String> maxSubQuery = query.subquery(String.class);
Root<EntityX> fromEntityX = maxSubQuery.from(EntityX.class);
maxSubQuery.select(cb.greatest(fromEntityX.get(EntityX_.nameX)));
query.where(cb.equal(root.get(EntityX_.nameX), maxSubQuery));
I created a sample Spring Data JPA app that demonstrates these JPA examples at
https://github.com/juttayaya/stackoverflow/tree/master/JpaQueryTest
It turns out that CriteriaBuilder does support calling LEAST and GREATEST as non-aggregate functions, and can be accessed by using the CriteriaBuilder.function(..), as shown here:
Predicate greatestPred = cb.equal(pathA.get(TableA_.col2),
cb.function("greatest", String.class,
pathX.get(TableX_.colY), pathX.get(TableX_.colZ)));

How to write left join in hibernate?

I have following as table structure
**BPV table**
id, vid, bid
**vt table**
vid, name, gender
**uv table**
uvid, vid, cast,...
i want to write left join between BPV ,uv based on vid in hibernate (mysql)
bpv entity have vt as one to one as well as uv have vt with one to one but there is no bpv &uv in realation
Hibernate has Criteria API, which is a good way to execute queries in a OO approach. If you have your persistent entities, then you can do this:
Criteria criteria = session.createCriteria(BPV.class, "bpv");
criteria.createCriteria("bpv.vt", "vt", CriteriaSpecification.LEFT_JOIN);
criteria.createCriteria("vt.uv", "uv", CriteriaSpecification.LEFT_JOIN);
// add restrictions
return criteria.list();
Hibernate SQL:
from BPV where BPV.uv.cast = "your_condition"
It requires properly described entities and dependency;
or pure SQL:
select * from BPV left join uv on BPV.vid = uv.vid where uv.cast = "your_condition"
What you're asking for is not posible, there is a Theta Join solution but it only supports inner join, so you have to give up on that approach and do one of the following solutions:
Map the proper relationship:
If this query is a unusual escenario, you dont necesary have to change your actual mapping , you can make a new dto Class with the new mapping, and use that one for this query.
Use native SQL:
This is prettly simple too, use .createSQLQuery(), to run the native query and you can use .addScalar() + .setResultTransformer(Transformers.aliasToBean(someDto.class)) to get a list of your entity.

HQL query join tables

I am using hibernate to connect to my database for a project.
I would like to have a query that gets the products out of my database with the discription and name in a certain language. The parameter I have is the short name for the language, so first I would have to get the id of the language and then get the text in the required languages.
I have tried the following hql query, without success.
from Products as p
where p.productlanguages.languages.shortname like 'eng'
This is an image of the part of the database where the data should come from:
I have got the desired result with an sql query, but I can't seem to get it to work in hibernate. But I would prefer to do this in hql.
SELECT * FROM products p
INNER JOIN productlanguage pl ON pl.Products_id = p.id
WHERE pl.Languages_id =
(
SELECT id FROM languages
WHERE Shortname = 'eng'
);
Could anyone tell me how to build this hql query?
Thank you.
Try below:
from Products p INNER JOIN p.productlanguages pl
where pl.languages.shortname ='eng'
I am assuming that you have mapped Product-Productlanguages relationship as OneToMany and Productlanguages-Langages relationship as ManyToOne as depicted in your E-R diagram.
EDIT: There seems to be a typo in Productlanguage mapping at line public Languages getLanguages() {barcode, remove the barcode in the end.
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="Languages_id", nullable=false, insertable=false, updatable=false)
public Languages getLanguages() {barcode
return this.languages;
}

Need help creating JPA criteria query

I'm building my first Java EE web application using Glassfish and JSF. I'm fairly new to the criteria query and I have a query I need to perform but the javaee6 tutorial seems a little thin on examples. Anyway, I'm having a hard time creating the query.
Goal: I want to pull the company with the most documents stored.
Companies have a OneToMany relationship with Documents.
Documents has a ManyToOne relationship with several tables, the "usertype" column distinguishes them.
MySQL query:
SELECT USERID, COUNT(USERID) AS CNT
FROM DOCUMENTS
WHERE USERTYPE="COMPANY"
GROUP BY USERID
ORDER BY CNT DESC
Thanks
--update--
Based on user feedback, here is what I have so far:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Documents> cqry = cb.createQuery(Documents.class);
//Intersting Stuff
Root<Documents> root = cqry.from(Documents.class);
Expression userid = root.get("userID");
Expression usertype = root.get("userType");
Expression count = cb.count(userid);
cqry.multiselect(userid, count);
Predicate userType = cb.equal(usertype, "COMPANY");
cqry.where(userType);
cqry.groupBy(userid);
cqry.orderBy(cb.desc(count));
//more boilerplate
Query qry = em.createQuery(cqry);
List<Documents> results = qry.getResultList();
The error I get is:
Exception Description: Partial object queries are not allowed to maintain the cache or be edited. You must use dontMaintainCache().
Typical error, means nothing to me!
Your query doesn't return a complete entity object as you're only selecting two fields of the given table (this is why you're getting an error that says yadayadapartialyadayada).
Your solution is almost right, here's what you need to change to make it work—making it partial.
Instead of a plain CriteriaQuery<...> you have to create a tuple CriteriaQuery<..> by calling CriteriaBuilder.createTupleQuery(). (Basically, you can call CriteriaBuilder.createQuery(...) and pass Tuple.class to it as an argument. Tuple is a sort of wildcard entity class.)
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> cq= cb.createTupleQuery();
Root<Documents> root = cq.from(Documents.class);
Expression<Integer> userId = root.get("USERID");
Expression<String> userType = root.get("USERTYPE");
Expression<Long> count = cb.count(userId);
cq.multiselect(userId.alias("USERID"), count.alias("CNT"));
cq.where(cb.equal(userType, "COMPANY");
cq.groupBy(userId);
cq.orderBy(cb.desc(count));
TypedQuery<Tuple> tq = em.createQuery(cq);
for (Tuple t : tq.getResultsList()) {
System.out.println(t.get("USERID"));
System.out.println(t.get("CNT"));
}
(Accessing fields of a Tuple gave me an error if I didn't use aliases for them (in multiselect(...)). This is why I've used aliases, but this can be tackled more cleanly by using JPA 2's Metamodel API, which is described in the specification quite thoroughly. )
The documentation for CriteriaQuery.multiselect(...) describes the behaviour of queries using Tuple objects more deeply.
If you are using Hibernate, this should work:
ProjectionList pl = Projections.projectionList()
.add(Projections.groupProperty("userid"))
.add(Projections.property("userid"))
.add(Projections.count("userid"));
Criteria criteria = session.createCriteria(Document.class)
.add(Restrictions.eq("usertype",usertype))
.setProjection(pl)
.addOrder(Order.desc("cnt"));
Hope it helps!
Take a look into this easy tutorial. It uses JPA2 and Criteria
http://www.jumpingbean.co.za/blogs/jpa2-criteria-api
Regards!
You need to add a constructor to Documents with only userid and count because you will need it on:
cqry.multiselect(userid, count);

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