Currently I'm working on a "filter"-function for a customer list.
I have this following subquery and I'm not sure if there is a way to build this with the CriteriaBuilder. It would also work without the multiselect b.date, ... but I need Group By, Order By and Limit in order to select the min value. The result should be a single number.
SELECT (COALESCE(SUM(b.account1),0) + COALESCE(SUM(b.account2),0) + COALESCE(SUM(b.deposits),0)) AS SUM FROM customer_balance b WHERE b.client_nr = '123455'
GROUP BY b.date
ORDER BY SUM
LIMIT 1
e.g. Customer Balance Table
"client_nr","date","account1","account2","deposits"
"1234567","2018-02-28",204600.0,82500.0,21120.0
UPDATE:
This is my code currently, but
Subquery<BigDecimal> CustomerBalanceSubQuery = CustomerQuery.subquery(BigDecimal.class);
Root<CustomerBalance> CustomerBalance = CustomerBalanceSubQuery.from(CustomerBalance.class);
Expression<BigDecimal> currentAccount = criteriaBuilder.sum(CustomerBalance.get("currentAccount"));
Expression<BigDecimal> overnightDeposits = criteriaBuilder.sum(CustomerBalance.get("overnightDeposits"));
Expression<BigDecimal> termDeposits = criteriaBuilder.sum(CustomerBalance.get("termDeposits"));
CustomerBalanceSubQuery.select(criteriaBuilder.sum(termDeposits, criteriaBuilder.sum(overnightDeposits, currentAccount)));
Predicate predicate = criteriaBuilder.equal(CustomerBalance.get("id").get("ClientNr"), Customer.get("ClientNr"));
CustomerBalanceSubQuery.where(predicate);
// adding to main-query
queryPredicateList.add(criteriaBuilder.and(criteriaBuilder.greaterThan(CustomerBalanceSubQuery, BigDecimal.valueOf(liquidity))));
How can I add orderBy for my subquery?
How can I set maxResult?
Thanks!
B
After talking to some colleagues I refactored the query to:
SELECT min(COALESCE(b.current_account,0) + COALESCE(b.overnight_deposits,0) + COALESCE(b.term_deposits,0)) FROM customer_balance b WHERE b.client_nr = '123456'
And the code...
Subquery<BigDecimal> customerBalanceSubQuery = customerQuery.subquery(BigDecimal.class);
Root<CustomerBalance> customerBalance = customerBalanceSubQuery.from(CustomerBalance.class);
Expression<BigDecimal> currentAccount = customerBalance.get("currentAccount");
Expression<BigDecimal> overnightDeposits = customerBalance.get("overnightDeposits");
Expression<BigDecimal> termDeposits = customerBalance.get("termDeposits");
customerBalanceSubQuery.select(criteriaBuilder.min(criteriaBuilder.sum(termDeposits, criteriaBuilder.sum(overnightDeposits, currentAccount))));
Predicate predicate = criteriaBuilder.equal(customerBalance.get("id").get("clientNr"), customer.get("clientNr"));
customerBalanceSubQuery.where(predicate);
queryPredicateList.add(criteriaBuilder.and(criteriaBuilder.greaterThan(CustomerBalanceSubQuery, BigDecimal.valueOf(liquidity))));
Anyway, it would be interesting to see the query from the original question.
Other than #BillyFrost mentioned, I'm not sure if every sql query is buildable with the Criteria API.
I have the following criteria query, which retrieves some fields from Anfrage and Sparte entities and also the translated string for the sparte.i18nKey.
This works as expected if I dont use orderBy.
Now I have the requirement to sort by the translated string for sparte.i18nKey and using the orderBy as shown below, results in QuerySyntaxException: unexpected AST node
So the problem must be the subselect in the orderBy clause!
select distinct new
my.domain.model.dto.AnfrageDTO(
anfrage0.id,
anfrage0.name,
anfrage0.sparte.id,
anfrage0.sparte.i18nKey,
-- retrieve translated string for sparte.i18nKey
(select rb0.value from at.luxbau.mis2.domain.model.ResourceBundleEntity as rb0
where (anfrage0.sparte.i18nKey = rb0.key) and (rb0.language = 'de'))
)
from my.domain.model.impl.Anfrage as anfrage0
left join anfrage0.sparte as sparte
order by (
-- sort by translated string for sparte.i18nKey
select rb1.value
from my.domain.model.ResourceBundleEntity as rb1
where (anfrage0.sparte.i18nKey = rb1.key) and (rb1.language = 'de')
) asc
My Java code looks like this:
private List<AnfrageDTO> getAnfragen() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<AnfrageDTO> query = cb.createQuery(AnfrageDTO.class);
Root<Anfrage> anfrage = query.from(Anfrage.class);
anfrage.join(Anfrage_.sparte, JoinType.LEFT);
query.select(cb.construct(AnfrageDTO.class,
anfrage.get(Anfrage_.id),
anfrage.get(Anfrage_.name),
anfrage.get(Anfrage_.sparte).get(Sparte_.id),
anfrage.get(Anfrage_.sparte).get(Sparte_.i18nKey),
// create subquery for translated sparte.i18nKey
createResourceBundleSubQuery(cb, query, anfrage.get(Anfrage_.sparte).get(Sparte_.i18nKey)).getSelection()));
TypedQuery<AnfrageDTO> tq = entityManager
.createQuery(query)
// use subquery to sort by translated sparte.i18nKey
.orderBy(cb.asc(createResourceBundleSubQuery(cb, query, anfrage.get(Anfrage_.sparte).get(Sparte_.i18nKey))));
tq.setMaxResults(10);
List<AnfrageDTO> anfragen = tq.getResultList();
return anfragen;
}
public Subquery<String> createResourceBundleSubQuery(CriteriaBuilder cb, CriteriaQuery<?> query, <String> expr) {
Subquery<String> subquery = query.subquery(String.class);
Root<ResourceBundleEntity> rb = subquery.from(ResourceBundleEntity.class);
subquery
.select(rb.get(ResourceBundleEntity_.value))
.where(cb.and(
cb.equal(expr, rb.get(ResourceBundleEntity_.key)),
cb.equal(rb.get(ResourceBundleEntity_.language), "de")));
return subquery;
}
Using a native SQL query with subselect in orderBy works also as expected.
select distinct
anfrage0_.id,
anfrage0_.name,
anfrage0_.sparte_id,
sparte4_.i18n_key,
(select rb3.i18n_value from resource_bundle rb3 where rb3.language_code = 'de' and rb3.i18n_key = sparte4_.i18n_key) as sparte_i18n_value
from
mis2.anfrage anfrage0_
left outer join mis2.sparte sparte4_ on anfrage0_.sparte_id = sparte4_.id
order by (
select rb.i18n_value
from mis2.resource_bundle rb
where sparte4_.i18n_key = rb.i18n_key and rb.language_code = 'de'
) asc
Also using an alias in the native SQL query works also as expected.
select distinct
anfrage0_.id,
anfrage0_.name,
anfrage0_.sparte_id,
sparte4_.i18n_key,
(select rb3.i18n_value from resource_bundle rb3 where rb3.language_code = 'de' and rb3.i18n_key = sparte4_.i18n_key) as sparte_i18n_value
from
mis2.anfrage anfrage0_
left outer join mis2.sparte sparte4_ on anfrage0_.sparte_id = sparte4_.id
order by sparte_i18n_value
asc
It would be great if JPA Criteria API would support using an alias in orderBy clause!
Any hints welcome - Thank you!
My environment is WildFly 11 and PostgreSQL 9.6.
JPA doesn't support passing parameter in order by clause, your problem has been asked before: Hibernate Named Query Order By parameter
Can you suggest a custom query for
SELECT p1.*
FROM ngb.payment_bifurcation AS p1
WHERE p1.bill_id = 13528 And exists ( SELECT *
FROM ngb.payment AS p2
WHERE p2.deleted = false and p1.payment_id = p2.id
)
bill_id is a parameter.
You might need to create a native query for this. Standard Data JPA methods might not help.
I would like to do a subselect in order to do the following postgresql query with the querybuilder:
SELECT i.* FROM internship i
WHERE EXISTS (SELECT iw.*
FROM internship_weeks iw
WHERE i.id = iw.internship)
Does anyone have an idea how to get the same result with queryBuilder? or maybe with DQL?
Thanks for the help !
As example, only for demonstrate HOW-TO use a subquery select statement inside a select statement, suppose we what to find all user that not yet have compile the address (no records exists in the address table):
// get an ExpressionBuilder instance, so that you
$expr = $this->_em->getExpressionBuilder();
// create a subquery
$sub = $this->_em->createQueryBuilder()
->select('iw')
->from(IntershipWeek::class, 'iw')
->where('i.id = iw.intership');
$qb = $this->_em->createQueryBuilder()
->select('i')
->from(Intership::class, 'u')
->where($expr->exists($sub->getDQL()));
return $qb->getQuery()->getResult();
Hope this help
This SQL query does exactly what is needed when executed. I am looking for the JPA Criteria equivalent? Basically gets the Device entity and the LATEST(greatest) GPS coord from a One-To-Many relationship
SELECT DISTINCT t0.DEVICE_I, t0.ACTIVE_S, t0.APP_CONFIG_I, t0.PREV_APP_CONFIG_I,
t0.APP_CONFIG_CONFIRMED_S, t0.APP_CONFIG_RECEIPT_D, t0.APP_CONFIG_SENT_S, t0.CHANNEL_X,
t0.CREATION_TS, t0.CREATION_USER_I, t0.DEST_QUEUE_C, t0.DIVISION_C, t0.LAN_IP_X,
t0.LAST_UPDATE_TS, t0.LAST_UPDATE_USER_I, t0.MOBILE_IP_X, t0.ORIGIN_MP_I,
t0.PREV_CHANNEL_X, t0.TELEPHONE_NUMBER_X, t0.VERSION_X, t1.DEVICE_I, t1.COORDINATE_I,
t1.CREATION_TS, t1.CREATION_USER_I, t1.GENERATED_TS, t1.GPGGA_X, t1.GPGSA_X,
t1.GPGSV_X, t1.GPRMC_X, t1.GPVTG_X, t1.LAST_UPDATE_TS, t1.LAST_UPDATE_USER_I,
t1.WORK_ORDER_NUMBER_I
FROM DEVICE t0, GPS_COORD t1
WHERE t0.DEVICE_I = t1.DEVICE_I AND
t1.GENERATED_TS IN ( select max(GENERATED_TS)
from GPS_COORD
group by DEVICE_I )
ORDER BY t1.DEVICE_I ASC, t1.GENERATED_TS DESC
The java code below is almost there and generates the following query which is missing joined fields needed from GpsCoord. These fields are included if a fetch is done, but the Join is necessary later in the where clause of the Subquery:
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Device> cq = criteriaBuilder.createQuery(Device.class);
Root<Device> device = cq.from(Device.class);
cq.distinct(true);
Join<Device, GpsCoord> j = device.join(Device_.gpsCoords, JoinType.LEFT);
//Fetch<Device,GpsCoord> f = device.fetch(Device_.gpsCoords);
CriteriaQuery<Device> select = cq.select(device);
Subquery<Timestamp> sq = cq.subquery(Timestamp.class);
Root<GpsCoord> gpsCoord = sq.from(GpsCoord.class);
sq.select(criteriaBuilder.greatest(gpsCoord.get(GpsCoord_.generatedTs)));
sq.groupBy(gpsCoord.get(GpsCoord_.device).get(Device_.deviceI));
select.where(j.get(GpsCoord_.generatedTs).in(sq));
TypedQuery<Device> query = this.getEntityManager().createQuery(cq);
Query that is generated is missing the GpsCoord fileds from the Join oepration
SELECT DISTINCT t0.DEVICE_I, t0.ACTIVE_S, t0.APP_CONFIG_I, t0.PREV_APP_CONFIG_I,
t0.APP_CONFIG_CONFIRMED_S, t0.APP_CONFIG_RECEIPT_D, t0.APP_CONFIG_SENT_S,
t0.CHANNEL_X, t0.CREATION_TS, t0.CREATION_USER_I, t0.DEST_QUEUE_C, t0.DIVISION_C,
t0.LAN_IP_X, t0.LAST_UPDATE_TS, t0.LAST_UPDATE_USER_I, t0.MOBILE_IP_X, t0.ORIGIN_MP_I,
t0.PREV_CHANNEL_X, t0.TELEPHONE_NUMBER_X, t0.VERSION_X
FROM SPW_OWN.DEVICE t0, SPW_OWN.GPS_COORD t1
WHERE (t1.GENERATED_TS IN (SELECT MAX(t2.GENERATED_TS) FROM SPW_OWN.GPS_COORD t2,
SPW_OWN.DEVICE t3 WHERE t2.DEVICE_I = t3.DEVICE_I GROUP BY t3.DEVICE_I))
AND t0.DEVICE_I = t1.DEVICE_I(+)
Using a multiselect from the CriteriaQuery like this generates the proper SQL when executed, but the following exception
Caused by: java.lang.Exception: java.lang.RuntimeException: Can not find constructor for "class Device" with argument types "[class java.lang.String ... java.sql.Timestamp]" to fill data.
gets thrown after the call:
CriteriaQuery<Device> select = cq.multiselect(
//"deviceI",
device.get(Device_.deviceI),
//"activeS",
device.get(Device_.activeS),
//"appConfigConfirmedS",
device.get(Device_.appConfigConfirmedS),
//"appConfigReceiptD",
device.get(Device_.appConfigReceiptD),
//"appConfigSentS",
device.get(Device_.appConfigSentS),
//"channelX",
device.get(Device_.channelX),
//"destQueueC",
device.get(Device_.destQueueC),
//"lanIpX",
device.get(Device_.lanIpX),
//"mobileIpX",
device.get(Device_.mobileIpX),
//"prevChannelX",
device.get(Device_.prevChannelX),
//"telephoneNumberX",
device.get(Device_.telephoneNumberX),
//"versionX"
device.get(Device_.versionX),
//"device",
j.get(GpsCoord_.device),
//"gpggaX",
j.get(GpsCoord_.gpggaX),
//"gprmcX",
j.get(GpsCoord_.gprmcX),
//"gpgsaX",
j.get(GpsCoord_.gpgsaX),
//"gpgsvX",
j.get(GpsCoord_.gpgsvX),
//"gpvtgX"
j.get(GpsCoord_.gpvtgX),
j.get(GpsCoord_.generatedTs)
);
A few issues,
you call, sq.from(GpsCoord.class); twice
using device.fetch(Device_.gpsCoords); will also fetch the object, and will join it twice
you IN is incorrect it should be,
select.where(criteriaBuilder.get(path).in(sq));