Как получить следующую / предыдущую запись в MySQL?

129

Скажем, у меня есть записи с идентификаторами 3,4,7,9, и я хочу иметь возможность переходить от одной к другой с помощью навигации по следующим / предыдущим ссылкам. Проблема в том, что я не знаю, как получить запись с ближайшим более высоким идентификатором.

Поэтому, когда у меня есть запись с ID 4, мне нужно иметь возможность получить следующую существующую запись, которой будет 7. Запрос, вероятно, будет выглядеть примерно так:

SELECT * FROM foo WHERE id = 4 OFFSET 1

Как я могу получить следующую / предыдущую запись без получения всего набора результатов и ручного выполнения итерации?

Я использую MySQL 5.

Якуб Арнольд
источник
8
Вы не должны полагаться на идентификаторы для сортировки (то есть предполагая, что ваш идентификатор является столбцом auto_increment). Вы должны создать еще один столбец в таблице, который явно отображает порядок сортировки.
Decent Dabbler

Ответы:

258

следующий:

select * from foo where id = (select min(id) from foo where id > 4)

предыдущая:

select * from foo where id = (select max(id) from foo where id < 4)
длинная шея
источник
7
Я думаю, что подзапросы должны быть (выберите min (id) из foo, где id> 4) и (выберите max (id) из foo, где id <4).
Decent Dabbler
1
Вы правы, я забыл предложение FROM. спасибо, что указали на это. :)
longneck
2
Это не решение, потому что если вы удалите строку из «foo», что произойдет? : P
Валентин Христов
@valentinhristov Я не понимаю, что вы говорите. Приведите пример.
longneck
@longneck Если постить после записи все нормально. Но если вы публикуете контент после идентификатора версии, это не критерий для позиции в последовательности.
Валентин Христов
140

В дополнение к раствору Джемкаленку :

следующая запись:

SELECT * FROM foo WHERE id > 4 ORDER BY id LIMIT 1;

предыдущая запись:

SELECT * FROM foo WHERE id < 4 ORDER BY id DESC LIMIT 1;

edit: Поскольку в последнее время этот ответ получил несколько голосов, я действительно хочу подчеркнуть комментарий, который я сделал ранее о понимании того, что столбец первичного ключа не предназначен для сортировки, потому что MySQL не гарантирует, что более высокое, автоматически увеличивающееся , значения обязательно добавляются позже.

Если вас это не волнует и вам просто нужна запись с более высоким (или более низким), idэтого будет достаточно. Только не используйте это как средство, чтобы определить, действительно ли запись добавлена ​​позже (или раньше). Вместо этого рассмотрите возможность использования, например, столбца datetime для сортировки.

Достойный дилетант
источник
Я предпочитаю этот ответ, я сортирую по отметке времени, и это означает отсутствие подзапросов, поэтому всего 2 запроса вместо 4.
Морис
61

Все вышеперечисленные решения требуют двух вызовов базы данных. Приведенный ниже код sql объединяет два оператора sql в один.

select * from foo 
where ( 
        id = IFNULL((select min(id) from foo where id > 4),0) 
        or  id = IFNULL((select max(id) from foo where id < 4),0)
      )    
оборота Sudip
источник
3
Однозначно лучшее решение. Быстро и все в одном запросе.
демон
Безупречно работает даже в гораздо более сложных запросах!
taiar
Добавление DISTINCTраньше *сделает его еще лучше, то есть, конечно, если это idможет иметь 0значение.
Ejaz
Отличное решение. Для первой и последней записи будет возвращена одна, idа не две.
lubosdz 08
19
SELECT * FROM foo WHERE id>4 ORDER BY id LIMIT 1
Джем Калёнку
источник
1
+1 Считаю это наиболее чистым решением. Но предложение LIMIT должно быть последним: SELECT * FROM foo WHERE id> 4 ORDER BY id LIMIT 1
Decent Dabbler
и SELECT * FROM foo WHERE id<4 ORDER BY id DESC LIMIT 1для предыдущего рекорда
fwonce
12

Я пытался сделать что-то подобное, но мне нужны были результаты, упорядоченные по дате, поскольку я не могу полагаться на поле идентификатора как на сортируемый столбец. Вот решение, которое я придумал.

