Java Hibernate constant in sql-query - java

Below is the original content of a hibernate sql query I have:
<sql-query name="countryOfOrigin-limit-country-city-location">
<return alias="rb" class="RecentBooking"/>
SELECT
bb.reserv_num as {rb.reservNum},
bb.origin as {rb.countryOfOrigin},
bb.pick_up_loc as {rb.locationId},
bb.first_date as {rb.bookingDate},
bb.pick_up_time as {rb.pickUpDate},
bb.drop_off_time as {rb.dropOffDate},
bb.car_price as {rb.carPrice},
bb.discount as {rb.discount},
bb.exchange_rate as {rb.exchangeRate},
SUBSTRING(a.internal_class,1,1)as {rb.carClass},
a.car_type as {rb.carType},
bb.vehicle_type as {rb.vehicleType},
s.name as {rb.supplier},
vv.country as {rb.country},
vv.city as {rb.city},
vv.location as {rb.location},
bb.cur as {rb.currency},
a.pics as {rb.carImage}
FROM (SELECT * FROM b WHERE first_date > DATE(NOW()-1)AS bb
JOIN a a ON a.id = bb.car_id
JOIN d d ON d.id = bb.pick_up_loc
JOIN supplier s ON s.id = d.supplier_id
JOIN v_location_trans vv ON vv.location_id = d.location_id
AND vv.lang=33
AND vv.country = :country
AND vv.city = :city
AND vv.location = :location
AND bb.origin = :countryOfOrigin
ORDER BY bb.id DESC
LIMIT :limit
</sql-query>
The sql runs fast enough, but as you might have noticed there is a big, the NOW()-1 should be NOW() - INTERVAL 1 DAY.
I modified the nested select statement as follows:
FROM (SELECT * FROM b WHERE first_date > DATE(NOW() - INTERVAL 1 DAY)) AS bb
The fix works fine in terms of results, however the query slows down massively (pretty much almost instant up to a few seconds).
I believe this is because the DATE(NOW() - INTERVAL 1 DAY) is being evaluated a large number of times (we have a very large data set). How would I go about seperating this calculation so that it only occurs once, without having to pass it in from java code like the country, city, limit, etc?
I have very little experience with hibernate or SQL, and I have tried seperating the calculation out into a variable before the select statement, which isnt allowed. I have also tried making small changes to the SQL query but I keep getting runtime errors, presumably due to invalid SQL.
How would I go about doing this?
Thanks.

You are creating a new temp table bb in your query on the whole record.
I would suggest that you should filter out the result in creating the temp table.
for example if you put the AND clause while creating temp table
AND bb.origin = :countryOfOrigin
Like:
FROM (SELECT * FROM b WHERE first_date > (NOW() - INTERVAL 1 DAY) AND origin = :countryOfOrigin)AS bb
Also you can put the limit it it as well.
FROM (SELECT * FROM b WHERE first_date > (NOW() - INTERVAL 1 DAY) AND origin = :countryOfOrigin LIMIT :limit)AS bb
EDITED -- New Answer.
I thing you do not need to change the INTERVAL 1 DAY, you stick to the previous query but replace the 1 with 86400000 which is equal to INTERVAL 1 DAY (24*60*60*1000)
new query would look like
FROM (SELECT * FROM b WHERE first_date > (NOW() - 86400000)
AND origin = :countryOfOrigin LIMIT :limit) AS bb

Related

How to combine three queries into one. Is it possible to combine them and write one cursor?

I haven't had many opportunities to write SQL queries other than very basic ones. I now have a requirement that uses three different queries. I am wondering if the three queries can be combined into one query (or a cursor) and make it run better/quicker and more efficient. Since I am new to writing SQL Queries I may not be using recommended standards. Please feel free to comment/correct.
Below are the queries I am working with:
--Main Query
Select
DTL.DetailId
, HDR.PersonId
, ODT.OrderNumber
from
CMPN.Header HDR
INNER JOIN
CMPN.Detail DTL
ON
HDR.HeaderId = DTL.HeaderId
INNER JOIN
CMPN.DetailStatus DST
ON
DTL.DetailId = DST.DetailId
INNER JOIN
CMPN.OperationalDetail ODT
ON
DTL.DetailId = ODT.DetailId
WHERE
DST.DetailStatusCode = 'Approved'
AND DST.ExpirationTimestamp IS NULL
and HDR.PayGroup = 'Group A';
--Query to get createtime
SELECT
max(CDS.effectiveTimestamp) as createtime
FROM
DetailStatus CDS
, Detail DTL
WHERE
DTL.DetailId = CDS.DetailId
AND CDS.DetailStatusCode = 'Extracted'
;
--If the createtime is not null
SELECT
sum (CAD.UnitValue)
, sum (CAD.RateAmount)
, sum (CAD.adjustmentAmount)
FROM
AdjustmentDetail CAD
, Detail DTL
WHERE
CAD.DetailId = :detailId
AND CAD.createTimestamp >= :createtime
;
--If the createtime stamp is null
SELECT
sum (CAD.UnitValue)
, sum (CAD.RateAmount)
, sum (CAD.adjustmentAmount)
FROM
AdjustmentDetail CAD
, Detail DTL
WHERE
DTL.DetailId = CAD.DetailId
;
Once the results are combined it should fetch the below:
DTL.DetailId
, HDR.PersonId
, ODT.OrderNumber
, sum (CAD.UnitValue)
, sum (CAD.RateAmount)
, sum (CAD.adjustmentAmount)
Below is the relationships among the tables

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

fetch the count of records from table by setting the Maximum results in JPA

I use JPA to fetch the count of records from table Customer by setting the Maximum results(10).
The expectation is,
1. to receive only 10 as count If the count returned by query is greater than 10.
2. to receive the exact number of transactions if the count is equal to or less than 10
I try to achieve in the below way.
Query query = em.createQuery("SELECT COUNT(c) FROM Customer c where c.countryId ='Canada' and c.lanuguage ='ENG'");
query.setMaxResults(10);
long customerCount = (Long)query.getSingleResult();
But I am not getting the result as expected. When the query results is 100 ( I mean more than 10), then I could see 100 is being returned by the query.
Can anyone please advise me If something is incorrect in my approach?
OR
Is it not possible to achieve this in single query?
OR
Any other best way to achieve this?
Thanks in adavance
You are getting COUNT so why you are using max result ?
Your result will be always single result as it will be a number of Customers COUNT?
Your code should be next if you want to get the total count
Query query = em.createQuery("SELECT COUNT(c) FROM Customer c WHERE c.countryId ='Canada' AND c.lanuguage ='ENG'");
long customerCount = (Long)query.getSingleResult();
UPDATE
AND If you just want to get 10 as a maximum total count if COUNT result than
10 else you will got the real COUNT result, so please try next
Query query = em.createQuery("SELECT CASE WHEN COUNT(c) > 10 THEN 10 ELSE COUNT(c) END FROM Customer c WHERE c.countryId ='Canada' AND c.lanuguage ='ENG'");
int customerCount = (Integer)query.getSingleResult();
Or if you need it in logn type you can add l to number, for example 10l like in next query (NOTE: in next query 10l is not 101 but 10 in long type)
Query query = em.createQuery("SELECT CASE WHEN COUNT(c) > 10l THEN 10l ELSE COUNT(c) END FROM Customer c WHERE c.countryId ='Canada' AND c.lanuguage ='ENG'");
long customerCount = (Long)query.getSingleResult();
Anyway you can use maximum result if you want to get list of customer ids for example (assuming customer has id property with type Integer)
Query query = em.createQuery("SELECT c.id FROM Customer c WHERE c.countryId ='Canada' AND c.lanuguage ='ENG'");
query.setMaxResults(10);
List<Integer> customersIds = query.getResultList();

How to query for number of records in select with "group by" clause in JPA/EclipseLink?

Suppose you have a following JPA query:
select car.year, car.month, count(car) from Car car group by car.year, car.month
Before we query for results, we need to know how many records this query will return (for pagination, UI and so on). In other words we need something like this:
select count(*) from
(select car.year, car.month, count(car)
from Car car group by car.year)
But JPA/EclipseLink does not support subqueries in "from" clause. It there a way around it?
(Of course you can use plain SQL and native queries, but this is not an option for us)
A portable JPA solution:
select count(*) from Car c where c.id in
(select MIN(car.id) from Car car group by car.year, car.month)
You could also go with something like:
select COUNT(DISTINCT CONCAT(car.year, "#", car.month)) from car
but I expect this to be less performant due to operations with textual values.
What about:
select count(distinct car.year) from car
I have another approach to solve this issue . by using my approach you don't need to know the no of rows this query is going to return.
here it is your solution :-
you going to need two variables
1) pageNo (your page no should be 1 for first request to data base and proceeding request it should be incremental like 2 ,3 , 4 5 ).
2) pageSize.
int start = 0;
if(pageNo!=null && pageSize!=null){
start = (pageNo-1) * pageSize;
}else{
pageSize = StaticVariable.MAX_PAGE_SIZE; // default page size if page no and page size are missing
}
your query
em.createquery("your query ")
.setfirstResult(start)
.setMaxResult(pageSize)
.getResultList();
As #chris pointed out EclipseLink supports subqueries. But the subquery can't be the first one in the from-clause.
So I came up with the following workaround which is working:
select count(1) from Dual dual,
(select car.year, car.month, count(car)
from Car car group by car.year) data
count(1) is important as count(data) would not work
You have to add an entity Dual (If your database does not have a DUAL table, create one with just one record.)
This works but I still consider it a workaround that would only work if you allowed to create the DUAL table.
Simply you can use setFirstResult and setMaxResult to set record bound for query ,also use size of list to return count of records that query runs. like this :
Query query = em.createQuery("SELECT d.name, COUNT(t) FROM Department d JOIN
d.teachers t GROUP BY d.name");
//query.setFirstResult(5);
//query.setMaxResult(15); this will return 10 (from 5 to 15) record after query executed.
List<Object[]> results = query.getResultList();
for (int i = 0; i < results.size(); i++) {
Object[] arr = results.get(i);
for (int j = 0; j < arr.length; j++) {
System.out.print(arr[j] + " ");
}
System.out.println();
}
-----Updated Section------
JPA does not support sub-selects in the FROM clause but EclipseLink 2.4 current milestones builds does have this support.
See, http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/JPQL#Sub-selects_in_FROM_clause
You can probably rewrite the query with just normal joins though.
Maybe,
Select a, size(a.bs) from A a
or
Select a, count(b) from A a join a.bs b group by a
I hope this helps you.

JPA 2 criteria API: How to select values from various joined tables without using Metamodel?

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");

Categories