Using two fields with "in" operator in QueryDSL - java

I have to write this query using QueryDSL:
select *
from table
where(field1, field2) in (
select inner_field_1, inner_field2
from ...
);
However, I don't know how to use two fields (field1 and field2) with an "in" operator in QueryDSL. I have been looking for it in the documentation but I haven't seen any example of two fields.
This is what I have so far:
Expression<?>[] projection = {
table.field1,
table.field2
};
SQLSubQuery outterQuery= new SQLSubQuery()
.from(table)
.where([some expression].in(inneryQuery.list(projection))) // ???
.groupBy(contentcache1.programId, contentcache1.id);
Any help would be appreciated
Thank you very much in advance

You can express it via
SQLSubQuery outerQuery = new SQLSubQuery()
.from(table)
.where(Expressions.list(column1, column2, ...).in(inneryQuery.list(projection)))
.groupBy(contentcache1.programId, contentcache1.id);

You can rewrite your original query as:
select *
from table, (select distinct inner_field_1, inner_field2 from ...) subquery
where field1 = subquery.field1 and field2 = subquery.field2
Then you don't have to use the IN operator.

You can manually transform your row-value-expression IN predicate into an equivalent EXISTS predicate, which should probably work with QueryDSL. Some details are explained in this blog post, which essentially explains how jOOQ automatically handles such SQL transformations for you, operating directly on the SQL AST, you'd write:
DSL.using(configuration)
.select()
.from(TABLE)
.where(row(TABLE.FIELD1, TABLE.FIELD2).in(
select(INNER_FIELD1, INNER_FIELD_2)
.from(...)
))
Your original query:
select *
from table
where(field1, field2) in (
select inner_field_1, inner_field_2
from ...
);
Is equivalent to this one:
select *
from table
where exists (
select 1
from ...
where table.field1 = inner_field_1 and table.field2 = inner_field2
)
... which I'm sure you can express with QueryDSL (unfortunately, I don't know the API well enough to show the actual query).
Note on compatibility
Chances are that your database doesn't support this kind of row value expression predicate anyway, in case of which you're on the safe side with EXISTS. At least these databases do support that predicate:
DB2
HSQLDB
MySQL
Oracle
Postgres

Related

QueryDsl - How create inner join with sorted and grouped table

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

Querydsl ignores common table expression alias for JPA Entities

