QueryDsl – 如何创建带有排序和分组表的内连接

huangapple go评论76阅读模式
英文:

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 在一个查询中获取排序和分组的列表,并将其与另一个表连接在一起,是不可能的。

但也许有更丰富经验的人有任何想法,也许解决方案比我想象的要简单 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:

QueryDsl – 如何创建带有排序和分组表的内连接

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 = &quot;CAR&quot; 

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 QueryDsl – 如何创建带有排序和分组表的内连接

public List&lt;SalesmanEntity&gt; findSalesman(SalesmanSearchCriteriaTo criteria) {
	SalesmanEntity salesmanEntity = Alias.alias(SalesmanEntity.class);
	EntityPathBase&lt;SalesmanEntity&gt; alias = Alias.$(salesman);	
	JPAQuery&lt;SalesmanEntity&gt; query = new JPAQuery&lt;SalesmanEntity&gt;(getEntityManager()).from(alias);

	... a lot of wird IF&#39;s....

	if (criteria.getLastSoldItemTyp() != null) {
		OrderEntity order = Alias.alias(OrderEntity.class);
		EntityPathBase&lt;OrderEntity&gt; 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(&quot;t&quot;);

List&lt;RecursiveEntity&gt; fetch = new BlazeJPAQuery&lt;&gt;(entityManager, cbf)
.select(recursiveEntity)
.from(select(recursiveEntity)
    .from(recursiveEntity)
    .where(recursiveEntity.parent.name.eq(&quot;root1&quot;))
    .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(&quot;SELECT salesman_id, sum(amount) FROM ( SELECT * FROM ORDERS ORDER BY ORDERS.date ) GROUP BY ORDERS.salesman_id&quot;)
  class OrdersBySalesMan {
        @Id @Column(name = &quot;salesman_id&quot;) 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))

huangapple
  • 本文由 发表于 2020年7月27日 19:46:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/63114665.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定