Сначала узнаем индекс нужной записи в таблице, когда она отсортирована так, как мы хотим:

SELECT row
FROM 
(SELECT @rownum:=@rownum+1 row, a.* 
FROM articles a, (SELECT @rownum:=0) r
ORDER BY date, id) as article_with_rows
WHERE id = 50;

Затем уменьшите результат на 2, поместите его в оператор limit. Например, результат выше вернул мне 21, поэтому я бегу:

SELECT * 
FROM articles
ORDER BY date, id
LIMIT 19, 3

Предоставляет вам вашу основную запись вместе со следующей и предыдущей записями в соответствии с указанным вами порядком.

Я попытался сделать это как один вызов базы данных, но не смог заставить оператор LIMIT принять переменную в качестве одного из параметров.

Дэн
источник
Мне интересен ваш подход. Что делать, если вам также нужно выполнить JOIN? Скажем, ИЗ статей a ПРИСОЕДИНЯЙТЕСЬ к авторам b НА b.this = a.that. Мне кажется, что JOIN нарушает порядок. Вот пример pastebin.com/s7R62GYV
idrarig 02
1
Чтобы выполнить JOIN, вам нужно определить две разные @variables. `SELECT current.row, current.id, previous.row, previous.id FROM (SELECT @rownum: = @ rownum + 1 row, a. * FROM article a, (SELECT @rownum: = 0) r ORDER BY date, id) как current_row LEFT JOIN (SELECT @ rownum2: = @ rownum2 + 1 row, a. * FROM article a, (SELECT @ rownum2: = 0) r ORDER BY date, id) как previous_row ON (current_row.id = previous_row. id) AND (current_row.row = previous_row.row -1) `
Эдуардо Моралес
7

Попробуйте этот пример.

create table student(id int, name varchar(30), age int);

insert into student values
(1 ,'Ranga', 27),
(2 ,'Reddy', 26),
(3 ,'Vasu',  50),
(5 ,'Manoj', 10),
(6 ,'Raja',  52),
(7 ,'Vinod', 27);

SELECT name,
       (SELECT name FROM student s1
        WHERE s1.id < s.id
        ORDER BY id DESC LIMIT 1) as previous_name,
       (SELECT name FROM student s2
        WHERE s2.id > s.id
        ORDER BY id ASC LIMIT 1) as next_name
FROM student s
    WHERE id = 7; 

Примечание. Если значение не найдено, возвращается значение null .

В приведенном выше примере предыдущее значение будет Raja, а следующее значение будет нулевым, потому что следующего значения нет.

Ранга Редди
источник
7

Используя подход @Dan, вы можете создавать СОЕДИНЕНИЯ. Просто используйте разные @variable для каждого подзапроса.

SELECT current_row.row, current_row.id, previous_row.row, previous_row.id
FROM (
  SELECT @rownum:=@rownum+1 row, a.* 
  FROM articles a, (SELECT @rownum:=0) r
  ORDER BY date, id
) as current_row
LEFT JOIN (
  SELECT @rownum2:=@rownum2+1 row, a.* 
  FROM articles a, (SELECT @rownum2:=0) r
  ORDER BY date, id
) as previous_row ON
  (current_row.id = previous_row.id) AND (current_row.row = previous_row.row - 1)
Эдуардо Моралес
источник
Как раз то, что мне нужно, спасибо. Обратите внимание, что ваш выбор неправильный, current-> current_row& previous-> previous_row.
Людовик Гийом
Этот запрос может быть полезен, чтобы узнать, были ли удалены записи из таблицы (последовательность столбца идентификатора auto_increment нарушается) или нет.
Дхарманг
@LudovicGuillaume, не могли бы вы быть более конкретными. Я не понимаю вашего замечания, извините. Но в запросе нет элементов в previous_row. *
Вальтер Шрабмайр,
4

Следующая строка

SELECT * FROM `foo` LIMIT number++ , 1

Предыдущая строка

SELECT * FROM `foo` LIMIT number-- , 1

образец следующей строки

SELECT * FROM `foo` LIMIT 1 , 1
SELECT * FROM `foo` LIMIT 2 , 1
SELECT * FROM `foo` LIMIT 3 , 1

