HQL: Counting a child collection conditionally within a query - java

I'm fairly inexperienced when it comes to HQL, so I was wondering if anyone can help me out on this. I have a relationship where Student has a OneToMany relationship with StudentCollege. I'm trying to write a query that finds each student who has made an application, and how many applications they've submitted. I have the following query written, but I am unsure if it's even close to what I should be doing.
select distinct new ReportVO (stu.id, stu.first_name, stu.last_name, stu.year, stu.school.school_name, sum(case when si.applied = 1 then 1 else 0 end) as numApplications) " +
"from StudentCollege as si join si.student as stu where stu.year <= 12 and stu.user.id = :userId and si.applied = 1 order by stu.last_name
When it runs the following exception is thrown:
org.hibernate.hql.ast.QuerySyntaxException: Unable to locate appropriate constructor on class [package.location.ReportVO] [select distinct new ReportVO (stu.id, stu.first_name, stu.last_name, stu.year, stu.school.school_name, sum(case when si.applied = 1 then 1 else 0 end) as numApplications) from package.location.StudentCollege as si join si.student as stu where stu.year <= 12 and stu.user.id = :userId and si.applied = 1 order by stu.last_name]
However I have a constructor for the VO object that takes in that same number of arguments, so I'm thinking the type returned from the sum is incorrect. I've also tried replacing numApplications with Integer and Count, but the same exception is thrown.
public ReportVO(int id, String firstName, String lastName, int year, String school, Integer numApplications)
Any help is appreciated!

I believe you are a missing a "group by" clause after the where clause.
select distinct new ReportVO (stu.id, stu.first_name, stu.last_name, stu.year, stu.school.school_name, sum(case when si.applied = 1 then 1 else 0 end) as numApplications) " +
"from StudentCollege as si join si.student as stu where stu.year <= 12 and stu.user.id = :userId and si.applied = 1 group by stu.id, stu.first_name, stu.last_name, stu.year, stu.school.school_name order by stu.last_name

Related

Check if any value in list satisfies a condition

Suppose I have the following table called Seasons:
...
start_month
end_month
...
2
6
...
3
4
...
...
...
I need to write a query which, for a given list of months, returns all the Seasons that satisfy the condition where at least 1 month in the list is: start_month <= month <= end_month.
I've written this query as a native query with JDBC, except the where clause.
#Repository
public class SeasonsRepositoryImpl implements SeasonsRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public List<SeasonsProjection> findByMonths(List<Integer> months) {
String query = "select * " +
"from seasons as s" +
"where ...."
try {
return em.createNativeQuery(query)
.setParameter("months", months)
.unwrap(org.hibernate.query.NativeQuery.class)
.setResultTransformer(Transformers.aliasToBean(SeasonsProjection.class))
.getResultList();
} catch (Exception e) {
log.error("Exception with an exception message: {}", e.getMessage());
throw e;
}
}
}
I have no idea how to write this, I thought of using the ANY operator until I found out that ANY only works with tables and not lists, I thought of writing this as a subquery with converting the list to a table, but I don't know if that's possible, I couldn't find anything in the MySQL documentation.
In your where clause, you will want to have an OR conditions for each month.
You can generate it in a loop and then concat or something similar.
Your final query for input of 1,3,4,7 for example will look as follows:
SELECT *
FROM seasons as s
WHERE (1 between start_month and end_month
OR 3 between start_month and end_month
OR 4 between start_month and end_month
OR 7 between start_month and end_month)
One way to accomplish this is:
select s.*
from (select 1 as mn union select 2 union select 3 union select 4 union select 5 union select 6 union select 7 union select 8
union select 9 union select 10 union select 11 union select 12) as a
inner join season s on a.mn between s.start_month and s.end_month
where a.mn in (:flexibleTypeMonths);

SQL: Get a limit of records per entity

