I need a little help setting up my query. I don't want to have to make multiple selects to form basically the same sub-query if I can avoid it. In a nut shell, I have Objects called TimeSlot that are used to track several details. Those TimeSlot's are items that are paid on. When its time for the TimeSlot to submit for a reimbursement they are used to create a PayableTimeSlot. Before the TimeSlot can be paid I need to make sure it has not been paid already.
As it sits the following is my query:
#NamedQuery(
name = "TimeSlot.by.person.academy.id.by.contract.date",
query = "select distinct ts
from TimeSlot ts
join ts.invitedInstructors ii
join ts.academyClass ac
join ac.academy a
where ii.person.id = ?
and a.id = ?
and ts.schedule.startDateTime BETWEEN ? AND ?
and ts.id not in (select e.id from PayableTimeslot pts join pts.event e)
and ? not in (select e.claimant from PayableTimeslot pts join pts.event e)")
As you can see I am already selecting an element from the PayableTimeSot for the first not in. Is there a way to expand the sub-query into:
(select e.id, e.claimant from PayableTimeslot pts join pts.event e) I am just not sure how to check for multiple items not in the sub-query. By all means if there is a better attack of the problem than the way I am doing it let me know.
Unless you all think the multiple selects wont be a big deal... There will be on average 30-50 entry's a week into the table with each entry being copied (for an audit trail) upwards of 7-9 times.
Okay so after some thinking this is what I came up with. I was indeed trying to answer the problem in the wrong way... I was doing two sub querys when all I needed was a where on the first thus combining the two.
#NamedQuery(
name = "TimeSlot.by.person.academy.id.by.contract.date",
query = "select distinct ts "
+ "from TimeSlot ts "
+ "join ts.invitedInstructors ii "
+ "join ts.academyClass ac "
+ "join ac.academy a "
+ "where ii.person.id = ? "
+ "and a.id = ? "
+ "and ts.schedule.startDateTime BETWEEN ? AND ? "
+ "and ts.id not in (select e.id from PayableTimeslot pts join pts.event e where pts.claimant = ?)")
Related
I am trying to use this query in springboot so I can display the results in a webpage. I know that this query works because I tested it in postgresql and it gave me the right results.
But JPA is telling me that the '(' after the first FROM is an unexpected token and the query was therefore viewed as invalid.
This is my query:
#Query(
"SELECT com.example.imse22.model.TrvlA_Cust_Dto(books_query.name, count(travelA_query.customer_id)) " +
"FROM (SELECT DISTINCT customer_servant.employee_id, books.customer_id FROM customer_servant " +
"INNER JOIN books ON customer_servant.employee_id = books.customer_servant_id) AS travelA_query " +
"INNER JOIN " +
"(SELECT travel_agency.id, travel_agency.name, employee.employee_id FROM travel_agency " +
"INNER JOIN employee ON travel_agency.id = employee.travel_agency_id) AS books_query " +
"ON travelA_query.employee_id = books_query.employee_id " +
"GROUOP BY travelA_query.name")
can somebody help me out how I could rewrite the query so that JPA approves it?
Your query is native so you should declare it in that way:
#Query(
value = "SELECT com.example.imse22.model.TrvlA_Cust_Dto(books_query.name, count(travelA_query.customer_id)) " +
"FROM (SELECT DISTINCT customer_servant.employee_id, books.customer_id FROM customer_servant " +
"INNER JOIN books ON customer_servant.employee_id = books.customer_servant_id) AS travelA_query " +
"INNER JOIN " +
"(SELECT travel_agency.id, travel_agency.name, employee.employee_id FROM travel_agency " +
"INNER JOIN employee ON travel_agency.id = employee.travel_agency_id) AS books_query " +
"ON travelA_query.employee_id = books_query.employee_id " +
"GROUOP BY travelA_query.name", nativeQuery = true)
link point 2.2: https://www.baeldung.com/spring-data-jpa-query
Ok so this is how I solved it:
I changed my query into a native one just like #notAPPP pointed out and then I only had to add an alias for the LEFT JOIN (also I changed INNER JOIN to LEFT JOIN).
here the code example:
#Query(value = "SELECT combined.name as name, count(combined.customer_id) as id " +
"FROM (" +
"(SELECT travel_agency.name, travel_agency.id, employee.employee_id " +
"FROM travel_agency INNER JOIN employee ON travel_agency.id = employee.travel_agency_id) as trvlEmp " +
"LEFT JOIN " +
"(SELECT books.customer_id, books.customer_servant_id, customer_servant.employee_id " +
"FROM books INNER JOIN customer_servant ON books.customer_servant_id = customer_servant.employee_id) as custBooks " +
"ON trvlEmp.employee_id = custBooks.employee_id) as combined " + // this "AS combined" got added
"GROUP BY combined.name", nativeQuery = true)
This makes sense, because after a FROM clause one should wirte the name of a table or a result table (e.g. from two joined queries like in my case). As I didnt specify an alias for the LEFT JOIN of my two subqueries, JPA obviously didnt know how to handle the result of those subqueries. Therefore always name your subqueries if they are not used in a WHERE clause, but rather with a FROM clause, like in my case. E.g. the name I gave my LEFT JOIN is "combined" as seen in the code example above.
Also I changed my INNER JOIN to a LEFT JOIN to get the value 0 of the elements that have 0 counts of what I wanted to count in the table.
If you want to know how to handle the result which such a query returns follow this link.thorben-janssen.com/spring-data-jpa-dto-native-queries
Currently I have such piece of code, which doesn't work, since I have to add schema name before each table in a query(like DEV.DASHBOARDS_METADATA):
public interface DashboardMetadataDao extends CrudRepository<DashboardMetadata, Integer> {
#Query("SELECT D FROM DASHBOARDS_METADATA D " +
"INNER JOIN FAC_DASHBOARDS_LINK DL ON D.ID = DL.DASHBOARD_ID " +
"INNER JOIN FIRMS F ON DL.FAC_ID = F.FAC_UNIT_ID " +
"INNER JOIN USERS U ON U.FIRM_ID = F.FIRM_ID WHERE LOWER(U.USERID) = LOWER(:userid)")
public Set<DashboardMetadata> findByUserId(#Param("userid") String userId);
}
The problem is that schema name differs from database to database (DEV/QA/PROD). Normally I use component's method which prepend schema's name to each table during query generation. How can do this using annotations?
Thanks!
Hibernate has a variable that can be used in native queries to get schema name called: {h-schema}
https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/chapters/query/native/Native.html#sql-global-catalog-schema You can just put {h-schema}table in your query. I think you have to convert it to a native query.
Below Code Snippet worked for me
#Query(
value = "select *\n" +
"from {h-schema}TABLE-A r left join {h-schema}TABLE-B p on r.ID=p.ID\n" +
"left join {h-schema}TABLE-C pe on p.ID=pe.ID\n" +
"left join {h-schema}TABLE-D e on pe.ENT_ID=e.ENT_ID\n" +
"where r.role_type='Provisioning'",
nativeQuery = true)
This is the jpql code (it's part of a spring rest data endpoint so there is not much overhead).
This is the minimal example I could get the bug with. The original code was much more complicated and the :#{false} originally contained a method call.
#RestResource(path = "forUser")
#Query("select distinct o from Organ o join o.meetings om where " +
"om.id in (select m.id from Meeting m join m.organ mo join mo.organCredentials oc join oc.organCredentialPersons ocp join ocp.person p where p.username = :#{#securityService.getCurrentUserUsername()}) " +
"or :#{false} = true")
Set<Organ> findForCurrentUser();
=> takes 13 seconds
This endpoint is really slow. It takes 13 seconds to get an answer.
Now take the exact same code, but replace :#{false} by "false" or :#{true}. and it now takes less than 1 second.
#RestResource(path = "forUser")
#Query("select distinct o from Organ o join o.meetings om where " +
"om.id in (select m.id from Meeting m join m.organ mo join mo.organCredentials oc join oc.organCredentialPersons ocp join ocp.person p where p.username = :#{#securityService.getCurrentUserUsername()}) " +
"or false = true")
Set<Organ> findForCurrentUser();
=> takes ~1s
#RestResource(path = "forUser")
#Query("select distinct o from Organ o join o.meetings om where " +
"om.id in (select m.id from Meeting m join m.organ mo join mo.organCredentials oc join oc.organCredentialPersons ocp join ocp.person p where p.username = :#{#securityService.getCurrentUserUsername()}) " +
"or :#{true} = true")
Set<Organ> findForCurrentUser();
=> takes ~1s
Honestly I'm stumped. our team gave up on making this work and took an alternative approach due to lack of time, but I would really like to know what is going on here.
I have the following code
#Query(nativeQuery = true, value = "select e.* from event e"
+ " join league l on (l.id = e.league_id)"
+ " join sport s on (l.sport_id = s.id)"
+ " join team t1 on (t1.id = e.team_one_id)"
+ " join team t2 on (t2.id = e.team_two_id)"
+ " join country c on (c.id = l.country_id)"
+ " where l.name LIKE %?1%")
Page<Event> getAllEventsFiltered(final PageRequest of, final String filter);
If execute this SQL into database I receive a lot of results but when is executed from java I receive no one result in the same SQL.
I already tried:
where l.name LIKE %?#{escape([1])} escape ?#{escapeCharacter()}
but just works to begins and I needs for both begin and end.
JDBC parameters can only be included in very specific places in a SQL statement. They are not replaced as strings in the SQL statement but they are applied. The specific valid places change from database to database, and also depend on the JDBC driver variant/version.
The general rule is that a parameter can be applied where a single value would be present. String parameters should not be enclosed in single quotes as a VARCHAR would be.
One option to make it compliant, is to place the parameter isolated from other text, for example by using the CONCAT() function. Your query could be rephrased as:
#Query(nativeQuery = true, value = "select e.* from event e"
+ " join league l on (l.id = e.league_id)"
+ " join sport s on (l.sport_id = s.id)"
+ " join team t1 on (t1.id = e.team_one_id)"
+ " join team t2 on (t2.id = e.team_two_id)"
+ " join country c on (c.id = l.country_id)"
+ " where l.name LIKE concat('%', ?1, '%')") // change here
Nevertheless, the specific syntax change can be different depending on the database you are using (that you haven't mentioned).
Following SQL query:
select distinct hotel.country 'Country', hotel.id 'Hotel ID', hotel.Name'Name', room.id 'Room ID'
from room, stay, reservation, hotel
where
(stay.roomid = room.id)
and (stay.reservationid = reservation.id)
and (reservation.status != 'Booked' AND reservation.status != 'CheckedIn')
and (reservation.arrivaldate >= '2012-08-08')
and (reservation.leavedate <= '2012-08-15')
and (room.hotelid = hotel.id)
order by hotel.country, hotel.id, hotel.name, room.id asc
This will give me a list of available rooms per hotel per country. Now I have to put this in HQL, but I can't do so because of this:
Query query = session.createQuery("from room, stay, reservation, hotel where stay.roomid = room.id");
This doesn't work becuase stay.roomid is a Room object whereas room.id is just an integer. I'm pretty new to Hibernate/HQL and the reference manual didn't really bring me anywhere... How can I "convert" (scary word, I know this doesn't involve actual converting) this SLQ query to a HQL statement? Thanks.
UPDATE
Using a join result in the same problem
Query query = session.createQuery("" +
"from Stay stay " +
"join stay.ReservationID reservation " +
"join stay.RoomID room " +
"join room.HotelID hotel " +
"where (stay.RoomID = room.ID)");
I'm really missing something (probably very logical) here...
You haven't shown your entities, but, just as you would use joins in SQL, you need to use joins in HQL:
select ...
from Stay stay
join stay.reservation reservation
join stay.room room
join rom.hotel hotel
where (reservation.status != 'Booked' AND reservation.status != 'CheckedIn')
and (reservation.arrivaldate >= '2012-08-08')
and (reservation.leavedate <= '2012-08-15')
The Hibernate documentation has a whole chapter on HQL, and a section of this chapter about associations in HQL.