如何在 Spring JpaRepository 中使用 JPQL 选择组中的最新记录?

Posted

技术标签:

【中文标题】如何在 Spring JpaRepository 中使用 JPQL 选择组中的最新记录?【英文标题】:How to select latest record in group using JPQL in Spring JpaRepository? 【发布时间】:2019-03-15 14:46:09 【问题描述】:

在 SpringBoot 微服务中,我试图为每个 mean_of_payment_id 选择演员的最新记录。为此,使用 mean_of_payment_id 上的 group by 子句为 actor_id 选择演员内容,其中 created_date 等于 max(created_date) 的嵌套查询的子集。我正在使用 JPQL。下面是表结构和查询。

    @Query("select ac from ActorContent ac "
        + "where (ac.actor.uuid=:actorUuid ) and "
        + "ac.createdDate IN ( SELECT MAX(aci.createdDate) "
            + "FROM ActorContent aci WHERE ac.actor.uuid=aci.actor.uuid "
            + "and aci.uuid = ac.uuid group by ac.meanOfPayment.id)"
        )

不幸的是,执行查询后,我得到了所有记录,但我期望的是前三行。 MeanOfPayment 和 Actor 是 ActorContent 的参考表。

【问题讨论】:

为什么在内部查询中引用 ac.actor.uuid 和 ac.uuid?仅 SELECT MAX(aci.createdDate) FROM ActorContent aci WHERE aci.actor.uuid=:actorUuid group by ac.meanOfPayment.id) 应该不够” @Zeromus,你就是男人。我更改了子查询并得到了完美的结果,但我有一个疑问,使用子查询是一种好方法还是我可以用另一种方式扩展这个查询,有什么想法吗? 这是一个很常见的问题,需要为它设置一个标签 [greatest-n-per-group]。我个人只是在相同情况下使用子查询,但可能有更好的选择 这里有一个类似的问题***.com/questions/7745609/… 感谢您的宝贵回复和反馈。我得到了正确的结果,但我觉得可能会有更多可扩展的解决方案。请随意看看。 【参考方案1】:

我认为就关系代数而言,您要求的是ActorContent 的集合减去ActorContent 的集合,受actor = actor 和 meanOfPayment = meanOfPayment 和 createDate ActorContent 与ac1.meanOfPayment = ac2.meanOfPayment and ac1.actor = ac2.actor and ac1.createDate < ac2.createDate 的叉积中得到第二组。然后从ActorContent 的集合中减去这个集合。我还没有看它是否比使用MAXGroup By更有效,例如:

@Query("select ac from ActorContent ac where ac.id not in (select ac1.id from ActorContent ac1, ActorContent ac2 where ac1.meanOfPayment = ac2.meanOfPayment and ac1.actor = ac2.actor and ac1.createDate < ac2.createDate)")

这给了我 UPPER 表中的前四行,代表第一个演员和他唯一的 meanOfPayment 以及第二个演员和他最近对所有三个 meanOfPayment 的付款。

ActorContent [id=1, actor=Actor [id=1], meanOfPayment=MeanOfPayment [id=1], amount=10500.00, createDate=2018-10-09 00:00:00.887]
ActorContent [id=2, actor=Actor [id=2], meanOfPayment=MeanOfPayment [id=1], amount=-10400.00, createDate=2018-10-02 00:00:00.887]
ActorContent [id=3, actor=Actor [id=2], meanOfPayment=MeanOfPayment [id=3], amount=6000.00, createDate=2018-10-02 00:00:00.887]
ActorContent [id=4, actor=Actor [id=2], meanOfPayment=MeanOfPayment [id=2], amount=200.00, createDate=2018-09-30 00:00:00.887]

之后,您可能希望通过连接获取ActorMeanOfPayment 实例来优化查询。举例:

@Query("select ac from ActorContent ac left outer join fetch ac.actor left outer join fetch ac.meanOfPayment where ac.id not in (select ac1.id from ActorContent ac1, ActorContent ac2 where ac1.meanOfPayment = ac2.meanOfPayment and ac1.actor = ac2.actor and ac1.createDate < ac2.createDate)")

这将导致以下休眠生成的 SQL 查询:

