Эмуляций ROW_NUMBER в MySQL

2 мая 2011 г.

Статья написана по мотивам этого замечательного топика. Нашел я его гуглем, прочитал, и решения, которое меня бы устраивало, к сожалению, не нашел.

Пришлось думать самому. Обидно было то, что основная моя работа связана 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

Теги:
рубрика Программирование
  • Похожие статьи
  • Предыдущие из рубрики