I have trouble to translate some easy SQL statement to JPQL, because of a used subquery, which is not supported by JPQL.
Can someone give me a hint, how to achieve the same result with JPQL or JPA2 Criteria API?
Given (Simplified fake data to demonstrate the problem):
CREATE TABLE person (id integer, name text);
CREATE TABLE phone (id integer, person_id integer, type text, number text);
INSERT INTO person VALUES (1, "John");
INSERT INTO person VALUES (2, "Mike");
INSERT INTO person VALUES (3, "Paul");
INSERT INTO person VALUES (4, "Walter");
INSERT INTO phone VALUES (1, 1, "MOBILE", "+49-123-11111");
INSERT INTO phone VALUES (2, 1, "HOME" , "+49-123-22222");
INSERT INTO phone VALUES (3, 2, "WORK" , "+49-123-33333");
INSERT INTO phone VALUES (4, 4, "MOBILE", "+49-123-44444");
-- Select all from person and their mobile number if possible
-- This query has to be translated to JPQL
SELECT person.name, mobile.number FROM person LEFT JOIN (
SELECT * FROM phone WHERE type = "MOBILE"
) AS mobile ON person.id = mobile.person_id;
Expected result:
| name | number |
|--------|---------------|
| John | +49-123-11111 |
| Mike | |
| Paul | |
| Walter | +49-123-44444 |
Java:
class Person {
String name;
List<Phone> phones;
}
class Phone {
String type;
String number;
}
JPQL (not working as expected :-( ):
SELECT person.name, phone.number FROM Person person
LEFT JOIN person.phones AS phone
WHERE phone.type = "MOBILE"
SELECT person.name, phone.number
FROM Person AS person LEFT JOIN person.phones AS phone
ON phone.type = 'MOBILE'
You can also replace the ON keyword with the hibernate specific WITH:
SELECT person.name, phone.number
FROM Person AS person LEFT JOIN person.phones AS phone
WITH phone.type = 'MOBILE'
First of all according to the Java Persistence Wikibook some JPA providers like EclipseLink and TopLink support sub selects in the FROM clause - although this is not defined in the JPA spec.
In JPA 2.1 you could use LEFT JOIN with ON:
SELECT person.name, phone.number
FROM Person person
LEFT JOIN person.phones AS phone ON phone.person = person AND phone.type = 'MOBILE'
Before JPA 2.1 you could use a case expression:
SELECT person.name,
CASE WHEN (phone.type = 'MOBILE') THEN phone.number ELSE '' END
FROM Person person
LEFT JOIN person.phones AS phone
But this will just wipe all none-mobile phone numbers - so there will be a row for each phone number of a person, even if he/she has more than one phone number that is not a mobile number.
You could use the list aggregation function of your database (like LISTAGG in Oracle), if your JPA provider renders these correctly (Hibernate does in most circumstances). This would make sense for the first two options as well - if a person can have more than one mobile number.
Try this:
SELECT person.name, phone.number
FROM Person person
LEFT JOIN person.phones AS phone
WHERE phone.type IS NULL OR phone.type = "MOBILE"
Related
Lets suppose i have 3 tables one is parent others are child
| Resource |
______________
| id |
| name |
|author |
_______________
| Movie |
_______________
|id
|lenght
|main actor
________________
| Book |
__________________
|id
|number of pages |
|Pubblisher |
___________________
now what i want to do is joining the three tables together start a rs = stmt.executeQuery(sql)
to retrieve all the data so that the sql String i pass might be
sql= "SELECT * FROM Resource
LEFT JOIN Movie
LEFT JOIN BOOK
USING(id)
then
while(rs.next())
{
//creating different objects with the data base on if its a movie or a book
..
}
how can i know from the join i made from which table the resulting row is from (movie or book) without using checks on attributes like
if(rs.getString("pubblisher").equals(NULL))
//creation of a movie
Thank you very much!
Don't use SELECT *. Instead, you should list the columns you want your query to return; and you can use a CASE expression to implement the logic that checks from which column values were returned:
select
r.id resource_id,
r.name resource_name,
r.author resource_author,
m.length movie_length,
m.main_actor movie_main_actor,
b.number_of_pages book_number_of_pages,
b.publisher book_publisher,
case
when m.id is not null then 'movie'
when b.id is not null then 'book'
else 'none'
end src
from resource r
left join movie m on m.id = r.id
left join book b on b.id = r.id
Here column src will contain either movie, book, or none, depending on which left join did succeed. If it is possible that both left joins would succceed (which does not seem likely given your set-up), then you can adapt the logic:
case
when m.id is not null and b.id is not null then 'both'
when m.id is not null then 'movie'
when b.id is not null then 'book'
else 'none'
end src
Consider I have a student table like this:
student_id name address ... school employer
1 Chris 2 John UofJ J Limited
2 Ann 3 Doe UofD D limited
Right now I need to find a list of students who have school = 'UofJ' and employer = 'J Limited'. Easy:
select * from student where school = 'UofJ' and employer = 'J Limited'
However, my reality is the last 2 attributes are stored in student table as columns but in a separate table called student_attribute as rows:
student_attribute_id student_id attribute_name attribute_value
1 1 school UofJ
1 1 company J Limited
1 2 school UofD
1 2 company D Limited
My task is to find a list of student IDs from this student_attribute table still based on school = 'UofJ' and employer = 'J Limited'. How should I do it?
Moreover, I am using Springboot JPS repository to do the query, so I am willing to listen to solution to both a sql way or JPA way.
You can use conditional aggregation to find out which student_id has both the conditions true.
select student_id
from student_attribute
group by student_id
having count(case
when attribute_name = 'school'
and attribute_value = 'UofJ'
then 1
end) > 0
and count(case
when attribute_name = 'company'
and attribute_value = 'J Limited'
then 1
end) > 0
You can then maybe join it with the student table to get the corresponding details.
select s.*
from student s
join (
select student_id
from student_attribute
group by student_id
having count(case
when attribute_name = 'school'
and attribute_value = 'UofJ'
then 1
end) > 0
and count(case
when attribute_name = 'company'
and attribute_value = 'J Limited'
then 1
end) > 0
) a on s.student_id = a.student_id;
Set up a join for each attribute you care about:
select * from student s
join student_attribute school on school.student_id = s.student_id
join student_attribute company on company.student_id = s.student_id
where company.attribute_value='J Limited'
and school.attribute_value='UofJ'
I have 4 tables
1. members(id, name, milk_no, ...)
2. collections(id, member_id, amount, date, ...)
3. credit_payment_transaction(id, member_id, amount, date, ...)
4. deductions(id, member_id, amount, date, ...)
I am trying to come up with a query that will return for each member.
milk_no | totalDeduction | totalStore | totalCollection
The result should return only member with atleast one of totalDeduction | totalStore | totalCollection
This is what i have come up with
SELECT members.milk_no, memberCollections.totalCollection, stores.totalStore, memberDeductions.totalDeduction
FROM members
LEFT JOIN
(SELECT SUM(amount) AS totalCollection, member_id
FROM collections
GROUP BY member_id) AS memberCollections
ON memberCollections.member_id = members.id
LEFT JOIN
(SELECT SUM(amount) AS totalStore, member_id
FROM credit_payment_transaction
GROUP BY member_id) AS stores
ON stores.member_id = members.id
LEFT JOIN
(SELECT SUM(amount) AS totalDeduction, member_id
FROM deductions
GROUP BY member_id) AS memberDeductions
ON memberDeductions.member_id = members.id
The above query return this
The problem with this result is, it includes unwanted data(the ones with 3 nulls). When i change to RIGHT JOIN no result is returned at all.
Simply add a WHERE clause at the bottom of your query:
WHERE totalCollection IS NOT NULL OR totalStore IS NOT NULL OR totalDeduction IS NOT NULL
You can simplify your query like this:
SELECT m.id, SUM(c.amount) AS totalCollection, SUM(cpt.amount) AS totalStore, SUM(d.amount) AS totalDeduction
FROM members m
LEFT JOIN collections c ON m.id = c.member_id
LEFT JOIN credit_payment_transaction cpt ON m.id = cpt.member_id
LEFT JOIN deductions d ON m.id = d.member_id
GROUP BY m.id
HAVING SUM(c.amount) > 0 OR SUM(cpt.amount) > 0 OR SUM(d.amount) > 0
Also this query will eliminate the member duplications
I have to print a message if ANY duplicate row with same ID (not a primary KEY) exists in table (very large table), that meets a where clause.
Table Person is (There is no Index on table)
PersonPrimaryKEY PersonName ID Email Address InvoiceID TaxID
1 Bob 1 bob#example.com 1/Harton st 1 1
2 John 2 john#example.com 2/Harton st 2 2
3 Peter 1123 peter#example.com 3/Harton st 3 3
I used hibernate
public Collection<Person> readByNameAndID (String name, String ID)
{
TypedQuery<Person> q = getEntityManager ().createNamedQuery("Select p FROM Person p WHERE Name =:Name AND ID <> :ID", Person.class);
q.setParameter ("Name", Name);
q.setParameter ("ID", ID);
return q.getResultList ();
}
Code to use is
if(results.size > 0)
{
System.out.println("Error exists");
}
Problem is, it is very inefficient when reading large table.
How can I make it very efficient ? I was thinking of using EXISTS or COUNT to do that but how to incorporate it with hibernate so that it returns only ONE row then I check size > 0, which will be efficient.
Or is setMaxResult only solution of that ?
Thanks
Aiden
Since you are considering records duplicate on the basis of having the same PersonName, the following HQL query should do the trick:
SELECT COUNT(p)
FROM Person p
GROUP BY p.PersonName
HAVING COUNT(p) > 1
If the count from this query is greater than 0, it means you have duplicates present.
I have the following type of query that I wish to convert into a jpa criteria query and the following table structure:
Table A 1-->1 Table B 1<--* Table C (proceedings) *-->1 Table D(prcoeedingsstatus)
-------- -------- ------- -------
aID bID cID dID
... .... timestamp textValue
f_bID .... f_bID
f_dID
1 A has 1 B, 1 B has many proceedings and each proceeding has a proceedingstatus.
SELECT a.*
FROM ((a LEFT JOIN b ON a.f_b = b.id)
LEFT JOIN proceedings ON b.id = proceedings.f_b)
RIGHT JOIN proceedingsstatus ON proceedings.f_d = proceedingsstatus.id
WHERE d.textValue IN ("some unique text")
AND c.timestamp BETWEEN 'somedate' AND 'anotherdate'
When I now try to do something like this for the predicates:
Predicate conditions = (root.join("tableB")
.joinList("proceedings")
.join("proceedingsstatus").get("textValue"))
.in(constraintList.getSelectedValues());
Predicate time = cb.between((root.join("tableB")
.joinList("proceedings")
.<Date>get("timestamp")), dt1.toDate(), dt2.toDate());
constraints = cb.and(conditions, time);
Right now it selects entries A where there is at least 1 occurrence of the right proceedingsstatus according to the conditions-predicate if in any of A's proceedings the 'timestamp' matches the time-predicate that I built.
So it would also select an entry A when C.timestamp is correct for a proceeding with the wrong textValue in D, if there is at least one entry C belonging to A with the right textvalue in D.
How can I change it so that it only selects A's where the proceedingsstatus has the right value AND the time of proceeds is correct?
Reuse the joins instead of creating new ones for each predicate.
Join proceedings = root.join("tableB").joinList("proceedings");