JPA Criteria Equivalent Query Subquery greatest or max - jpa

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

Related

Linq order by using query expression

Is it possible to do orderby expression using linq query expression based on dynamic string parameter? because the query i have is producing weird SQL query
my linq:
var product = from prod in _context.Products
join cat in _context.Categories on prod.CategoryId equals cat.CategoryId
join sup in _context.Suppliers on prod.SupplierId equals sup.SupplierId
orderby sortParam
select new ProductViewModel
{
ProductName = prod.ProductName,
ProductId = prod.ProductId,
QuantityPerUnit = prod.QuantityPerUnit,
ReorderLevel = prod.ReorderLevel,
UnitsOnOrder = prod.UnitsOnOrder,
UnitPrice = prod.UnitPrice,
UnitsInStock = prod.UnitsInStock,
Discontinued = prod.Discontinued,
Category = cat.CategoryName,
Supplier = sup.CompanyName,
CategoryId = cat.CategoryId,
SupplierId = sup.SupplierId
};
where var sortParam = "prod.ProductName"
The code above produces weird sql where order by sortParam is being converted to (SELECT 1). Full query catched by sql profiler below:
exec sp_executesql N'SELECT [prod].[ProductName], [prod].[ProductID], [prod].[QuantityPerUnit], [prod].[ReorderLevel], [prod].[UnitsOnOrder], [prod].[UnitPrice], [prod].[UnitsInStock], [prod].[Discontinued], [cat].[CategoryName] AS [Category], [sup].[CompanyName] AS [Supplier], [cat].[CategoryID], [sup].[SupplierID]
FROM [Products] AS [prod]
INNER JOIN [Categories] AS [cat] ON [prod].[CategoryID] = [cat].[CategoryID]
INNER JOIN [Suppliers] AS [sup] ON [prod].[SupplierID] = [sup].[SupplierID]
ORDER BY (SELECT 1)
OFFSET #__p_1 ROWS FETCH NEXT #__p_2 ROWS ONLY',N'#__p_1 int,#__p_2 int',#__p_1=0,#__p_2=10
I'm seeing a lot of people doing linq order by using dynamic parameter but all of them use lambda not query expression, please enlighten me
As was already mentioned, you are passing a string value instead of an expression that reflects the column name. There are options for what you want however, see for example here.

JPA Criteria orderBy: unexpected AST node

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

Need help in creating CriteriaQuery

First of all, I would like to know if it is possible to do?
Below is my query and I am trying to build using criteria.
SELECT CONCAT('record-', rl.record_id) AS tempId,
'sloka' AS type,
rl.record_id AS recordId,
rl.title AS title,
rl.locale as locale,
rl.intro AS intro,
rl.title AS localetitle,
NULL AS audioUrl,
lp.name AS byName,
lp.person_id AS byId,
lp.name AS onName,
lp.person_id AS onId
FROM record_locale rl
LEFT JOIN record r ON rl.record_id = r.record_id
LEFT JOIN locale_person lp ON r.written_on = lp.person_id
WHERE rl.title LIKE :title
AND rl.locale = :locale
AND lp.locale = :locale
UNION
SELECT CONCAT('lyric-', s.song_id) AS tempId,
'bhajan' AS type,
s.song_id AS recordId,
s.title,
l.locale as locale,
NULL AS intro,
l.title AS localetitle,
s.audio_url AS audioUrl,
lpb.name AS byName,
lpb.person_id AS byId,
lpo.name AS onName,
lpo.person_id AS onId
FROM song s
LEFT JOIN locale_person lpb
ON (s.written_by = lpb.person_id AND lpb.locale = :locale)
LEFT JOIN locale_person lpo
ON (s.written_on = lpo.person_id AND lpo.locale = lpb.locale)
INNER JOIN lyric l
ON (l.locale = lpb.locale AND l.song_id = s.song_id)
WHERE s.title LIKE :title AND s.approved_by IS NOT NULL
ORDER BY localeTitle ASC
// END
Based on few conditions, I might need to have union of both queries or just individual query without union.
Converting the SQL to JPQL is usually a good first step, as we can't quite tell what these tables map to, or what you are expecting to get back. If it is possible to do in JPQL, it should be possible with a criteria query. Except in this case: JPA/JPQL does not have the union operator so it won't work in straight JPA, but some providers such as EclipseLink have support. See:
UNION to JPA Query
and
http://www.eclipse.org/eclipselink/documentation/2.5/jpa/extensions/j_union.htm

How to return specific properties from a JPA repository?

