普通 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)、用于填充表格的代码和查询。