I have a rather complex query which works in SQL, but I would like to express this in HQL for portability. I'm going to fetch a user configured preference value if they exist, if not I must use a default value. This value must be subtracted from current date and the matched against a column in the table which I'm interested in:
select d.id, d.guid, d.deletetimestamp, u.id
from descriptor d, ownerkey ow, user u
where
d.parentid in
(select td.id
from descriptor td, store s
where s.type = 'Trash'
and s.descriptorid = td.id
)
and d.ownerkeyid = ow.id
and ow.ownerid = u.id
and
(
(d.deletetimestamp < CURRENT_TIMESTAMP() - INTERVAL
(select pv.value
from preferencevalue pv, userpreference up
where u.id = up.userid
and up.preferenceid = 26
and up.value = pv.id)
DAY)
or
(d.deletetimestamp < CURRENT_TIMESTAMP() - INTERVAL
(select cast(pv.value as SIGNED)
from preferencevalue pv, defaultpreference dp
where dp.preferenceid = 26
and not exists(select up.userid from userpreference up where u.id = up.userid and up.preferenceid = 26)
and dp.value = pv.id)
DAY)
)
I'm trying to construct this by using the Criteria API which seems to include most of the logical operators that I need (equals, larger than, or, isEmpty/isNull), but not sure how I would express all these parts.
Using a view is not an option at this point since we're using MySQL as the production database while the integration tests are running with H2 inmemory database. I'm not able to get find the sata substract function in H2 while MySQL do support this.
The select fields isn't important since they have only been used for testing purposes.
you can use Restrictions.disjunction() for or -and Restrictions.conjuction() for and clauses.
To reference a certain property of an entity (like pv.value) you can use Projections.property("value")
for the casting I'm not sure, perhaps using the #Formula annotation on your entity? But this is a hibernate and not a JPA annotation.
as far as I know there is no equivalent for INTERVAL in hibernate but in such cases (maybe also for the above cast) you could use Restrictions.sqlRestriction("some sql...")
It will be a challenge putting all of this together to transform your query to hibernate criteria.
greetz,
Stijn
Related
I have to find Salesmen that have sold some itemType. I created method (see below).
But client told me that he wants to find Salesmen by LAST sold itemType.
DB schema:
My attempts: we have in table ORDERS date column, so in normal SQL query I can do double subquery and it should work.
Double, because first I'm sorting by date, then group by salesman - that returns list with only last sold items.
SELECT *
FROM SALESMEN
JOIN
(SELECT *
FROM
(SELECT *
FROM ORDERS
ORDER BY ORDERS.date)
GROUP BY ORDERS.salesman_id) ON SALESMEN.id = ORDERS.salesman_id
WHERE ORDERS.item_type = "CAR"
Unfortunately, queryDSL can do subquery only in IN clause not in FROM.
I spend many hours to find a solution, and in my opinion, it's simply impossible using queryDSL to get sorted and grouped list and join it with another table in one query.
But maybe someone with grater experience has any idea, maybe a solution is simpler than I think :D
public List<SalesmanEntity> findSalesman(SalesmanSearchCriteriaTo criteria) {
SalesmanEntity salesmanEntity = Alias.alias(SalesmanEntity.class);
EntityPathBase<SalesmanEntity> alias = Alias.$(salesman);
JPAQuery<SalesmanEntity> query = new JPAQuery<SalesmanEntity>(getEntityManager()).from(alias);
... a lot of wird IF's....
if (criteria.getLastSoldItemTyp() != null) {
OrderEntity order = Alias.alias(OrderEntity.class);
EntityPathBase<OrderEntity> aliasOrder = Alias.$(order);
query.join(aliasOrder)
.on(Alias.$(salesman.getId()).eq(Alias.$(order.getSalesmanId())))
.where(Alias.$(order.getItemTyp()).eq(criteria.getLastSoldItemTyp()));
}
return query.fetch();
}
Environment:
Java 1.8
SpringBoot 2.0.9
QueryDSL 4.1.4
This is not a limitation of QueryDSL, rather it is a limitation of JPQL - the query language of JPA. For example, SQL does allow subqueries in the FROM clause, and as such querydsl-sql also allows it. With plain plain JPA, or even Hibernate's proprietary HQL it cannot be done. You would have to write a native SQL query then. For this you can have a look at #NamedNativeQuery.
It is possible to add subqueries on top of JPA using Common Table Expressions (CTE) using Blaze-Persistence. Blaze-Persistence ships with an optional QueryDSL integration as well.
Using that extension library, you can just write the following:
QRecursiveEntity recursiveEntity = new QRecursiveEntity("t");
List<RecursiveEntity> fetch = new BlazeJPAQuery<>(entityManager, cbf)
.select(recursiveEntity)
.from(select(recursiveEntity)
.from(recursiveEntity)
.where(recursiveEntity.parent.name.eq("root1"))
.orderBy(recursiveEntity.name.asc())
.limit(1L), recursiveEntity)
.fetch();
Alternatively, when using Hibernate, you can map a Subquery as an Entity, and then correlate that in your query. Using this you can achieve the same result, but you won't be able to reference any outer variables in the subquery, nor will you be able to parameterize the subquery. Both of these features will however be available with the above approach!
#Entity
#Subselect("SELECT salesman_id, sum(amount) FROM ( SELECT * FROM ORDERS ORDER BY ORDERS.date ) GROUP BY ORDERS.salesman_id")
class OrdersBySalesMan {
#Id #Column(name = "salesman_id") Long salesmanId;
#Basic BigDecimal amount; // or something similarly
}
And then in your query:
.from(QSalesman.salesman)
.innerJoin(QOrdersBySalesMan.ordersBySalesMan)
.on(ordersBySalesMan.salesmanId.eq(salesman.id))
I'm using Spring Boot 1.5.2 and the following #Query:
#Query(value = "SELECT m FROM Message m WHERE m.from.userId = :myId OR m.to.userId = :myId GROUP BY m.from.userId, m.to.userId ORDER BY m.date DESC")
List<Message> chatOverview(#Param("myId") final Long myUserId);
The intention of the query is to create a chat messenger overview, where you see the last message of each conversation you had. It works fine for me in dev, but in production (newer MySQL database version) I get this error:
java.sql.SQLSyntaxErrorException: Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'message0_.message_id' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
I read in this thread what the reason for this change was, however I couldn't find a way to fix this with JPA / Spring. I cannot change any settings in the production MySQL database and I would like to avoid any upgrading in Spring either. How can I fix my query?
Here is the definition and purpose of the GROUP BY (see section 4.7) clause:
The GROUP BY construct enables the aggregation of result values according to a set of properties.
That means it is used only if you're aggregating (sum, avg, max, min, ...) the value(s) of a field(s). But in your case I don't see any aggregation function. So just remove the GROUP BY clause and everything should be fine:
SELECT m FROM Message m WHERE m.from.userId = :myId OR m.to.userId = :myId ORDER BY m.date DESC
Grouping on userId doesn't make sense too because all the entities returned by this query will have the same value for this field.
I'm new to MongoDB, I'm trying to convert existing DB2 query to MongoDB. I'm Using Java to run this query.
Current DB2 Query:
SELECT * FROM
(SELECT MBI.*, ROW_NUMBER() OVER Order by PDATE DESC AS rownumber
FROM USER1.COLLECTION1 MBI, USER1.COLLECTION2 IC
WHERE MBI.VISIBILITY = 1 and MBI.MBOXID = '1234'
AND UPPER(MBI.MBOXITID) >= '1234555'
AND UPPER(MBI.CATEGORY) = 'S'
AND UPPER(MBI.Mimetype) = 'PDF'
AND UPPER(MBI.Psystemid) = 'TBA'
AND days(current date) - days(MBI.PDATE) < 20
AND MBI.MBOXITID = IC.ICONTENTID) AS FinalResult
WHERE rownumber BETWEEN 1 and 2 Order by PDATE DESC
Am really not sure how to get the ROW_NUMBER details in MongoDB.
Can you help for me solve problem?
#bharathiraja, hopefully, you've done the migration by now and figured out the steps. you'll have to convert your inner joins to mongodb lookup operator and then remove nulls (because lookup is left-outer-join-operator). From your query, looks like you're trying to paginate using row_number(). In Mongodb aggregation framework, after you've done the sort on the PDATE, you can add the limit and skip operator to paginate.
At Couchbase (where I work), we've built row_number() just like standard SQL. Checkout. https://blog.couchbase.com/on-par-with-window-functions-in-n1ql/
It feels like I'm close, but I cannot figure out how to do something like the below in jOOq.
MERGE INTO USER_ASSIGNMENTS ua
USING (
SELECT core_object_id
FROM core_objects
WHERE exists(SELECT *
FROM LKU_CODE lc JOIN LKU_CODE_TYPE lct
ON lc.LKU_CODE_TYPE_ID = lct.LKU_CODE_TYPE_ID AND lct.CODE_TYPE = 'OBJECT_TYPE' AND
lc.CODE = 'PORTFOLIOS'
WHERE lc.LKU_CODE_ID = core_objects.OBJECT_TYPE_ID) AND object_id = 83
) "co"
ON (ua.CORE_OBJECT_ID = "co".CORE_OBJECT_ID AND USER_ID = 24 AND SECTION = 1)
WHEN MATCHED THEN UPDATE
SET create_date = sysdate, created_by = '24', capabilities = 12
WHERE capabilities <> 12
WHEN NOT MATCHED THEN INSERT
(CAPABILITIES, CORE_OBJECT_ID, CREATE_DATE, CREATED_BY, SECTION, USER_ID)
VALUES (5, "co".CORE_OBJECT_ID, sysdate, '24', 1, 24);
The big thing to note is that I'm trying to use the value returned by USING, so I have to alias it and .values() has to accept a field call. I think I can get around the .values() issue using the .values(Collection<?>) call, bundling things, including that field, into a Collection, so I think that I have that part. What concerns me is that I cannot do an .as() call after .using(). If I make the USING query a "table" via .asTable(), supplying an alias, will that let me call the field? Here's kind of what I have at the moment:
Table<Record1<BigDecimal>> usingStatement = readContext
.select(_co.CORE_OBJECT_ID)
.from(_co)
.where(DSL.exists(readContext.select(_lc.fields()).from(
_lc.join(_lct).onKey(Keys.LC_LCT___FK)
.and(_lc.CODE.equal(capability.getObjectTypeCode()))
.and(_lct.CODE_TYPE.equal(LkuCodeTypeLookup.OBJECT_TYPE))))).asTable("sdf");
...
return writeContext
.mergeInto(_ua)
.using(usingStatement)
.on(sectionalConditions.and(_ua.CORE_OBJECT_ID.equal(coidField)))
.whenMatchedThenUpdate()
.set(_ua.CREATE_DATE, time)
.set(_ua.CREATED_BY, creator)
.set(_ua.CAPABILITIES, capabilities)
.where(_ua.CAPABILITIES.notEqual(capabilities))
.whenNotMatchedThenInsert(_ua.CAPABILITIES, _ua.CORE_OBJECT_ID, _ua.CREATE_DATE,
_ua.CREATED_BY, _ua.SECTION, _ua.USER_ID)
.values(capabilities, gcoid, time, creator, section, uuid).execute();
A "straight merge" using dual is simple in jOOq, but I'd like to try to combine that select into the merge to save queries and let the DB do what it does best, so I'm trying not to have to get core_object_id in another query, if possible.
The aliasing really happens on the table (i.e. the select), not on some artefact returned by the USING clause. At least, that's how jOOQ models it. You have already correctly aliased your usingStatement variable. Now all you have to do is dereference the desired column from it, e.g.:
usingStatement.field(_co.CORE_OBJECT_ID);
This will look for the column named CORE_OBJECT_ID in the usingStatement table.
In my Java Web application I use Postgresql and some data tables are filled automatically in server. In the database I have a STATUS table like below:
I want to select the data related to a vehicle between selected dates and where the vehicle stayed connected. Simply I want to select the data which are green in the above table which means I exactly want the data when firstly io1=true and the data when io1=false after the last io1=true. I have postgresql query statement which exactly gives me the desired data; however, I have to convert it to HQL because of my application logic.
working postgresql query:
WITH cte AS
( SELECT iostatusid, mtstrackid, io1,io2,io3, gpsdate,
(io1 <> LAG(io1) OVER (PARTITION BY mtstrackid
ORDER BY gpsdate)
) AS status_changed
FROM iostatus
WHERE mtstrackid = 'redcar' AND gpsdate between '2014-02-28 00:00:00' and '2014-02-28 23:59:59'
)
SELECT iostatusId, mtstrackid, io1, io2, io3,gpsdate
FROM cte
WHERE status_changed
OR io1 AND status_changed IS NULL
ORDER BY gpsdate ;
How should I convert the above query to HQL or how could I retrieve the desired data with HQL?
The goal of hibernate is mapping database entities to java objects. This kind of complex queries are not entities themselves. This is against the spirit of hibernate.
If this query generates an entity in your application logic, I recommend putting the results into a table and applying Hibernate queries to that table.
If this query generates some kind of aggregation or summary, there are two possible ways:
One way is you compute this aggregation/summary in your application after retrieving entities from iostatus table with hibernate.
If this query has nothing to do with your application logic then you can use Native SQL interface of Hibernate and execute the query directly. (You can even use JPA if you are willing to manipulate two database connections.)
If you absolutely need to convert it to HQL, you need to eliminate the partition function. If the order of iostatusId is identical to the order of gpsdate, you can do it similar to
SELECT i2.*
FROM iostatus i1
INNER JOIN iostatus i2 ON i1.iostatusId = i2.iostatusId - 1
AND i1.io1 <> i2.io1
AND i1.mstrackid = i2.mstrackid
WHERE i2.mtstrackid = 'redcar' AND
i2.gpsdate between '2014-02-28 00:00:00' and '2014-02-28 23:59:59'
If gpsdate is no way related to iostatusId then you need something like
SELECT i2.*
FROM iostatus i1
INNER JOIN iostatus i2 ON i1.gpsdate < i2.gpsdate
AND i1.io1 <> i2.io1
AND i1.mstrackid = i2.mstrackid
WHERE i2.mtstrackid = 'redcar' AND
i2.gpsdate between '2014-02-28 00:00:00' and '2014-02-28 23:59:59' AND
NOT EXISTS (SELECT * FROM iostatus i3
WHERE i3.gpsdate > i1.gpsdate AND
i2.gpsdate > i3.gpsdate AND
i3.io1 = i1.io1 AND
i1.mstrackid = i3.mstrackid)
I guess both of the queries can be converted to HQL, but I'm not positively sure.
By the way I must warn you that, these methods might not perform better then finding the changes in your application, because they involve joining the table onto itself, which is an expensive operation; and the second query involves a nested query after the join, which is also quite expensive.