select actorconte0_.id as id1_1_0_, actor1_.id as id1_0_1_, meanofpaym2_.id as id1_2_2_, actorconte0_.actor_id as actor_id4_1_0_, actorconte0_.amount as amount2_1_0_, actorconte0_.create_date as create_d3_1_0_, actorconte0_.mean_of_payment_id as mean_of_5_1_0_ from actor_content actorconte0_ left outer join actor actor1_ on actorconte0_.actor_id=actor1_.id left outer join mean_of_payment meanofpaym2_ on actorconte0_.mean_of_payment_id=meanofpaym2_.id where actorconte0_.id not in  (select actorconte3_.id from actor_content actorconte3_ cross join actor_content actorconte4_ where actorconte3_.mean_of_payment_id=actorconte4_.mean_of_payment_id and actorconte3_.actor_id=actorconte4_.actor_id and actorconte3_.create_date<actorconte4_.create_date)

当然,如果您想要一个特定的 Actor,那么只需将其添加到 where 子句即可。

@Query("select ac from ActorContent ac left outer join fetch ac.actor left outer join fetch ac.meanOfPayment where ac.actor.id = :actorId and ac.id not in (select ac1.id from ActorContent ac1, ActorContent ac2 where ac1.meanOfPayment = ac2.meanOfPayment and ac1.actor = ac2.actor and ac1.createDate < ac2.createDate)")
public List<ActorContent> findLatestForActor(@Param("actorId") Integer actorId);

这给了我“前三行”

ActorContent [id=2, actor=Actor [id=2], meanOfPayment=MeanOfPayment [id=1], amount=-10400.00, createDate=2018-10-02 00:00:00.066]
ActorContent [id=3, actor=Actor [id=2], meanOfPayment=MeanOfPayment [id=3], amount=6000.00, createDate=2018-10-02 00:00:00.066]
ActorContent [id=4, actor=Actor [id=2], meanOfPayment=MeanOfPayment [id=2], amount=200.00, createDate=2018-09-30 00:00:00.066]

如果您对 Actor 和 MeanOfPayment 组合具有相同的 createDate 有疑问,那么您可以通过几种不同的方式进行处理。首先,如果您有一个逻辑约束,以至于您不想处理这些重复项,那么您可能也应该有一个数据库约束,这样您就不会得到它们,并确保您不会首先创建它们。另一件事是您可以手动检查结果列表并将其删除。最后,您可以在查询中使用 distinct,但您必须省略 ActorContent id 字段,因为它不是唯一的。您可以使用 DTO 执行此操作,但 JPA 无法同时处理投影和 join fetch,因此您只会获得 actor.id 和 meanOfPayment.id ,否则您将进行多项选择。在这个用例中,多选可能不是交易杀手,但您必须自己决定所有这些。当然,您也可以将 ActorContent 的主键设置为 actor.id、meanOfPayment.id 和 createDate 的组合,这将具有成为上述约束的额外好处。

这些是我合作过的Entities

@Entity
public class Actor 
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;

@Entity
public class MeanOfPayment 
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;

@Entity
public class ActorContent 
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;

    @ManyToOne
    private Actor actor;
    @ManyToOne
    private MeanOfPayment meanOfPayment;

    private BigDecimal amount;
    @Temporal(TemporalType.TIMESTAMP)
    private Date createDate;

【讨论】:

谢谢@K.Nicholas,您提出的解决方案为我提供了我正在寻找的确切结果。关于性能,你怎么看?假设我们有 5000 条记录只选择三个记录,我们正在执行类似 5000 - 4777 的操作。(不在 (select ac1.id from ActorContent ac1, ActorContent ac2 where ac1.meanOfPayment = ac2.meanOfPayment and ac1.actor = ac2.actor和 ac1.createDate 为了提高性能,最好了解和检查您使用的任何平台的查询分析工具。确保你有足够的 buffer_pool_size 和内存设置也很重要,这样 sql server 才能有效地工作。 您也可以将and ac1.actor.id = :actorId and ac2.actor.id = :actorId 添加到子查询中以限制那里的行,但这可能并不重要。同样,如果您可以使用服务器分析性能。

以上是关于如何在 Spring JpaRepository 中使用 JPQL 选择组中的最新记录?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 JpaRepository spring boot 中使用 postgresql array_agg 函数?

如何使用 Spring Boot JpaRepository 保存多个表

在 spring jparepository 中加入多个表

如何在 Spring JpaRepository 中使用 JPQL 选择组中的最新记录?

如何将对象从一个 JpaRepository 转换为另一个 JpaRepository

在测试中使用 Spring JpaRepository