Простой SQL
Как и Бульба сказал соответствующий способ - присоединиться к подзапросу с группой с помощью.
JPA, JPQL
Проблема заключается в том, чточто вы не можете присоединиться к подзапросу.
Вот обходной путь.
Позволяет увидеть, что вы получаете в подзапросе с помощью group by.Вы получите список пар (attendee_id, max(meeting_date))
.Эта пара похожа на новый уникальный идентификатор для строки с максимальной датой, к которой вы хотите присоединиться.Затем обратите внимание, что каждая строка в таблице образует пару (attendee_id, meeting_date)
.Таким образом, каждая строка имеет идентификатор в виде пары (attendee_id, meeting_date)
.Давайте возьмем строку, если только она формирует идентификатор, принадлежащий списку, полученному в подзапросе.
Для простоты давайте представим эту пару идентификаторов в виде объединения 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)
Обратите внимание, что существует только один подзапрос на запрос, а не один подзапрос для каждой строки, как внекоторые ответы .
Боитесь сравнивать строки?
У нас есть специальное предложение для вас!
Позволяет закодировать этот идентификатор-пара в номер.Это будет сумма attendee_id
и meeting_date
, но с изменениями для обеспечения уникальности кода.Мы можем взять числовое представление даты как время Unix.Мы установим значение максимальной даты, которую может записать наш код, поскольку конечный код имеет ограничение максимального значения (например, bigint (int8) <2 <sup>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 *)1047 * -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 строк, произвольно выбирающих attendee_id
из [1, 10000] и случайной даты из диапазона [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)
как ключ
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 мс
Кодирование 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), кодом для заполнения таблицы и запросами.