I'm studying JPA criteria api and my database contains Employee table.
I am trying to find all the employees who are paid second highest salary. I was able to write JPQL successfully as follows.
SELECT e FROM Employee e WHERE e.salary = (SELECT MAX(emp.salary) FROM Employee emp WHERE emp.salary < (SELECT MAX(employee.salary) FROM Employee employee) )
but now I am trying to convert it to criteria api and have tried following.
CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> e1 = c.from(Employee.class);
c.select(e1);
Subquery<Number> sq = c.subquery(Number.class);
Root<Employee> e2 = sq.from(Employee.class);
sq.select(cb.max(e2.<Number> get("salary")));
Subquery<Number> sq1 = sq.subquery(Number.class);
Root<Employee> e3 = sq1.from(Employee.class);
sq1.select(cb.max(e3.<Number> get("salary")));
c.where(cb.lessThan(e2.<Number>get("salary"), e3.<Number>get("salary")));// error here
c.where(cb.equal(e1.get("salary"), sq));
I get the error that parameters are not compatible with lessThan method. I do not understand how can I get this query worked out. Is my approach right?
EDIT :- Updating the question after Mikko's answer.
The jpql provided above provides following results, which are the employees with second highest salary.
Harish Taware salary 4000000.0
Nilesh Deshmukh salary 4000000.0
Deodatta Chousalkar salary 4000000.0
Deodatta Chousalkar salary 4000000.0
but the updated criteria query as below,
CriteriaQuery<Employee> c = cb.createQuery(Employee.class);
Root<Employee> e1 = c.from(Employee.class);
c.select(e1);
Subquery<Long> sq = c.subquery(Long.class);
Root<Employee> e2 = sq.from(Employee.class);
sq.select(cb.max(e2.<Long> get("salary")));
Subquery<Long> sq1 = sq.subquery(Long.class);
Root<Employee> e3 = sq1.from(Employee.class);
sq1.select(cb.max(e3.<Long> get("salary")));
c.where(cb.lessThan(e2.<Long> get("salary"), e3.<Long> get("salary")));
c.where(cb.equal(e1.get("salary"), sq));
employees = em.createQuery(c).getResultList();
for (Employee employee : employees) {
System.out.println(employee.getName() + "salary"
+ employee.getSalary());
}
This provides the employee with highest salary. The result is as below.
Pranil Gildasalary5555555.0
Please tell me where I am being wrong. An explanation is deeply appreciated.
After some more trial and error, I could write the query to select employees with second maximum salary. I would like to suggest that you should write a JPQL query first and write the criteria api accordingly. This is what I analyzed from JPQL.
SELECT e FROM Employee e
WHERE e.salary = (SELECT MAX(emp.salary) FROM Employee emp
WHERE emp.salary < (SELECT MAX(employee.salary) FROM Employee employee) )
Now we can see that
There are 2 subqueries, i.e. subquery of main query contains another subquery
The identification variables e, emp and employee correspond to the main query, subquery of main query and subquery of subquery.
Now while comparing the result of subqueries i.e. maximum salary compared with the employee salary of outer query, the identification variable from outer query is used. for e.g. WHERE emp.salary = (SELECT MAX(emp.salary) FROM Employee emp)
Now let us convert this query in criteria api.
First write CriteriaQuery that corresponds to outermost query i.e. SELECT e FROM Employee e WHERE e.salary =
CriteriaQuery<Employee> c1 = cb.createQuery(Employee.class);
Root<Employee> e3 = c1.from(Employee.class);
c1.select(e3);
Let us leave the WHERE e.salary = for now and go for the subquery
Now this should have a subquery that selects the maximum salary of employees i.e. SELECT MAX(emp.salary) FROM Employee emp
WHERE emp.salary < again let us leave the WHERE emp.salary < for now.
Subquery<Long> sq1 = c1.subquery(Long.class);
Root<Employee> e4 = sq1.from(Employee.class);
sq1.select(cb.max(e4.<Long> get("salary")));
repeating this for subquery of above subquery,
Subquery<Long> sq2 = sq1.subquery(Long.class);
Root<Employee> e5 = sq2.from(Employee.class);
sq2.select(cb.max(e5.<Long> get("salary")));
Now we have written subqueries but WHERE conditions need to be applied yet. So now the where condition in criteria api corresponding to WHERE emp.salary < (SELECT MAX(employee.salary) FROM Employee employee) will be as below.
sq1.where(cb.lessThan(e4.<Long> get("salary"), sq2));
Similarly, WHERE condition corresponding to WHERE e.salary = (SELECT MAX(emp.salary) FROM Employee emp will be as below.
c1.where(cb.equal(e3.<Long> get("salary"), sq1));
So the complete query which gives the employees with second highest salary can be written in criteria api as below.
CriteriaQuery<Employee> c1 = cb.createQuery(Employee.class);
Root<Employee> e3 = c1.from(Employee.class);
c1.select(e3);
Subquery<Long> sq1 = c1.subquery(Long.class);
Root<Employee> e4 = sq1.from(Employee.class);
sq1.select(cb.max(e4.<Long> get("salary")));
Subquery<Long> sq2 = sq1.subquery(Long.class);
Root<Employee> e5 = sq2.from(Employee.class);
sq2.select(cb.max(e5.<Long> get("salary")));
sq1.where(cb.lessThan(e4.<Long> get("salary"), sq2));
c1.where(cb.equal(e3.<Long> get("salary"), sq1));
employees = em.createQuery(c1).getResultList();
for (Employee employee : employees) {
System.out.println(employee.getName() + " " + employee.getSalary());
}
As documented, it cannot work because Number is not Comparable:
<Y extends java.lang.Comparable<? super Y>> Predicate lessThan(Expression<? extends Y> x,
Expression<? extends Y> y)
For expression with Number there is method Criteriabuilder.lt that takes such arguments:
c.where(cb.lt(e2.<Number>get("salary"), e3.<Number>get("salary")));
Other option is to change type argument from Number to something more specific. If salary is Long, following should work:
Subquery<Long> sq = c.subquery(Long.class);
Root<Employee> e2 = sq.from(Employee.class);
sq.select(cb.max(e2.<Long> get("salary")));
Subquery<Long> sq1 = sq.subquery(Long.class);
Root<Employee> e3 = sq1.from(Employee.class);
sq1.select(cb.max(e3.<Long> get("salary")));
c.where(cb.lessThan(e2.<Long>get("salary"), e3.<Long>get("salary")));
c.where(cb.equal(e1.get("salary"), sq));
Related
I have two tables called source and base. The source table has a bunch of ids and all combination of weekly dates. The base table as ids, their tagged devices and the device start and end dates.
Example source table :
id
com_date
acc_1
11/25/2022
acc_1
11/18/2022
acc_1
11/11/2022
acc_2
11/25/2022
acc_3
11/25/2022
acc_3
11/25/2022
Example of base table :
id
device_id
start_date
end_date
acc_1
d1
11/24/2022
12/31/2999
acc_1
d2
11/19/2022
12/31/2999
acc_1
d3
11/12/2022
11/28/2022
acc_2
d4
11/20/2022
11/26/2022
acc_3
d5
11/17/2022
11/24/2022
acc_3
d6
11/10/2022
12/31/2999
I would like my final table to look something like this with nested columns -
Column count should be the count of distinct devices applicable for that com_date
and each com_date should lie between start_date and end_date
You might consider below query.
(I've tested it after changing last com_date in source_table to 11/18/2022.)
SELECT s.id, s.com_date AS dates,
COUNT(DISTINCT device_id) count,
ARRAY_AGG(STRUCT(b.device_id, b.start_date AS to_date, b.end_date AS from_date)) d
FROM source_table s JOIN base_table b
ON s.id = b.id
AND PARSE_DATE('%m/%d/%Y', com_date) BETWEEN PARSE_DATE('%m/%d/%Y', start_date) AND PARSE_DATE('%m/%d/%Y', end_date)
GROUP BY 1, 2;
https://github.com/apache/calcite/blob/master/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
Here in RelBuilder class documentation for pivot() method provided example is below.
for SQL ::
SELECT * FROM (SELECT mgr, deptno, job, sal FROM emp) PIVOT (SUM(sal) AS ss, COUNT(*) AS c FOR (job, deptno) IN (('CLERK', 10) AS c10, ('MANAGER', 20) AS m20))
RelBuilder.pivot(groupKey, aggCalls, axes, valueMap.build().entrySet())
can be used to create a RelNode object. Here we pass a Map(alias, pivot_column_values) object as "values" parameter.
final Function<RelBuilder, RelNode> f = b -> b.scan("emp")
.pivot(b.groupKey("MGR"), Arrays.asList(b.sum(b.field("SAL")).as("SS"), b.count().as("C")),
b.fields(Arrays.asList("JOB", "DEPTNO")),
ImmutableMap.<String, List<RexNode>>builder()
.put("C10", Arrays.asList(b.literal("CLERK"), b.literal(10)))
.put("M20", Arrays.asList(b.literal("MANAGER"), b.literal(20))).build().entrySet())
.build();
How to pass "values" parameter when we don't have alias for pivot column values.
e.g. how to pass "values" parameter of RelBuilder.pivot() method for below query
SELECT * FROM (SELECT mgr, deptno, job, sal FROM emp) PIVOT (SUM(sal) AS ss, COUNT(*) AS c FOR (job, deptno) IN (('CLERK', 10), ('MANAGER', 20)))
Also is it compulsory to have at least one entry in values Map?
I have a question regarding JPA criteria.
Here is my JPA criteria query:
CriteriaBuilder criteriaBuilder = getEm().getCriteriaBuilder();
CriteriaQuery<InventoryItemSumReport> query = criteriaBuilder.createQuery(InventoryItemSumReport.class);
Root<InventoryItemDetail> from = query.from(InventoryItemDetail.class);
Join<InventoryItemDetail, InventoryItem> joinItem = from.join(InventoryItemDetail_.inventoryItem);
Predicate where = criteriaBuilder.lessThanOrEqualTo(from.get(InventoryItemDetail_.effectiveDate), date);
query.multiselect(joinItem.get(InventoryItem_.product),joinItem.get(InventoryItem_.facility),joinItem.get(InventoryItem_.customer));
query.groupBy(joinItem.get(InventoryItem_.product),joinItem.get(InventoryItem_.facility),joinItem.get(InventoryItem_.customer));
query.where(where);
TypedQuery<InventoryItemSumReport> createQuery = getEm().createQuery(query);
List<InventoryItemSumReport> resultList = createQuery.getResultList();
Here is the resulting query produced by the JPA provider:
select
inventoryi1_.PRODUCT_ID as col_0_0_,
inventoryi1_.FACILITY_ID as col_1_0_,
inventoryi1_.CUSTOMER_ID as col_2_0_
from
INVENTORY_ITEM_DETAIL inventoryi0_
inner join
INVENTORY_ITEM inventoryi1_
on inventoryi0_.INVENTORY_ITEM_ID=inventoryi1_.ID
inner join
PRODUCT product2_
on inventoryi1_.PRODUCT_ID=product2_.ID
inner join
FACILITY facility3_
on inventoryi1_.FACILITY_ID=facility3_.ID
inner join
CUSTOMER customer4_
on inventoryi1_.CUSTOMER_ID=customer4_.ID
where
inventoryi0_.EFFECTIVE_DATE<= ?
group by
inventoryi1_.PRODUCT_ID ,
inventoryi1_.FACILITY_ID ,
inventoryi1_.CUSTOMER_ID
But I would like the following query:
select
inventoryi1_.PRODUCT_ID as col_0_0_,
inventoryi1_.FACILITY_ID as col_1_0_,
inventoryi1_.CUSTOMER_ID as col_2_0_
from
INVENTORY_ITEM_DETAIL inventoryi0_
inner join
INVENTORY_ITEM inventoryi1_
on inventoryi0_.INVENTORY_ITEM_ID=inventoryi1_.ID
inner join
PRODUCT product2_
on inventoryi1_.PRODUCT_ID=product2_.ID
inner join
FACILITY facility3_
on inventoryi1_.FACILITY_ID=facility3_.ID
left join
CUSTOMER customer4_
on inventoryi1_.CUSTOMER_ID=customer4_.ID
where
inventoryi0_.EFFECTIVE_DATE<= ?
group by
inventoryi1_.PRODUCT_ID ,
inventoryi1_.FACILITY_ID ,
inventoryi1_.CUSTOMER_ID
with a left join CUSTOMER to get also results where Customers are null.
Customer, Product, Facility are all entites, while InventoryItemSumReport is a Value object or DTO.
public class InventoryItemSumReport implements Serializable {
private static final long serialVersionUID = 1L;
private Product product;
private Facility facility;
private Customer customer;
public InventoryItemSumReport(Product product, Facility facility, Customer customer) {
super();
this.product = product;
this.facility = facility;
this.customer = customer;
}
}
I found as follows it works:
CriteriaBuilder criteriaBuilder = getEm().getCriteriaBuilder();
CriteriaQuery<InventoryItemSumReport> query = criteriaBuilder.createQuery(InventoryItemSumReport.class);
Root<InventoryItemDetail> from = query.from(InventoryItemDetail.class);
Join<InventoryItemDetail, InventoryItem> joinItem = from.join(InventoryItemDetail_.inventoryItem);
Predicate where = criteriaBuilder.lessThanOrEqualTo(from.get(InventoryItemDetail_.effectiveDate), date);
Join<InventoryItem, Customer> joinCustomer = joinItem.join(InventoryItem_.customer, JoinType.LEFT);
query.multiselect(joinItem.get(InventoryItem_.product),joinItem.get(InventoryItem_.facility),joinItem.get(InventoryItem_.customer));
query.groupBy(joinItem.get(InventoryItem_.product),joinItem.get(InventoryItem_.facility),joinCustomer);
query.where(where);
TypedQuery<InventoryItemSumReport> createQuery = getEm().createQuery(query);
List<InventoryItemSumReport> resultList = createQuery.getResultList();
I have a query
SELECT d.name, count(e.id) FROM department d LEFT OUTER JOIN employee e on e.department_id = d.id and e.salary > 5000
and how i can convert this to jpa
right now i have:
CriteriaQuery<Object[]> criteria = builder.createQuery(Object[].class);
Root<Department> root = criteria.from(Department.class);
Path<String> name = root.get("name");
Expression<Long> empCount = builder.count(root.get("employees").get("id"));
criteria.multiselect(name,empCount);
TypedQuery<Object[]> query = em.createQuery(criteria);
I simplified both examples by removing ordering and grouping
can anyone tell me how i can modifie my jpa code to get same reslults like from my sql query
thanks in advance
You're not far from the result. The problem is that, AFAIK, you can't add any restriction on the on clause, using JPA. So the query wil have to be rewritten as
SELECT d.name, count(e.id) FROM department d
LEFT OUTER JOIN employee e on e.department_id = d.id
where (e.id is null or e.salary > 5000)
Here is the equivalent of this query not tested):
CriteriaQuery<Object[]> criteria = builder.createQuery(Object[].class);
Root<Department> root = criteria.from(Department.class);
Path<String> name = root.get("name");
Join<Department, Employee> employee = root.join("employees", JoinType.LEFT);
Expression<Long> empCount = builder.count(employee.get("id"));
criteria.multiselect(name,empCount);
criteria.where(builder.or(builder.isNull(employee.get("id")),
builder.gt(employee.get("salary"), 5000)));
TypedQuery<Object[]> query = em.createQuery(criteria);
My Requirement is to write a sql query to get the sub-region wise (fault)events count that occurred for the managedobjects. My database is postgres 8.4. Let me explain using the table structure.
My tables in django:
Managedobject:
class Managedobject(models.Model):
name = models.CharField(max_length=200, unique=True)
iscontainer = models.BooleanField(default=False,)
parentkey = models.ForeignKey('self', null=True)
Event Table:
class Event(models.Model):
Name = models.CharField(verbose_name=_('Name'))
foid = models.ForeignKey(Managedobject)
Managedobject Records:
NOC
Chennai
MO_1
MO_2
MO_3
Mumbai
MO_4
MO_5
MO_6
Delhi
Bangalore
IP
Calcutta
Cochin
Events Records:
event1 MO_1
event2 MO_2
event3 MO_3
event4 MO_5
event5 MO_6
Now I need to get the events count for all the sub-regions. For example,
for NOC region:
Chennai - 3
Mumbai - 2
Delhi - 0
Bangalore - 0
So far I am able to get the result in two different queries.
Get the subregions.
select id from managedobject where iscontainer = True and parentkey = 3489
For each of the region (using for loop), get the count as follows:
SELECT count(*)
from event ev
WHERE ev.foid
IN (
WITH RECURSIVE q AS (
SELECT h
FROM managedobject h
WHERE parentkey = 3489
UNION ALL
SELECT hi
FROM q
JOIN managedobject hi
ON hi.parentkey = (q.h).id
)
SELECT (q.h).id FROM q
)
Please help to combine the queries to make it a single query and for getting the top 5 regions. Since the query is difficult in django, I am going for a raw sql query.
I got the query:
WITH RECURSIVE q AS (
SELECT h,
1 AS level,
id AS ckey,
displayname as dname
FROM managedobject h
WHERE parentkey = 3489
and logicalnode=True
UNION ALL
SELECT hi,
q.level + 1 AS level,
ckey,
dname
FROM q
JOIN managedobject hi ON hi.parentkey = (q.h).id
)
SELECT count(ckey) as ccount,
ckey,
dname
FROM q
JOIN event as ev on ev.foid_id = (q.h).id
GROUP BY ckey, dname
ORDER BY ccount DESC
LIMIT 5