Как выбрать последнюю запись в группе, используя JPQL в Spring JpaRepository? - PullRequest
0 голосов
/ 10 октября 2018

В микросервисе SpringBoot я пытаюсь выбрать последнюю запись для актера для каждого mean_of_payment_id.Чтобы добиться этого, выбираем содержимое актера для actor_id, где create_date равен подмножеству вложенного запроса max (creation_date), используя выражение group by для mean_of_payment_id.Я использую JPQL.Ниже приведены структура таблицы и запрос.

enter image description here

    @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)"
        )

enter image description here

К сожалению, послевыполняя запрос, я получаю все записи, но ожидаю первых трех строк.MeanOfPayment и Actor являются ссылочными таблицами для ActorContent.

1 Ответ

0 голосов
/ 24 октября 2018

Я думаю, что с точки зрения реляционной алгебры вы запрашиваете набор ActorContent минус набор ActorContent, ограниченный actor = actor и meanOfPayment = meanOfPayment и createDate ActorContent с ac1.meanOfPayment = ac2.meanOfPayment and ac1.actor = ac2.actor and ac1.createDate < ac2.createDate.Затем вычтите этот набор из набора ActorContent.Я не смотрел, чтобы увидеть, является ли это более эффективным, чем использование MAX и Group 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, второй участник и его последние платежи для всех трех meanOfPayments.

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]

После этого вы можете захотеть оптимизировать запрос, объединив выборку экземпляров Actor и MeanOfPayment.К примеру:

@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-запросу hibernate:

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]

Если у вас возникла проблема с одинаковым createDate для комбинации Actor и MeanOfPayment, то вы можете разобраться внесколько разных способов.Во-первых, если у вас есть логическое ограничение, такое, что вы не хотите обрабатывать эти дубликаты, тогда у вас, вероятно, также должны быть ограничения базы данных, чтобы вы их не получали и не гарантировали, что вы их не создадите.Другое дело, что вы можете вручную проверить список результатов и удалить их.Наконец, вы можете использовать отличительное в вашем запросе, но вы должны опустить поле 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;
...