I need to build SQL query with common table expression using QueryDSL:
WITH cte AS (
SELECT DISTINCT BUSINESS_ID FROM BUSINESS WHERE MERCHANT_CODE like ?
)
SELECT t0.*
FROM PAYMENT t0
LEFT JOIN cte t1 ON t0.PAYER = t1.BUSINESS_ID
LEFT JOIN cte t2 ON t0.PAYEE = t2.BUSINESS_ID
WHERE (t1.BUSINESS_ID IS NOT NULL OR t2.BUSINESS_ID IS NOT NULL)
I have two JPA entities (Payment, Business).
This is how I implemented that:
String merchantCode = "abcd%";
QPayment payment = QPayment.payment;
QBusiness business = QBusiness.business;
QBusiness cte = new QBusiness("cte");
QBusiness merchant1 = new QBusiness("t1");
QBusiness merchant2 = new QBusiness("t2");
Configuration configuration = new Configuration(new OracleTemplates());
new JPASQLQuery<>(entityManager, configuration)
.with(cte,
JPAExpressions.selectDistinct(business.businessId).from(business)
.where(business.merchantCode.like(merchantCode)))
.select(payment)
.from(payment)
.leftJoin(cte, merchant1).on(payment.payer.eq(merchant1.businessId))
.leftJoin(cte, merchant2).on(payment.payee.eq(merchant2.businessId))
.where(merchant1.businessId.isNotNull()
.or(merchant2.businessId.isNotNull()));
And the problem is that during leftJoin it doesn't treat cte as a link, instead it inserts table name and two aliases: LEFT JOIN BUSINESS cte t1 ON .... I tried different templates – didn't help.
Am I doing something wrong or it's a QueryDSL bug?
JPQL doen't support CTEs, as we can see in grammar. And querydsl works over JPQL. CTEs are pretty vendor-specific, so you'll have to do one of following:
Rewrite query to be JPA-compatible
Use JPA native query
Query sql with querydsl (actually I don't remember if it supports CTEs)
From all above I would chose the 2nd option. Making native queries doen't harm your code. It makes your code more performant.
Take a good look at the tutorial
QCat cat = QCat.cat;
QCat mate = new QCat("mate");
QCat kitten = new QCat("kitten");
query.from(cat)
.innerJoin(cat.mate, mate)
.leftJoin(cat.kittens, kitten)
.list(cat);
You will want to .leftjoin(cte.merchant1, merchant1).on(...) or whatever the corresponding field is called in the parent "cte".
Basically you need to name the field which you want to join. Just stating the meta model does not suffice as there is no way of telling what you actually want. You can see it in your code (as well in the tutorial's kitten example): you have two Merchant you want to join to the cte, so which one is which.
The .on()-clause just states the conditions under which a join is valid, like you could place filters there.
.

Using a java method in select query jooq

I have a mysql query which is in the following format
dslContext
.select(
ITEMDATA.ITEMID,
ITEMDATA.COST,
ITEMNAMES.ITEMNAME
)
.from(ITEMDATA)
.join(ITEMNAMES)
.on(ITEMDATA.ITEMID=ITEMNAMES.ITEMID)
.where(conditions);
The above query joins ITEMDATA with ITEMNAMES table to select ITEMNAME in the result. I am caching ITEMNAMES table in-memory and want to avoid the join with ITEMNAMES table. This would speed up the query and would simplify the query since the actual query is much more complex.
I would like to use it something similar to the following. I want to call itemNamesCache.getItemName in the select params list which gives the ITEMNAME and returns a part of the select result. getItemName should take the ITEMID returned in the response as a parameter and give the ITEMNAME.
dslContext.
select(
ITEMDATA.ITEMID,
ITEMDATA.COST,
itemNamesCache.getItemName(valueOfItemId)
)
.from(ITEMDATA)
.where(conditions);
P.S: I can iterate the results and call the itemNamesCache.getItemName. But I would like to use something embedded in the query if it's possible
You cannot have a callback from a SQL query back into some Java logic, even if the fact that you're constructing the SQL query with jOOQ (and thus Java) makes it look like that were feasible.
However, you could post-process the jOOQ result by patching records using a previously built cache:
A Java solution
In case you're working with a database that really can't handle this simple join (and you've checked that you have all proper indexes and constraints in place!) then you could try the following solution:
// Assuming this import:
import static org.jooq.impl.DSL.*;
write...
Map<Integer, String> itemNamesCache =
dslContext.selectDistinct(ITEMNAMES.ITEMID, ITEMNAMES.NAME)
.from(ITEMNAMES)
.fetchMap(ITEMNAMES.ITEMID, ITEMNAMES.NAME);
dslContext
.select(
ITEMDATA.ITEMID,
ITEMDATA.COST,
// create an empty column here
inline(null, String.class).as(ITEMNAMES.NAME))
.from(ITEMDATA)
.where(conditions)
// fill the empty column with cached values
.fetch(r -> r.value3(itemNamesCache.get(r.value1())));
A SQL-based solution
The SQL way to do that would be to write a correlated subquery.
SELECT
itemdata.itemid,
itemdata.cost,
(SELECT itemnames.name FROM itemnames WHERE itemnames.itemid = itemdata.itemid)
FROM
itemdata
WHERE
...
With jOOQ
// Assuming this import:
import static org.jooq.impl.DSL.*;
... write:
dslContext
.select(
ITEMDATA.ITEMID,
ITEMDATA.COST,
field(select(ITEMNAMES.NAME)
.from(ITEMNAMES)
.where(ITEMDATA.ITEMID.eq(ITEMNAMES.ITEMID)))
.as(ITEMNAMES.NAME)
)
.from(ITEMDATA)
.where(conditions)
.fetch();
In theory, both queries should run at exactly the same speed, because they're equivalent (if you have a foreign key on ITEMDATA.ITEMID).
In practice, most databases will probably have better performance for the JOIN query, unless they implement scalar subquery caching (e.g. like Oracle), which can drastically speed up the second query, depending on the number of distinct ITEMIDs (the smaller, the better).

jooq - execute string as subquery

I have a query, represented by a string:
final String q = "select 1 union select 2 union select 3";
This string comes from an external source (configuration), hence it is a string. In the real scenario, the query is ofcourse more meaningful.
I would like to execute this query as a subquery within a jOOQ type-safe query. The following works, but it is not really what I want:
System.out.println(<context>.select().from(DSL.table("person")).where(DSL.field("identifier").in(
<context>.fetch(q).intoArray(0)
)).fetch());
The problem here is that I am essentially executing two queries. This introduces overhead.
Is it possible to execute the string-query as a real subquery? I somehow have to convert the string-query to a Select<Record1> instance (I guess), but I cannot find how to do that.
There are a variety of places where you can inject a Select type as plain SQL. For instance:
As a plain SQL WHERE clause:
<context>.select()
.from(DSL.table("person"))
.where(
"identifier in ({0})", DSL.resultQuery(q)
)
.fetch();
As a plain SQL Table:
<context>.select()
.from(DSL.table("person"))
.where(DSL.field("identifier").in(
DSL.select().from("(" + q + ")")
))
.fetch();
There are others. The important thing to notice is that by using plain SQL, you have the possibility to embed your own SQL strings in templates that have enumerated placeholders
... {0} ... {1} ...

Is a IN subquery matching multiple columns possible in JPQL?

I have a SQL query I'm trying to convert to JPQL. The query is as follows :
SELECT *
FROM MyTable
WHERE (myFirstColumn, mySecondColumn) IN (
SELECT myFirstColumn, max(mySecondColumn)
FROM MyTable
GROUP BY myFirstColumn
)
My conversion attempt is straightforward :
select myObject
from MyObject as myObject
where (myObject.myFirstValue, myObject.mySecondValue) in (
select subMyObject.myFirstValue, max(subMyOject.mySecondValue)
from MyObject as subMyObject
group by subMyObject.myFirstValue
)
MyObject is mapped to MyTable (using annotations).
If I understand the JPQL docs on the IN statement (http://openjpa.apache.org/builds/1.2.3/apache-openjpa/docs/jpa_langref.html#jpa_langref_in), and I'm really not sure I do, such a direct conversion isn't possible. Is there another way ?
Perhaps you can change the query a little. You can use EXISTS instead of IN.
select myObject
from MyObject myObject
where exists
(
select subMyObject.myFirstValue, max(subMyOject.mySecondValue)
from MyObject subMyObject
where myObject.myFirstValue = subMyObject.myFirstValue
group by subMyObject.myFirstValue
having max(subMyOject.mySecondValue) = myObject.mySecondValue
)
In the end, I couldn't find a way and had Java do the heavy lifting (ie. sorting one the second value and finding the biggest). It's not as pretty but it works.

Categories

Resources