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

假设我有一个会议实体。 每次会议都有一位与会者和一个会面日期。 在我的会议表中,每个与会者可能会有多个会议,每个会议的日期各不相同。 我需要一个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(等等)并且可能加入到我自己,但我不确定最好的方法来解决这个问题。

更新:在晚上玩这个之后,我仍然没有可接受的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对象来映射到视图我就可以了。

在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解决方案,请让我们都知道!

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

 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) 

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

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

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

普通的SQL

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

JPA,JPQL

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

这是一个解决方法。

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

为简单起见,我们将此id-pair表示为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) 

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

害怕比较字符串?

我们为您提供特别优惠!

让我们将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,25 -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]的随机日期。 我使用以下技术对(使用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); 

    执行时间:873260.878毫秒

  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; 

    执行时间: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); 

      执行时间: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); 

      执行时间:127.595毫秒

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

尝试这个

 SELECT MAX(m.MeetingDate) FROM Meeting m