образец предыдущей строки

SELECT * FROM `foo` LIMIT -1 , 1
SELECT * FROM `foo` LIMIT -2 , 1
SELECT * FROM `foo` LIMIT -3 , 1

SELECT * FROM `foo` LIMIT 3 , 1
SELECT * FROM `foo` LIMIT 2 , 1
SELECT * FROM `foo` LIMIT 1 , 1
IDALICIUS
источник
4

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

Сначала выберите индекс строки, здесь ничего особенного.

SELECT row
FROM 
(SELECT @rownum:=@rownum+1 row, a.* 
FROM articles a, (SELECT @rownum:=0) r
ORDER BY date, id) as article_with_rows
WHERE id = 50;

Теперь используйте два отдельных запроса. Например, если индекс строки равен 21, запрос для выбора следующей записи будет:

SELECT * 
FROM articles
ORDER BY date, id
LIMIT 21, 1

Чтобы выбрать предыдущую запись, используйте этот запрос:

SELECT * 
FROM articles
ORDER BY date, id
LIMIT 19, 1

Имейте в виду, что для первой строки (индекс строки равен 1) предел будет равен -1, и вы получите ошибку MySQL. Вы можете использовать оператор if, чтобы предотвратить это. Только не выбирайте ничего, так как предыдущей записи все равно нет. В случае последней записи будет следующая строка, поэтому результата не будет.

Также имейте в виду, что если вы используете DESC для упорядочивания, вместо ASC, ваши запросы на выбор следующей и предыдущей строк остаются такими же, но переключаются.

magnetronnie
источник
3

Это универсальное решение для условий с большим количеством одинаковых результатов.

<?php
$your_name1_finded="somethnig searched"; //$your_name1_finded must be finded in previous select

$result = db_query("SELECT your_name1 FROM your_table WHERE your_name=your_condition ORDER BY your_name1, your_name2"); //Get all our ids

$i=0;
while($row = db_fetch_assoc($result)) { //Loop through our rows
    $i++;
    $current_row[$i]=$row['your_name1'];// field with results
    if($row['your_name1'] == $your_name1_finded) {//If we haven't hit our current row yet
        $yid=$i;
    }
}
//buttons
if ($current_row[$yid-1]) $out_button.= "<a  class='button' href='/$your_url/".$current_row[$yid-1]."'>BUTTON_PREVIOUS</a>";
if ($current_row[$yid+1]) $out_button.= "<a  class='button' href='/$your_url/".$current_row[$yid+1]."'>BUTTON_NEXT</a>";

echo $out_button;//display buttons
?>
asdf2017
источник
3

Здесь у нас есть способ получить предыдущие и следующие записи, используя один запрос MySQL. Где 5 - это идентификатор текущей записи.

select * from story where catagory=100 and  (
    id =(select max(id) from story where id < 5 and catagory=100 and order by created_at desc) 
    OR 
    id=(select min(id) from story where id > 5 and catagory=100 order by created_at desc) )
Джаскаран Сингх
источник
2

Как получить следующую / предыдущую запись в MySQL и PHP?

Мой пример - получить только идентификатор

function btn_prev(){

  $id = $_POST['ids'];
  $re = mysql_query("SELECT * FROM table_name WHERE your_id < '$id'  ORDER BY your_id DESC LIMIT 1");

  if(mysql_num_rows($re) == 1)
  {
    $r = mysql_fetch_array($re);
    $ids = $r['your_id'];
    if($ids == "" || $ids == 0)
    {
        echo 0;
    }
    else
    {
        echo $ids;
    }
  }
  else
  {
    echo 0;
  }
}



function btn_next(){

  $id = $_POST['ids'];
  $re = mysql_query("SELECT * FROM table_name WHERE your_id > '$id'  ORDER BY your_id ASC LIMIT 1");

  if(mysql_num_rows($re) == 1)
  {
    $r = mysql_fetch_array($re);
    $ids = $r['your_id'];
    if($ids == "" || $ids == 0)
    {
        echo 0;
    }
    else
    {
        echo $ids;
    }
  }
  else
  {
    echo 0;
  }
}
Пурвеш Теджани
источник
Проголосовали против, потому что он уязвим для внедрения SQL . Я знаю, что это старый пост, но начинающие разработчики должны это знать.
ayrtonvwf
2

