【问题标题】:How to rewrite subquery in ORDER BY clause in JPA CriteriaQuery如何在 JPA CriteriaQuery 中的 ORDER BY 子句中重写子查询
【发布时间】:2019-02-14 19:25:14
【问题描述】:

我正在尝试使用 CriteriaQuery 编写 SQL 查询,但我很难做到。这个查询基本上是获取一个发货清单并按授权日期对它们进行排序。此授权日期表示为状态转换消息表中第一条记录的日期属性,初始状态为 3,最终状态为 4。这是我的查询:

SELECT s.id
FROM shipment s
ORDER BY (SELECT min(stm.date)
          FROM status_transition_message stm
          WHERE stm.initial_status = 1 AND stm.final_status = 3 AND stm.shipment_id = s.id) desc;

我尝试了多种不同的解决方案,但到目前为止都没有奏效。

我当前的迭代如下:

private void sortByAuthDate(Root<ShipmentTbl> root, CriteriaQuery<?> query, CriteriaBuilder builder, ListSort sort) {
        Subquery<Timestamp> authDateQuery = query.subquery(Timestamp.class);
        Root<StatusTransitionMessageTbl> stmRoot = authDateQuery.from(StatusTransitionMessageTbl.class);

        Predicate shipmentId = builder.equal(stmRoot.<ShipmentTbl>get("shipment").<String>get("id"), root.<String>get("id"));
        Predicate initialStatus = builder.equal(stmRoot.<Integer>get("initialStatus"), 3);
        Predicate finalStatus = builder.equal(stmRoot.<Integer>get("finalStatus"), 4);

        // returns the authorization date for each queried shipment
        authDateQuery.select(builder.least(stmRoot.<Timestamp>get("date")))
                .where(builder.and(shipmentId, initialStatus, finalStatus));

        Expression<Timestamp> authDate = authDateQuery.getSelection();
        Order o = sort.getSortDirection() == ListSort.SortDirection.ASC ? builder.asc(authDate) : builder.desc(authDate);

        query.multiselect(authDate).orderBy(o);
    }

这个方案的问题是CriteriaQuery生成的SQL查询不支持ORDER BY子句中的子查询,导致解析异常。

【问题讨论】:

  • 为什么要将此 SQL 查询重写为 JPQL / Criteria Query?你不能继续使用 SQL,因为它更容易工作吗?
  • 因为我们当前的平台已经在使用 CriteriaQuery 来设置数据库查询的过滤器,以及对数据库进行实际的查询,所以还必须使用 CriteriaQuery 实现排序
  • 如果您绝对必须这样做,您仍然可以在数据库中使用视图。标准查询不会注意到。但我会真诚地重新考虑将相对有限的 API 用于everything。有一些选项,你知道 :) 想象一下用这个 API 计算一个运行总数......

标签: java jpa criteria-api


【解决方案1】:

我的 CriteriaQuery-fu 不足以帮助您完成这部分,但您可以将您的 SQL 查询重写为:

SELECT s.id
FROM shipment s
LEFT JOIN status_transition_message stm 
ON stm.initial_status = 1 AND stm.final_status = 3 AND stm.shipment_id = s.id
GROUP BY s.id
ORDER BY min(stm.date) DESC;

对我来说,这似乎是一个比在 ORDER BY 子句中运行相关子查询更快的解决方案,尤其是在优化器不太复杂的 RDBMS 上。

【讨论】:

  • 是的,我也想到了,但是 CriteriaQuery / JPQL 也不支持 JOIN 子句的子查询。它只在 WHERE 子句中支持它们,在其他地方没有:/
  • @TiagoSilva:我明确没有加入派生表,而是使用了GROUP BY
  • 是的,我认为您的解决方案可能有效。检查我的新问题编辑
  • @TiagoSilva:太好了!您知道您可以在 Stack Overflow 上添加您自己的答案,而不是编辑您的问题。将来的读者会更容易理解问题的流程。
  • 是的,但问题是 CriteriaQuery 不会在 SELECT 语句中保留别名列。它只是单独使用min(stm.date)。我通过从查询中删除 DISTINCT 关键字解决了这个问题,现在它工作得很好!
【解决方案2】:

所以我尝试遵循@Lukas Eder 解决方案并达到了这个解决方案:

