在Hibernate 6.2中连接CTE(公共表达式)

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

Joining CTEs in Hibernate 6.2

问题

Hibernate 6.2引入了CTE支持,但如何在单个HQL查询中使用多个CTE尚不清楚。

所以,假设我有一个名为City的实体以及以下的CTE查询:

TypedQuery<Integer> typedQuery = em.createQuery(
    """
    with max_cities as (
      SELECT id id from City c ORDER BY population DESC
      LIMIT 20 
    ), min_cities as (
      SELECT id id from City c ORDER BY population ASC
      LIMIT 20
    )
    SELECT m1.id from max_cities m1 join min_cities m2
    on m1.id = m2.id
    """, 
    Integer.class);

它包含两个CTE(min_cities和max_cities)。Hibernate对此没有抱怨。但错误消息在这里非常明确:

Caused by: java.lang.IllegalArgumentException: Could not resolve entity reference: min_cities
	at org.hibernate.metamodel.model.domain.internal.JpaMetamodelImpl.resolveHqlEntityReference(JpaMetamodelImpl.java:166)

所以Hibernate允许在FROM子句中使用第一个CTE,但在JOIN子句中期望实体(而不是CTE)。

然而,MySQL文档表明连接两个CTE是合法的:

WITH
  cte1 AS (SELECT a, b FROM table1),
  cte2 AS (SELECT c, d FROM table2)
SELECT b, d FROM cte1 JOIN cte2
WHERE cte1.a = cte2.c;

所以,在Hibernate中支持连接CTE吗?

英文:

Hibernate 6.2 introduced CTE support but it's unclear how to use multiple CTEs in the single HQL query.
So let's say I have City entity and the following CTE query:

		TypedQuery&lt;Integer&gt; typedQuery = em.createQuery(
		&quot;&quot;&quot;
           with max_cities as (
             SELECT id id from City c ORDER BY population DESC
             LIMIT 20 
          ), min_cities as (
             SELECT id id from City c ORDER BY population ASC
             LIMIT 20
          )
           SELECT m1.id from max_cities m1 join min_cities m2
              on m1.id = m2.id
		&quot;&quot;&quot;, 
		Integer.class);

It contains two CTEs (min_cities and max_cities). And Hibernate doesn't complain about that. But the error message is pretty clear here:

Caused by: java.lang.IllegalArgumentException: Could not resolve entity reference: min_cities
	at org.hibernate.metamodel.model.domain.internal.JpaMetamodelImpl.resolveHqlEntityReference(JpaMetamodelImpl.java:166)

So Hibernate allows to use first CTE in FROM clause but expects entity (and not CTE) in the JOIN clause.
However MySQL documentation states that joining two CTEs is pretty legal:

WITH
  cte1 AS (SELECT a, b FROM table1),
  cte2 AS (SELECT c, d FROM table2)
SELECT b, d FROM cte1 JOIN cte2
WHERE cte1.a = cte2.c;

So is joining CTEs supported in Hibernate ?

答案1

得分: 2

这似乎是在 ExpectingEntityJoinDelegate#consumeIdentifier 中的一个错误:

@Override
public void consumeIdentifier(String identifier, boolean isTerminal, boolean allowReuse) {
	if ( path.length() != 0 ) {
		path.append( &#39;.&#39; );
	}
	path.append( identifier );
	if ( isTerminal ) {
		final String fullPath = path.toString();
		final EntityDomainType&lt;?&gt; joinedEntityType = creationState.getCreationContext()
				.getJpaMetamodel()
				.resolveHqlEntityReference( fullPath );
		// 这里的实现假设 resolveHqlEntityReference 可能返回 null,
		// 但实际上它从不返回 null。最可能应该使用 getHqlEntityReference
		if ( joinedEntityType == null ) {
			final SqmCteStatement&lt;?&gt; cteStatement = creationState.findCteStatement( fullPath );
			if ( cteStatement != null ) {
				join = new SqmCteJoin&lt;&gt;( cteStatement, alias, joinType, sqmRoot );
				creationState.getCurrentProcessingState().getPathRegistry().register( join );
				return;
			}
			throw new SemanticException( &quot;无法解析连接路径 - &quot; + fullPath );
		}

顺便说一下,交叉连接(cross join)的确按预期工作,即:

&quot;&quot;&quot;
   with max_cities as (
     SELECT id id from City c ORDER BY population DESC
     LIMIT 20
  ), min_cities as (
     SELECT id id from City c ORDER BY population ASC
     LIMIT 20
  )
   SELECT m1.id from max_cities m1, min_cities m2
      where m1.id = m2.id
&quot;&quot;&quot;
英文:

That looks like a bug in ExpectingEntityJoinDelegate#consumeIdentifier:

@Override
public void consumeIdentifier(String identifier, boolean isTerminal, boolean allowReuse) {
	if ( path.length() != 0 ) {
		path.append( &#39;.&#39; );
	}
	path.append( identifier );
	if ( isTerminal ) {
		final String fullPath = path.toString();
		final EntityDomainType&lt;?&gt; joinedEntityType = creationState.getCreationContext()
				.getJpaMetamodel()
				.resolveHqlEntityReference( fullPath );
		// here impl assumes that resolveHqlEntityReference may return nulls,
		// however it never does that. Most probably getHqlEntityReference
		// should be used instead
		if ( joinedEntityType == null ) {
			final SqmCteStatement&lt;?&gt; cteStatement = creationState.findCteStatement( fullPath );
			if ( cteStatement != null ) {
				join = new SqmCteJoin&lt;&gt;( cteStatement, alias, joinType, sqmRoot );
				creationState.getCurrentProcessingState().getPathRegistry().register( join );
				return;
			}
			throw new SemanticException( &quot;Could not resolve join path - &quot; + fullPath );
		}

BTW, cross join does work as expected, i.e.:

&quot;&quot;&quot;
   with max_cities as (
     SELECT id id from City c ORDER BY population DESC
     LIMIT 20
  ), min_cities as (
     SELECT id id from City c ORDER BY population ASC
     LIMIT 20
  )
   SELECT m1.id from max_cities m1, min_cities m2
      where m1.id = m2.id
&quot;&quot;&quot;

huangapple
  • 本文由 发表于 2023年5月28日 20:53:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/76351608.html
匿名

发表评论

匿名网友

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

确定