I have the following setup (Java/Hibernate/PostgreSQL):
TeamName {
id: Long;
name: String;
team: Team;
....
}
Series {
id: Long;
season: Season;
dateScheduled: Date;
}
SeriesTeam {
id: Long;
series: Series;
team: TeamName;
}
SeriesTeam {
id: Long;
team: TeamName;
}
What I want to do is do a select of the past n series (say 10) or the next series from the current date. Here's what I have so far:
select s.* from series s
inner join series_teams st on st.series_id = s.id
inner join team_names tn on tn.id = st.team_name_id
where tn.id in (:teamIds) and s.date_scheduled < CURRENT_DATE
order by s.date_scheduled desc
But that is going to get me all the prior series for all teams and I will have to use Java to pick out what I want How would I go about doing what I want? Thanks!
EDIT: For example, say I wanted a limit of 10 per team name, and there are 24 teams, I would want max of 240 records returned to me. (assuming 10 exist before current date)
EDIT2: Here is the code that I want for a single team:
select s. from series s
inner join series_teams st on st.series_id = s.id
where st.team_name_id=85 and s.date_scheduled < CURRENT_DATE
order by s.date_scheduled desc
limit 10
I just need to be able to apply this for all the teams....I don't want to make x SQL calls for every team.
I think this will work. The syntax is in mysql and you can try it at that site the structure they have is similar to yours. you can adjust the limit value to change how many from each employee to return sorted by date. Probably add the current date check there too i guess.
Basically I joined all the needed tables together then created a new column that will tell me if that row is one we should return then added that check in the where clause.
https://www.w3schools.com/sql/trysql.asp?filename=trysql_op_in
SELECT e.employeeid, lastname,orderdate, orderdate in (select orderdate from
orders ord where e.employeeid=ord.employeeid order by orderdate limit 2) as
good FROM Employees as e join orders as o on e.employeeid=o.employeeid where
good=1 order by e.employeeid, o.orderdate;
for your case:
select s.id, s.season_id, s.date_scheduled, st.team_name_id,
s.date_scheduled in (
select s2.date_scheduled from series s2
inner join series_teams st2 on st2.series_id = s2.id
inner join team_names tn2 on tn2.id = st2.team_name_id
where tn.id =tn2.id and s2.date_scheduled < CURRENT_DATE
order by s.date_scheduled desc limit 5
) as foo
from series s
inner join series_teams st on st.series_id = s.id
inner join team_names tn on tn.id = st.team_name_id
where tn.id in (:teamIds) and foo = true
order by st.team_name_id, s.date_scheduled desc

Dynamic attribute name search

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'

Using EXISTS in hibernate to return one row only for performance(without setMaxResult)

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.

Count Duplicate Results in Oracle Database

I am working on a database project using both Java and SQL with an Oracle Database. I am new at working with databases and new at SQL. My question is: how would I be able to get the customer count and record each of their purchase history on a pivot table? For example, on the following table, I have Lee a total of 3 times, and purchased Item A, Item B, and Item C. Ann is there 3 times as well and purchased Item D, Item E, and Item F. I want to put their name, the number of occurrences and what they purchased on separate pivot table.
Row Customer Purchase_History
1 Lee Item A
2 Lee Item B
3 Lee Item C
4 Ann Item D
5 Ann Item E
6 Ann Item F
I've written some code attempting to do this, but when I compile and run, it will not give me the desired results. Here is my code:
String TableCount = "SELECT J.Row, J.Customer, J.Purchase_History, C.cnt" +
" FROM Table J INNER JOIN(SELECT Customer, count(Customer) as cnt" +
"FROM Table GROUP BY Customer") C ON J.Customer = C.Customer;
ResultSet rs = st.executeQuery(TableCount);
while(rs.next()){
st.executeUpdate("CREATE TABLE IF NOT EXISTS CUSTOMER_COUNT" +
"(TableCount , Purchase_History )");
String InsertIntoTable = String.format("INSERT INTO CUSTOMER_COUNT" +
"("TableCount","Purchase_History")" +
" VALUES ('%s','%s)");
}
What am I doing wrong here? Any help would be greatly appreciated!
Your query logic is fine. You should print out the query before running it. The problem would be these two lines (at least):
" FROM Table J INNER JOIN(SELECT Customer, count(Customer) as cnt" +
"FROM Table GROUP BY Customer") C ON J.Customer = C.Customer;
I assume you really mean:
" FROM Table J INNER JOIN(SELECT Customer, count(Customer) as cnt" +
"FROM Table GROUP BY Customer) C ON J.Customer = C.Customer";
In any case, when you print this out, you'll have the expression cntFROM table. You need a space:
" FROM Table J INNER JOIN(SELECT Customer, count(Customer) as cnt" +
" FROM Table GROUP BY Customer) C ON J.Customer = C.Customer";
But, an easier way to write your query is to use analytic functions:
select J.Row, J.Customer, J.Purchase_History,
count(*) over (partition by customer) as cnt
from table j

Categories