Получение последних записей в запросе
У меня есть следующие таблицы:
tblPerson:
PersonID | Name
---------------------
1 | John Smith
2 | Jane Doe
3 | David Hoshi
tblLocation:
LocationID | Timestamp | PersonID | X | Y | Z | More Columns...
---------------------------------------------------------------
40 | Jan. 1st | 3 | 0 | 0 | 0 | More Info...
41 | Jan. 2nd | 1 | 1 | 1 | 0 | More Info...
42 | Jan. 2nd | 3 | 2 | 2 | 2 | More Info...
43 | Jan. 3rd | 3 | 4 | 4 | 4 | More Info...
44 | Jan. 5th | 2 | 0 | 0 | 0 | More Info...
Я могу создать SQL-запрос, который получает записи местоположения для каждого Лица, например:
SELECT LocationID, Timestamp, Name, X, Y, Z
FROM tblLocation
JOIN tblPerson
ON tblLocation.PersonID = tblPerson.PersonID;
чтобы создать следующее:
LocationID | Timestamp | Name | X | Y | Z |
--------------------------------------------------
40 | Jan. 1st | David Hoshi | 0 | 0 | 0 |
41 | Jan. 2nd | John Smith | 1 | 1 | 0 |
42 | Jan. 2nd | David Hoshi | 2 | 2 | 2 |
43 | Jan. 3rd | David Hoshi | 4 | 4 | 4 |
44 | Jan. 5th | Jane Doe | 0 | 0 | 0 |
Моя проблема в том, что нас беспокоит только самая последняя запись местоположения. Таким образом, нас действительно интересуют только следующие строки: LocationID 41, 43 и 44.
Вопрос: как мы можем запросить эти таблицы, чтобы предоставить нам самые последние данные для каждого человека? Какая специальная группировка должна произойти для получения желаемого результата?
Ответы
Ответ 1
MySQL не имеет функций ранжирования/аналитики/окон.
SELECT tl.locationid, tl.timestamp, tp.name, X, Y, Z
FROM tblPerson tp
JOIN tblLocation tl ON tl.personid = tp.personid
JOIN (SELECT t.personid,
MAX(t.timestamp) AS max_date
FROM tblLocation t
GROUP BY t.personid) x ON x.personid = tl.personid
AND x.max_date = tl.timestamp
SQL Server 2005+ и Oracle 9i + поддерживают аналитику, поэтому вы можете использовать:
SELECT x.locationid, x.timestamp, x.name, x.X, x.Y, x.Z
FROM (SELECT tl.locationid, tl.timestamp, tp.name, X, Y, Z,
ROW_NUMBER() OVER (PARTITION BY tp.name ORDER BY tl.timestamp DESC) AS rank
FROM tblPerson tp
JOIN tblLocation tl ON tl.personid = tp.personid) x
WHERE x.rank = 1
Использование переменной для получения того же, что и функция ROW_NUMBER в MySQL:
SELECT x.locationid, x.timestamp, x.name, x.X, x.Y, x.Z
FROM (SELECT tl.locationid, tl.timestamp, tp.name, X, Y, Z,
CASE
WHEN @name != t.name THEN
@rownum := 1
ELSE @rownum := @rownum + 1
END AS rank,
@name := tp.name
FROM tblLocation tl
JOIN tblPerson tp ON tp.personid = tl.personid
JOIN (SELECT @rownum := NULL, @name := '') r
ORDER BY tp.name, tl.timestamp DESC) x
WHERE x.rank = 1
Ответ 2
Это классический вопрос "максимум на группу", который появляется на переполнении стека почти каждый день. Существует множество способов решить эту проблему, и вы можете найти примеры решений поиск. Вот один из способов сделать это в MySQL:
SELECT
location.LocationId,
location.Timestamp,
person.Name,
location.X,
location.Y,
location.Z
FROM (
SELECT
LocationID,
@rn := CASE WHEN @prev_PersonID = PersonID
THEN @rn + 1
ELSE 1
END AS rn,
@prev_PersonID := PersonID
FROM (SELECT @prev_PersonID := NULL) vars, tblLocation
ORDER BY PersonID, Timestamp DESC
) T1
JOIN tblLocation location ON location.LocationID = T1.LocationId
JOIN tblPerson person ON person.PersonID = location.PersonID
WHERE rn = 1
Ответ 3
Как отмечает @Mark Byers, эта проблема часто возникает при переполнении стека.
Здесь решение, которое я чаще всего рекомендую, учитывая ваши таблицы:
SELECT p.*, l1.*
FROM tblPerson p
JOIN tblLocation l1 ON p.PersonID = l1.PersonID
LEFT OUTER JOIN tblLocation l2 ON p.PersonID = l2.PersonID AND
(l1.timestamp < l2.timestamp OR l1.timestamp = l2.timestamp AND l1.LocationId < l2.LocationId)
WHERE l2.LocationID IS NULL;
Чтобы увидеть другие примеры, следуйте тегу greatest-n-per-group
, который я добавил к вашему вопросу.