假设我有一个会议实体。每次会议都有一位与会者和一个会面日期。在我的会议桌内,我可能会为每位与会者举行多次会议,每次会议的日期各不相同。我需要一个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对象来映射到视图我就可以了。
答案 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)
作为Bulba has said,适当的方法是使用group by来加入子查询。
问题是您无法加入子查询。
这是一种解决方法。
让我们看一下使用group by在子查询中得到的内容。您将获得对(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)
请注意,每个查询只有一个子查询,而不是像某些answers中每行的一个子查询。
我们为您提供特别优惠!
让我们将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)
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}})个查询进行了基准测试:
相关子查询
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
通过
加入子查询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
使用对(attendee_id, date)
作为键
Concat attendee_id
和meeting_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_id
和meeting_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
这是一个带有表格方案的EXPLAIN ANALYZE,表格数据(作为csv),用于填充表格的代码和查询。
答案 4 :(得分:-4)
试试这个
SELECT MAX(m.MeetingDate) FROM Meeting m