Как выбрать последнюю запись в группе с помощью JPQL в Spring JpaRepository?

В микросервисе SpringBoot я пытаюсь выбрать последнюю запись для актера для каждого mean_of_payment_id. Для этого выберите содержимое актора для act_id, где created_date равно подмножеству вложенного запроса max(created_date) с использованием предложения group by для mean_of_payment_id. Я использую 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.


person ajkush    schedule 10.10.2018    source источник
comment
почему вы ссылаетесь на ac.actor.uuid и ac.uuid во внутреннем запросе? Недостаточно просто ВЫБРАТЬ MAX(aci.createdDate) FROM ActorContent aci WHERE aci.actor.uuid=:actorUuid group by ac.meanOfPayment.id)   -  person Zeromus    schedule 11.10.2018
comment
@Zeromus, ты Мужчина. Я изменил подзапрос и получил идеальный результат, но у меня есть одно сомнение: использование подзапроса — это хороший способ или я могу масштабировать этот запрос другим способом, какие-нибудь мысли?   -  person ajkush    schedule 11.10.2018
comment
Это достаточно распространенная проблема, чтобы иметь тег только для нее [наибольшее-n-на-группу]. Лично я просто использую подзапрос в той же ситуации, но могут быть и лучшие альтернативы.   -  person Zeromus    schedule 11.10.2018
comment
Здесь аналогичный вопрос stackoverflow.com/questions/7745609/   -  person Zeromus    schedule 11.10.2018
comment
Спасибо за ваш ценный ответ и обратную связь. У меня правильные результаты, но я чувствую, что может быть более масштабируемое решение. Пожалуйста, не стесняйтесь посмотреть.   -  person ajkush    schedule 07.11.2018


Ответы (1)


Я думаю, что с точки зрения реляционной алгебры вы запрашиваете набор ActorContent минус набор ActorContent, ограниченный действующим лицом = действующим лицом и средним значением платежа = средним значением платежа и createDate ‹ 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», а также второго участника и его самые последние платежи для всех трех значений «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]

После этого вы можете оптимизировать запрос, объединив выборку экземпляров 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-запросу:

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 для комбинации Актера и MeanOfPayment, вы можете решить ее несколькими способами. Во-первых, если у вас есть логическое ограничение, из-за которого вы не хотите обрабатывать эти дубликаты, вам, вероятно, следует также иметь ограничение базы данных, чтобы вы не получали их и не создавали их в первую очередь. Другое дело, что вы можете вручную проверить список результатов и удалить их. Наконец, вы можете использовать в своем запросе отдельный идентификатор, но вы должны пропустить поле ActorContent id, поскольку оно не будет уникальным. Вы можете сделать это с помощью DTO, но JPA не может обрабатывать проекцию и join fetch одновременно, поэтому вы будете получать только act.id и meanOfPayment.id или будете делать множественный выбор. Множественный выбор, вероятно, не убивает сделку в этом случае использования, но вы должны решить все это сами. Конечно, вы также можете сделать первичный ключ ActorContent комбинацией act.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;
person K.Nicholas    schedule 24.10.2018
comment
Спасибо @K.Nicholas, решение, которое вы предложили, дало мне именно тот результат, который я искал. Что вы думаете о производительности? Предположим, у нас есть 5000 записей, чтобы выбрать только три записи, мы делаем что-то вроде 5000 - 4777. (не в (выберите ac1.id из ActorContent ac1, ActorContent ac2, где ac1.meanOfPayment = ac2.meanOfPayment и ac1.actor = ac2.actor и ac1.createDate ‹ ac2.createDate) - person ajkush; 25.10.2018
comment
Для повышения производительности лучше всего понимать и проверять инструменты анализа запросов для любой платформы, которую вы используете. Также может быть важно убедиться, что у вас достаточно параметров buffer_pool_size и памяти, чтобы сервер sql мог эффективно выполнять свою работу. - person K.Nicholas; 25.10.2018
comment
Вы также можете добавить and ac1.actor.id = :actorId and ac2.actor.id = :actorId к подзапросу, чтобы ограничить количество строк, но в любом случае это может не иметь значения. Опять же, если вы можете проанализировать производительность с сервером. - person K.Nicholas; 25.10.2018