private void sortByAuthDate(Root<ShipmentTbl> root, CriteriaQuery<?> query, CriteriaBuilder builder, ShipmentListSort sort) {
        Join<ShipmentTbl, StatusTransitionMessageTbl> shipmentStatuses = root.join("shipmentStatus", JoinType.LEFT);

        Predicate initialStatus = builder.equal(shipmentStatuses.<Integer>get("initialStatus"), 1);
        Predicate finalStatus = builder.equal(shipmentStatuses.<Integer>get("finalStatus"), 3);

        Expression<Timestamp> authDate = builder.least(shipmentStatuses.<Timestamp>get("date"));
        Order o = sort.getSortDirection() == ShipmentListSort.SortDirection.ASC ? builder.asc(authDate) : builder.desc(authDate);

        shipmentStatuses.on(builder.and(initialStatus, finalStatus));

        query.multiselect(authDate).groupBy(root.<String>get("id")).orderBy(o);
    }
}

但现在它抛出了这个异常:

ERROR o.h.e.jdbc.spi.SqlExceptionHelper - ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list

发生这种情况是因为查询稍后只会获得不同的货物,并且它要求排序列也出现在选择中。问题是我不知道如何强制 CriteriaQuery 将该列保留在 SELECT 语句中。它只会自动放入 ORDER BY。

这是它在我的测试中执行的 JPQL 查询:

select
        distinct generatedAlias0 
    from
        ShipmentTbl as generatedAlias0 
    left join
        generatedAlias0.shipmentStatus as generatedAlias1 with ( generatedAlias1.initialStatus=:param0 ) 
        and (
            generatedAlias1.finalStatus=:param1 
        ) 
    where
        lower(generatedAlias0.shipmentName) like :param2 
    group by
        generatedAlias0.id 
    order by
        min(generatedAlias1.date) desc

以及生成的 SQL 查询:

select
            distinct shipmenttb0_.id as id1_13_,
            shipmenttb0_.archived_date as archived2_13_,
            shipmenttb0_.auth_code as auth_cod3_13_,
            shipmenttb0_.authorization_date as authoriz4_13_,
            shipmenttb0_.booked_in_by_user as booked_i5_13_,
            shipmenttb0_.business_channel as business6_13_,
            shipmenttb0_.courier as courier7_13_,
            shipmenttb0_.courier_amount as courier_8_13_,
            shipmenttb0_.courier_currency as courier_9_13_,
            shipmenttb0_.ship_to as ship_to39_13_,
            shipmenttb0_.estimated_shipment_date as estimat10_13_,
            shipmenttb0_.last_updated_date as last_up11_13_,
            shipmenttb0_.measurement_unit as measure12_13_,
            shipmenttb0_.original_submitted_date as origina13_13_,
            shipmenttb0_.packaging_type as packagi14_13_,
            shipmenttb0_.placeholder_message as placeho15_13_,
            shipmenttb0_.scheduled_period_of_day as schedul16_13_,
            shipmenttb0_.scheduled_shipment_date as schedul17_13_,
            shipmenttb0_.ship_from as ship_fr40_13_,
            shipmenttb0_.ship_origin as ship_or41_13_,
            shipmenttb0_.shipment_name as shipmen18_13_,
            shipmenttb0_.status as status19_13_,
            shipmenttb0_.submitted_date as submitt20_13_,
            shipmenttb0_.supplier_contact_email as supplie21_13_,
            shipmenttb0_.supplier_contact_name as supplie22_13_,
            shipmenttb0_.supplier_contact_phone_number as supplie23_13_,
            shipmenttb0_.supplier_email as supplie24_13_,
            shipmenttb0_.supplier_secondary_contact_email as supplie25_13_,
            shipmenttb0_.supplier_secondary_contact_name as supplie26_13_,
            shipmenttb0_.supplier_secondary_contact_phone_number as supplie27_13_,
            shipmenttb0_.tenant as tenant28_13_,
            shipmenttb0_.total_received_boxes as total_r29_13_,
            shipmenttb0_.total_units as total_u30_13_,
            shipmenttb0_.total_value as total_v31_13_,
            shipmenttb0_.total_volume as total_v32_13_,
            shipmenttb0_.total_weight as total_w33_13_,
            shipmenttb0_.tracking_number as trackin34_13_,
            shipmenttb0_.tt_note as tt_note35_13_,
            shipmenttb0_.tt_priority as tt_prio36_13_,
            shipmenttb0_.updated_by_user as updated37_13_,
            shipmenttb0_.weight_unit as weight_38_13_ 
        from
            shipment shipmenttb0_ 
        left outer join
            status_transition_message shipmentst1_ 
                on shipmenttb0_.id=shipmentst1_.shipment_id 
                and (
                    shipmentst1_.initial_status=? 
                    and shipmentst1_.final_status=?
                ) 
        where
            lower(shipmenttb0_.shipment_name) like ? 
        group by
            shipmenttb0_.id 
        order by
            min(shipmentst1_.date) desc limit ?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-07
    • 2012-05-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多