Эмуляций ROW_NUMBER в MySQL
Статья написана по мотивам этого замечательного топика. Нашел я его гуглем, прочитал, и решения, которое меня бы устраивало, к сожалению, не нашел.
Пришлось думать самому. Обидно было то, что основная моя работа связана Microsoft SQL Server, в котором данная задача решается элементарно c помощью функции ROW_NUMBER.
Задача, которую ставит автор в конце на 99% совпала с моей.
Сформулирую её, в несколько ином виде. Допустим, есть две таблицы: новости и комментарии к ним. Нужно выбрать одним запросом последние двадцать новостей (отсортированые по убыванию) и для каждой новости 10 последних комментариев, отсортированных по возрастанию.
Конечно, до элегантности синтаксиса T-SQL’a данному решению как до луны, но что делать. Буду очень признателен, если кто-то предложит более простое, но не менее быстрое решение.
Итак, код.
Для начала DDL:
CREATE TABLE `post` ( `ID_Post` int(11) NOT NULL AUTO_INCREMENT, `Post` mediumtext, `Date_Creation` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`ID_Post`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `post_comment` ( `ID_Comment` bigint(20) NOT NULL AUTO_INCREMENT, `ID_Post` int(11) NOT NULL, `Comment` mediumtext, `Date_Creation` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`ID_Comment`), KEY `ID_Post` (`ID_Post`), CONSTRAINT `post_comment_fk` FOREIGN KEY (`ID_Post`) REFERENCES `post` (`ID_Post`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
И, собственно, всего один запрос:
SELECT NULL INTO @Last_ID; -- На всякий случай очищаем переменную
SELECT
ID_Post, Post, Date_Creation, ID_Comment, Comment, Comment_Date, rn
FROM (
SELECT
*,
@Reset_Num := CASE -- Признак смены поста
WHEN @Last_ID = ID_Post
THEN 1
ELSE 0
END AS Reset_Num,
-- Идентификатор последнего выбранного поста, ценен тем,
-- что выбирается после расчёта @Reset_Num
@Last_ID := ID_Post,
-- ROW_NUMBER() OVER
-- (PARTITION BY ID_Post ORDER BY c.Date_Creation DESC)
@rn := CASE
WHEN @Reset_Num = 0
THEN 0
ELSE @rn + 1
END AS rn
FROM (
SELECT
pp.ID_Post,
pp.Date_Creation,
pp.Post,
c.ID_Comment,
c.`Comment`,
c.Date_Creation AS Comment_Date
FROM
(
SELECT * FROM post
/* WHERE ID_User = 441 -- например */
ORDER BY Date_Creation DESC
LIMIT 0, 20
) AS pp
LEFT JOIN post_comment AS c
ON c.ID_Post = pp.ID_Post
ORDER BY pp.ID_Post, c.ID_Comment DESC
) AS t
) AS tt
WHERE rn < 10
ORDER BY ID_Post, rn DESC