JPA为每个项目选择最新实例

dig*_*oel 22 java sql jpa jpql jpa-2.0

假设我有一个会议实体.每次会议都有一位与会者和一个会面日期.在我的会议桌内,我可能会为每位与会者举行多次会议,每次会议的日期各不相同.我需要一个JPA查询,它只为所有与会者选择最新的会议.例如,如果我的表看起来像这样

Meeting ID | Attendee ID | Meeting Date
1          | 1           |  6/1/2011
2          | 2           |  6/1/2011
3          | 1           |  6/6/2011
4          | 3           |  6/6/2011
Run Code Online (Sandbox Code Playgroud)

我的结果应该是

Meeting ID | Attendee ID | Meeting Date
2          | 2           |  6/1/2011
3          | 1           |  6/6/2011
4          | 3           |  6/6/2011
Run Code Online (Sandbox Code Playgroud)

使用JPA 2对抗postgres.会议有1-1参加者和一个简单的时间戳日期.我怀疑我需要做一个小组by和max(blah),也许是一个加入我自己,但我不确定最好的方法来解决这个问题.

更新: 在晚上玩这个之后,我仍然没有可接受的JPQL解决方案.这是我到目前为止:

select m from Meeting m 
where m.meetingDate in 
    ( select max(meet.meetingDate) 
      from Meeting meet group by meet.attendee )
Run Code Online (Sandbox Code Playgroud)

我有其他与此问题无关的其他条件,例如由与会部门过滤等等.这有效的唯一原因是因为我们将会议日期跟踪到第二(或更精细),并且在同一时间召开两次会议的可能性很小.我们正在为它们添加一些Java内容,以便只为每个与会者提供最后一次会议,以防我们同时获得两个会议,但这是一个非常糟糕的解决方案.在查询中获取所有内容真的不应该太难,但我还没弄明白.

Update2:添加sql标签,因为如果我需要使用sql创建一个视图并创建一个JPA对象来映射到视图我就可以了.

小智 15

在SQL中,解决方案非常简单 - 将表与子查询连接起来,为每个与会者提供最近的会议:

select * from Meeting ALL
join ( select max(meetingDate) as newest, attendee
from Meeting group by attendee ) LATEST
on ALL.meetingDate = LATEST.newest AND ALL.attendee = LATEST.attendee
Run Code Online (Sandbox Code Playgroud)

这工作,并且工作得很快!

JPA的问题在于它(或大多数实现)不允许连接的子查询.花了几个小时尝试了首先编译的东西,然后,它有多慢,我决定我讨厌JPA.像上面那样的解决方案 - 比如EXISTS(SELECT ..)或IN(SELECT ..) - 需要花费很长时间才能执行,比它们要慢几个数量级.

拥有一个有效的解决方案意味着我只需要从JPA访问该解决方案.SQL中有两个神奇的单词可以帮助您做到这一点:

CREATE VIEW
Run Code Online (Sandbox Code Playgroud)

生活变得如此简单......只需定义这样的实体并使用它.注意:它是只读的.

当然,任何JPA纯粹主义者都会在你这样做时瞧不起你,所以如果有人有纯粹的JPA解决方案,请让我们都知道!


dig*_*oel 14

我想我已经得到了这个查询.

select m from Meeting m 
    where m.meetingDate = 
        (select max(m1.meetingDate) 
            from Meeting m1 
            where m1.attendee = m.attendee )
    and not exists 
        (select m2 from Meeting m2 
            where m2.attendee = m.attendee 
            and m2.meetingDate > m.meetingDate)
Run Code Online (Sandbox Code Playgroud)

  • 好问题.5年过去了,现在肯定看起来多余了.不知道我有这个来源了解我为什么需要它. (3认同)

Voo*_*Voo 8

在SQL中,我认为这很简单,所以我假设可以映射到JPA:

SELECT m.AttendeeId, MAX(m.MeetingDate) from Meeting m GROUP BY m.AttendeeId
Run Code Online (Sandbox Code Playgroud)

编辑:如果您还需要messageId本身,您可以使用一个简单的子查询来执行此操作,该子查询返回其他两个值相等的消息的messageId.只要确保你处理同一个参与者和日期有多个messageId的情况(例如选择第一个结果,因为他们都应该同样好 - 尽管我怀疑这些数据甚至对会议有意义)


dim*_*e47 7

普通的SQL

正如Bulba所说,适当的方式是加入子查询与group by.

JPA,JPQL

问题是您无法加入子查询.

这是一个解决方法.

