JPA 为每个项目选择最新实例
Posted
技术标签:
【中文标题】JPA 为每个项目选择最新实例【英文标题】:JPA Select latest instance for each item 【发布时间】:2011-09-11 21:16:18 【问题描述】:假设我有一个会议实体。每个会议都有一个与会者和一个会议日期。在我的会议桌中,我可能为每个与会者安排多个会议,每个会议的日期不同。我需要一个 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
对 postgres 使用 JPA 2。会议有 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 对象以映射到视图,我可以这样做。
【问题讨论】:
您可能需要添加sql
标签。
不是sql,是jpql。
好吧,也许有 SQL 知识的人也会有所帮助。下面是相同的螺母和螺栓。而且可能有很多人在看SQL标签。
是的,我明白你的意思。我不想误导人们认为我在寻找 sql,因为两者之间存在足够的差异,以至于 sql 的答案可能没有那么有用。
【参考方案1】:
在 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 解决方案,请告诉我们!
【讨论】:
【参考方案2】:我想我已经掌握了这个查询。
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)
【讨论】:
我了解半联接,但反联接是什么? 好问题。 5 年过去了,现在看起来肯定是多余的,不是吗。不确定我是否有那个来源方便弄清楚我为什么需要它。【参考方案3】:我认为在 SQL 中这很简单,所以我假设它可以映射到 JPA:
SELECT m.AttendeeId, MAX(m.MeetingDate) from Meeting m GROUP BY m.AttendeeId
编辑:如果您也需要 messageId 本身,您可以使用一个简单的子查询来执行此操作,该子查询返回其他两个值相等的消息的 messageId。只需确保您处理相同的参加者和日期有多个 messageId 的情况(例如,选择第一个结果,因为它们都应该同样好 - 尽管我怀疑这些数据是否对会议有意义)
【讨论】:
是的,我就是这么想的。在 JPQL 中,我将选择会议实体,对我(不是 JPQL 专家)来说,max(m.meetingDate) 逻辑必须在 where 子句中或以某种方式加入。是“不知何故”让我感到震惊。 看起来完全正确(除了在 JPQL 中,您可能会查询 m.attendee,而不是 m.attendeeId) @digitaljoel 您似乎认为您不能在 SELECT 之后立即拥有 MAX()。我不是 100% 确定,但我认为它是有效的。 我认为“会议”是包含您在第一个表格中的帖子中看到的所有信息的表格的名称。 MeetingID 是它的主键。我不明白为什么这不起作用,因为 JPA 确实有一个 group by 语句(我的快速谷歌搜索) - 这也意味着 MAX() 更好地被允许在选择部分(否则 group by 完全没用) 是的,我可以在选择后立即获得 Max,但我并不想选择最大值。我想选择会议实体,由于其关联,它将包含与会者和会议日期。这就是为什么我正在寻找一个可以简单地过滤掉其他条目而不是选择单个字段的子句。像“由 m.attendee 从具有 max(m.meetingDate) 的会议 m 组中选择 m”之类的东西,当我回到家时,我必须尝试一下,以确保每个与会者的最大会议日期,而不是绝对的最大会议日期。【参考方案4】:普通 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_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 对编码为数字。
它将是attendee_id
和meeting_date
的总和,但会进行修改以确保代码的唯一性。我们可以将日期的数字表示为 Unix 时间。
我们将修复我们的代码可以捕获的最大日期的值,因为最终代码具有最大值限制(例如 bigint(int8)63)。为方便起见,我们将最大日期设为 2149-06-07 03:00:00。它等于 5662310400 秒和 65536 天。
我将在这里假设我们需要以天为单位的日期精度(因此我们忽略小时及以下)。
为了构造唯一代码,我们可以将其解释为以 65536 为底的数字系统中的数字。在这种数字系统中或代码中的最后一个符号(从 0 到 216-1 的数字)是数字天。其他符号将捕获attendee_id
。在这样的解释中,代码看起来像XXXX
,其中每个 X 在 [0,216-1] 范围内(更准确地说,第一个 X 在 [0,215 -1] 因为1位符号),前三个X代表attendee_id
,最后一个X代表meeting_date
。
所以我们的代码可以捕获的attendee_id
的最大值是247-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)查询进行了基准测试:
相关子查询
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 毫秒
通过分组加入子查询
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 毫秒
使用对 (attendee_id, date)
作为键
将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 毫秒
将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 毫秒
这是一个git,其中包含表格方案、表格数据(作为 csv)、用于填充表格的代码和查询。
【讨论】:
【参考方案5】:试试这个
SELECT MAX(m.MeetingDate) FROM Meeting m
【讨论】:
这将为我提供所有与会者中最近的会议。我需要每位与会者的最新会议。以上是关于JPA 为每个项目选择最新实例的主要内容,如果未能解决你的问题,请参考以下文章