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

时间:2011-06-09 16:45:43

标签: java sql jpa jpa-2.0 jpql

假设我有一个会议实体。每次会议都有一位与会者和一个会面日期。在我的会议桌内,我可能会为每位与会者举行多次会议,每次会议的日期各不相同。我需要一个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

我的结果应该是

Meeting ID | Attendee ID | Meeting Date
2          | 2           |  6/1/2011
3          | 1           |  6/6/2011
4          | 3           |  6/6/2011

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

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

select m from Meeting m 
where m.meetingDate in 
    ( select max(meet.meetingDate) 
      from Meeting meet group by meet.attendee )

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

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

5 个答案:

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

这很有效,而且效果很快!

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

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

CREATE VIEW

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

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

答案 1 :(得分: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)

答案 2 :(得分:8)

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

SELECT m.AttendeeId, MAX(m.MeetingDate) from Meeting m GROUP BY m.AttendeeId

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

答案 3 :(得分:5)

普通SQL

作为Bulba has said,适当的方法是使用group by来加入子查询。

JPA,JPQL

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

这是一种解决方法。

让我们看一下使用group by在子查询中得到的内容。您将获得对(attendee_id, max(meeting_date))对的列表。 这一对就像是一个新的唯一ID,用于您希望加入的最大日期。 然后请注意,表格中的每一行都形成一对(attendee_id, meeting_date)。 因此,每一行都有一对id (attendee_id, meeting_date)。 如果它只形成属于子查询中收到的列表的id,则让我们连续。

为简单起见,我们将此ID对表示为attendee_idmeeting_dateconcat(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)

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

害怕比较字符串?

我们为您提供特别优惠!

让我们将id-pair编码为数字。 它将是attendee_idmeeting_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)

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);

基准

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

  1. 相关子查询

    SELECT * FROM meetings m1 WHERE m1.meeting_date=
    (SELECT max(m2.meeting_date) FROM meetings m2 WHERE m2.attendee_id=m1.attendee_id);
    

    执行时间:873260.878 ms

  2. 通过

    加入子查询
    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>
    

    执行时间:103.427 ms

  3. 使用对(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);
      

      执行时间:207.720 ms

    • 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);
      

      执行时间:127.595 ms

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

答案 4 :(得分:-4)

试试这个

SELECT MAX(m.MeetingDate) FROM Meeting m