英文:
QueryDsl - How create inner join with sorted and grouped table
问题
以下是翻译好的部分:
我必须找到销售员已经销售过某种 itemType
的情况。我创建了一个方法(见下文)。
但客户告诉我,他想要按照最后销售的 itemType
来找到销售员。数据库模式如下:
(图片已省略)
我的尝试:在表 ORDERS 中,我们有一个日期列,所以在普通的 SQL 查询中,我可以进行双重子查询,应该可以实现目标。
双重,因为我首先按日期排序,然后按销售员分组 - 这将返回仅包含最后销售项目的列表。
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"
不幸的是,queryDSL
只能在 IN 子句中进行子查询,而不能在 FROM 子句中进行子查询。
我花了很多时间寻找解决方案,在我看来,使用 queryDSL
在一个查询中获取排序和分组的列表,并将其与另一个表连接在一起,是不可能的。
但也许有更丰富经验的人有任何想法,也许解决方案比我想象的要简单
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);
... 许多奇怪的 IF 语句 ...
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();
}
环境:
- Java 1.8
- SpringBoot 2.0.9
- QueryDSL 4.1.4
英文:
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
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
答案1
得分: 3
这并不是 QueryDSL 的限制,而是 JPQL(Java Persistence Query Language,JPA 的查询语言)的限制。例如,SQL 允许在 FROM 子句中使用子查询,因此 querydsl-sql
也支持此功能。但在纯 JPA 或者甚至是 Hibernate 的专有 HQL 中,是无法实现的。这种情况下,你需要编写一个原生的 SQL 查询。你可以查看 @NamedNativeQuery
。
通过使用通用表达式(Common Table Expressions,CTE),可以在 JPA 之上添加子查询,Blaze-Persistence 是一个能够实现这一功能的库。Blaze-Persistence 也提供了与 QueryDSL 的集成。
使用这个扩展库,你可以编写如下代码:
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();
另外,当使用 Hibernate 时,你可以将一个子查询映射为一个实体,然后在查询中进行关联。通过这种方式,你可以实现相同的结果,但是在子查询中无法引用任何外部变量,也无法对子查询进行参数化。而上述的方法则可以提供这两个功能。
在你的查询中可以这样使用:
.from(QSalesman.salesman)
.innerJoin(QOrdersBySalesMan.ordersBySalesMan)
.on(ordersBySalesMan.salesmanId.eq(salesman.id))
(注意:这是代码翻译,可能需要根据上下文进行适当的调整。)
英文:
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))
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论