Оптимизация подхода @Don для использования только одного запроса

SELECT * from (
  SELECT 
     @rownum:=@rownum+1 row,
     CASE a.id WHEN 'CurrentArticleID' THEN @currentrow:=@rownum ELSE NULL END as 'current_row',
     a.*  
  FROM articles a,
     (SELECT @currentrow:=0) c,  
     (SELECT @rownum:=0) r
   ORDER BY `date`, id  DESC
 ) as article_with_row
 where row > @currentrow - 2
 limit 3

измените CurrentArticleID на текущий идентификатор статьи, например

SELECT * from (
  SELECT 
     @rownum:=@rownum+1 row,
     CASE a.id WHEN '100' THEN @currentrow:=@rownum ELSE NULL END as 'current_row',
     a.*  
  FROM articles a,
     (SELECT @currentrow:=0) c,  
     (SELECT @rownum:=0) r
   ORDER BY `date`, id  DESC
 ) as article_with_row
 where row > @currentrow - 2
 limit 3
Зишан Анджум
источник
Это самый полезный ответ, который я нашел здесь до сих пор. Единственное реальное улучшение, которое я предлагаю, - это использование переменных и перемещение всех уникальных частей вверх, чтобы их было легко редактировать. Затем можно легко преобразовать в функцию или процесс, на которые часто ссылаются.
liljoshu
1

Есть еще один трюк, который вы можете использовать для отображения столбцов из предыдущих строк, используя любой порядок, который вы хотите, используя переменную, аналогичную трюку @row:

SELECT @prev_col_a, @prev_col_b, @prev_col_c,
   @prev_col_a := col_a AS col_a,
   @prev_col_b := col_b AS col_b,
   @prev_col_c := col_c AS col_c
FROM table, (SELECT @prev_col_a := NULL, @prev_col_b := NULL, @prev_col_c := NULL) prv
ORDER BY whatever

По-видимому, выбранные столбцы оцениваются по порядку, поэтому сначала будут выбраны сохраненные переменные, а затем обновлены переменные до новой строки (выбрав их в процессе).

NB: Я не уверен, что это определенное поведение, но я использовал его, и оно работает.

Виллем М.
источник
1

Если вы хотите скормить более одногоid запроса и получить next_idдля всех ...

Назначьте cur_idв поле выбора, а затем подайте его в подзапрос, попадая next_idв поле выбора. А затем выберите просто next_id.

Используя длинный ответ для вычисления next_id:

select next_id
from (
    select id as cur_id, (select min(id) from `foo` where id>cur_id) as next_id 
    from `foo` 
) as tmp
where next_id is not null;
Romeno
источник
1
CREATE PROCEDURE `pobierz_posty`(IN iduser bigint(20), IN size int, IN page int)
BEGIN
 DECLARE start_element int DEFAULT 0;
 SET start_element:= size * page;
 SELECT DISTINCT * FROM post WHERE id_users .... 
 ORDER BY data_postu DESC LIMIT size OFFSET start_element
END
Петр Кос
источник
6
Добро пожаловать в StackOverflow. Ответы, полностью состоящие из кода, редко бывают полезными. Пожалуйста, рассмотрите возможность объяснения того, что делает ваш код. Документация в коде и / или имена переменных на английском языке также будут полезны.
Политанк-З
1

Если у вас есть столбец индекса, например id, вы можете вернуть предыдущий и следующий id в одном запросе sql. Замените: id своим значением

SELECT
 IFNULL((SELECT id FROM table WHERE id < :id ORDER BY id DESC LIMIT 1),0) as previous,
 IFNULL((SELECT id FROM table WHERE id > :id ORDER BY id ASC LIMIT 1),0) as next
Патрис Флао
источник
0

Мое решение получить следующую запись и превью также, чтобы вернуться к первой записи, если я нахожусь на последней, и наоборот

Я не использую идентификатор, я использую заголовок для красивого URL

Я использую Codeigniter HMVC

$id = $this->_get_id_from_url($url);

