Эмуляций 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