I'm getting a ClassCastException when retrieving an entity into a resource. It occurs only when the JPA repository statement explicitly specifies the properties to be returned.
For example, this works just fine and there is no exception triggered:
#Query("SELECT r FROM Rollout r, RolloutAdmin ra WHERE r.id = ra.rollout.id AND ra.admin = :admin")
And it renders as:
select rollout0_.id as id1_8_, rollout0_.version as version2_8_, rollout0_.bts_nb_all as bts_nb_a3_8_, rollout0_.country_id as country_9_8_, rollout0_.creation_datetime as creation4_8_, rollout0_.description as descript5_8_, rollout0_.end_datetime as end_date6_8_, rollout0_.name as name7_8_, rollout0_.operator_id as operato10_8_, rollout0_.start_datetime as start_da8_8_ from rollout rollout0_ cross join rollout_admin rolloutadm1_ where rollout0_.id=rolloutadm1_.rollout_id and rolloutadm1_.admin_id=1 order by rollout0_.name asc limit 10;
But this triggers the exception:
#Query("SELECT DISTINCT r.id, r.country, r.operator, r.name, r.description, r.creationDatetime, r.startDatetime, r.endDatetime FROM Rollout r, RolloutAdmin ra, BTS b WHERE b.rollout.id = r.id AND r.id = ra.rollout.id AND ra.admin = :admin GROUP BY r.id")
And it renders as:
select distinct rollout0_.id as col_0_0_, rollout0_.country_id as col_1_0_, rollout0_.operator_id as col_2_0_, rollout0_.name as col_3_0_, rollout0_.description as col_4_0_, rollout0_.creation_datetime as col_5_0_, rollout0_.start_datetime as col_6_0_, rollout0_.end_datetime as col_7_0_, country3_.id as id1_4_0_, operator4_.id as id1_6_1_, country3_.version as version2_4_0_, country3_.code as code3_4_0_, country3_.list_order as list_ord4_4_0_, country3_.name as name5_4_0_, operator4_.version as version2_6_1_, operator4_.country_id as country_9_6_1_, operator4_.description as descript3_6_1_, operator4_.image as image4_6_1_, operator4_.messenger as messenge5_6_1_, operator4_.name as name6_6_1_, operator4_.operator_id as operator7_6_1_, operator4_.url as url8_6_1_ from rollout rollout0_ inner join country country3_ on rollout0_.country_id=country3_.id inner join operator operator4_ on rollout0_.operator_id=operator4_.id cross join rollout_admin rolloutadm1_ cross join bts bts2_ where bts2_.rollout_id=rollout0_.id and rollout0_.id=rolloutadm1_.rollout_id and rolloutadm1_.admin_id=1 group by rollout0_.id order by rollout0_.name asc limit 10;
How come such a difference ? Any way to specify the properties to be returned without fetching the whole dependent parent properties ?
EDIT: I now understand, thanks to Nizet, that I need to return an object of the same type as my repository method.
It's quite simple:
SELECT r FROM Rollout r
means: I want this query to return r, of type Rollout. The query returns a List<Rollout>.
Whereas
SELECT DISTINCT r.id, r.country, r.operator, r.name, r.description, r.creationDatetime, r.startDatetime, r.endDatetime FROM Rollout r
means: I want this query to return many different things: an ID, a country, an operator, etc. Since the query returns many different things, of different types, and potentially from several different entities (not the case in this specific example, but you could also return properties from ra), the query returns a List<Object[]>, where the first element of the array of objects is ID, the second is country, the third is operator, etc.
Not that even your first query is more complex than it should. The association between the two entities already says how the two entities are joined. So all you need is
SELECT r FROM RolloutAdmin ra join ra.rollout r WHERE ra.admin = :admin

Linq to Entities nested subqueries

I am fairly new to linq and am having a problem translating the following sql to linq:
select
c3.COURSE, c3.DESCR
from
(select c.courseprefix, MAX(c.coursesuffix) [coursesuffix]
from
(select distinct SUBSTRING(course,1,3)[courseprefix], RIGHT(course, LEN(course) - 3) [coursesuffix]
from PsCourses where LEN(course) >= 3 ) c
group by c.courseprefix
) c2 inner join PsCourses c3 on (c2.courseprefix + c2.coursesuffix) = c3.COURSE
where
c3.COURSE_STATUS = 'A'
order by
c3.course
Well, you can use http://www.sqltolinq.com/downloads
or you can make a IQueryable, but in my personal criteria the complex queries I prefer to write in native sql