//get the next id
$next_sql = $this->_custom_query("select * from projects where id = (select min(id) from projects where id > $id)");
foreach ($next_sql->result() as $row) {
    $next_id = $row->url;
}

if (!empty($next_id)) {
    $next_id = $next_id;
} else {
    $first_id = $this->_custom_query("select * from projects where id = (SELECT MIN(id) FROM projects)");
    foreach ($first_id->result() as $row) {
        $next_id = $row->url;
    }
}

//get the prev id
$prev_sql = $this->_custom_query("select * from projects where id = (select max(id) from projects where id < $id)");
foreach ($prev_sql->result() as $row) {
    $prev_id = $row->url;
}

if (!empty($prev_id)) {
    $prev_id = $prev_id;
} else {
    $last_id = $this->_custom_query("select * from projects where id = (SELECT MAX(id) FROM projects)");
    foreach ($last_id->result() as $row) {
        $prev_id = $row->url;
    }     
}
toiglicher
источник
Проголосовали против, потому что он уязвим для внедрения SQL . Я знаю, что это старый пост, но начинающие разработчики должны это знать.
ayrtonvwf
0

Вот мой ответ. Я беру идею из « Достойного любителя » и добавляю часть, которая проверяет, находится ли id между min (id) и max (id). Вот часть для создания моей таблицы.

CREATE TABLE Users (
UserID int NOT NULL auto_increment,
UserName varchar(45),
UserNameID varchar(45),
PRIMARY KEY (UserID)

);

Следующим шагом является создание хранимой процедуры, которая отвечает за получение предыдущего идентификатора.

    CREATE DEFINER=`root`@`localhost` PROCEDURE `printPreviousIDbySelectedIDUser`(
IN ID int,
IN search_name varchar(45)
)
BEGIN
SELECT CONCAT(ns.UserID) AS 'Previous ID' from Users ns
 where ns.UserName=search_name AND ns.UserID IN (select min(ns.UserID) from Users ns where ns.UserID > ID 
 union select max(ns.UserID) from Users ns where  ns.UserID < ID) LIMIT 1 ;
END

Первый способ хорош, если индексы отсортированы, но если нет. Например, если у вас есть индексы: 1,2,7 и вам нужно получить индекс номер 2 таким образом, гораздо лучше использовать другой подход.

    CREATE DEFINER=`root`@`localhost` PROCEDURE `getPreviousUserID`(
IN ID int,
IN search_name varchar(45)
)
BEGIN
SELECT CONCAT(ns.UserID) AS 'Previous ID' from Users ns
  WHERE ns.UserName=search_name AND  ns.UserID < ID   ORDER BY ns.UserID DESC LIMIT 1;
END
Деннис
источник
1
Охота на значок некроманта? На этот вопрос есть общепринятый ответ уже 10 лет.
Якуб Арнольд
Я только что нашел эту тему, потому что недавно у меня возникла такая же проблема.
Деннис
0

100% рабочий

SELECT * FROM `table` where table_id < 3 ORDER BY `table_id` DESC limit 1
Ragul
источник
-1

Я думаю, что для реальной следующей или предыдущей строки в таблице SQL нам нужно реальное значение с равным, (<или>) вернуть более одного, если вам нужно изменить положение строки в таблице заказа.

нам нужно значение $positionдля поиска в neighboursстроке В моей таблице я создал столбец «позиция»

и SQL-запрос для получения нужной строки:

для следующего:

SELECT * 
FROM `my_table` 
WHERE id = (SELECT (id) 
            FROM my_table 
            WHERE position = ($position+1)) 
LIMIT 1

для предыдущего:

 SELECT * 
 FROM my_table 
 WHERE id = (SELECT (id) 
             FROM my_table 
             WHERE `position` = ($position-1)) 
 LIMIT 1
Руссос
источник
Если строка была удалена, это сломается. например: если идентификаторы - 1,2,6,7,8.
Кевин Уотерсон
-2

Выберите верхний 1 * из foo, где id> 4, порядок по id asc

Gratzy
источник
1
Эквивалент MySQL - «LIMIT 1»: select * from foo where id>4 order by id asc LIMIT 1
mob