如何在JPQL的ORDER BY子句中重写子查询

时间:2019-02-14 19:25:14

标签: java jpa jpql criteria-api

我正在尝试使用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生成的JPQL查询不支持ORDER BY子句中的子查询,从而导致解析异常。

2 个答案:

答案 0 :(得分: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上。

答案 1 :(得分:0)

所以我尝试遵循@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 ?