让我们看看你在子查询中得到的是什么.你得到一对配对清单(attendee_id, max(meeting_date)).这一对就像是一个新的唯一ID,用于您希望加入的最大日期.然后请注意表中的每一行都形成一对(attendee_id, meeting_date).所以每一行都有一对id (attendee_id, meeting_date).如果只有它形成属于子查询中收到的列表的id,则让我们连续.

为简单起见,我们将此id对表示为attendee_id和的连接meeting_date:concat(attendee_id, meeting_date).

然后SQL中的查询(类似于JPQL和JPA CriteriaBuilder)将如下所示:

SELECT * FROM meetings 
WHERE concat(attendee_id, meeting_date) IN
(SELECT concat(attendee_id, max(meeting_date)) FROM meetings GROUP BY attendee_id)
Run Code Online (Sandbox Code Playgroud)

请注意,每个查询只有一个子查询,而不是像某些答案中的每行一个子查询.

害怕比较字符串?

我们为您提供特别优惠!

让我们将id-pair编码为数字.这将是的总和attendee_id,并meeting_date而是修改,以确保代码的唯一性.我们可以将日期的数字表示作为Unix时间.我们将修复代码可以捕获的最大日期值,因为最终代码具有最大值限制(例如bigint(int8)<2 63).让我们方便最大日期为2149-06-07 03:00:00.它等于5662310400秒,65536天.我将在这里假设我们需要精确的日期(因此我们忽略小时和以下).为了构造唯一代码,我们可以将其解释为数字系统中的数字,其基数为65536.这样的数字系统中的最后一个符号(数字从0到2 16 -1)或代码是天数.其他符号将被捕获attendee_id.在这样的解释代码看起来XXXX,其中每个X在范围[0,2 16 -1](更准确,第一个X在范围[0,2 15 -1],因为1位用于符号),前三个X代表attendee_id,最后X代表meeting_date.因此attendee_id我们的代码可以捕获的最大值是2 47 -1.代码可以计算为attendee_id*65536 +"以天为单位的日期".

在postgresql中它将是:

attendee_id*65536 + date_part('epoch', meeting_date)/(60*60*24)
Run Code Online (Sandbox Code Playgroud)

date_part在此我们通过把在不断转换为天秒返回的日期.

最后查询以获得所有与会者的最新会议:

SELECT * FROM meetings
WHERE attendee_id*65536 + date_part('epoch', meeting_date)/(60*60*24)
IN (SELECT attendee_id*65536 + date_part('epoch', max(meeting_date))/(60*60*24) from meetings GROUP BY attendee_id);
Run Code Online (Sandbox Code Playgroud)

标杆

我已经在问题中创建了一个带有结构的表,并使用attendee_id从[1,10000]中随机选择的100000行和来自范围[1970-01-01,2017-09-16]的随机日期填充它.我使用以下技术对(使用EXPLAIN ANALYZE)查询进行了基准测试:

  1. 相关子查询

    SELECT * FROM meetings m1 WHERE m1.meeting_date=
    (SELECT max(m2.meeting_date) FROM meetings m2 WHERE m2.attendee_id=m1.attendee_id);
    
    Run Code Online (Sandbox Code Playgroud)

    执行时间:873260.878 ms

  2. 使用group by加入子查询

    SELECT * FROM meetings m
    JOIN (SELECT attendee_id, max(meeting_date) from meetings GROUP BY attendee_id) attendee_max_date
    ON attendee_max_date.attendee_id = m.attendee_id;</code>
    
    Run Code Online (Sandbox Code Playgroud)

    执行时间:103.427毫秒

  3. 使用pair (attendee_id, date)作为键

    • Concat attendee_idmeeting_date作为字符串

      SELECT * FROM meetings WHERE concat(attendee_id, meeting_date) IN
      (SELECT concat(attendee_id, max(meeting_date)) from meetings GROUP BY attendee_id);
      
      Run Code Online (Sandbox Code Playgroud)

      执行时间:207.720毫秒

    • 编码attendee_idmeeting_date单个数字(代码)

      SELECT * FROM meetings
      WHERE attendee_id*65536 + date_part('epoch',meeting_date)/(60*60*24)
      IN (SELECT attendee_id*65536 + date_part('epoch',max(meeting_date))/(60*60*24) from meetings GROUP BY attendee_id);
      
      Run Code Online (Sandbox Code Playgroud)

      执行时间:127.595毫秒

这是一个带有表格方案的git,表格数据(作为csv),用于填充表格的代码和查询.