Como obter o registro seguinte / anterior no MySQL?

129

Digamos que tenho registros com IDs 3,4,7,9 e desejo poder ir de um para outro navegando nos links seguintes / anteriores. O problema é que eu não sei como buscar registros com o ID mais alto mais próximo.

Portanto, quando tenho um registro com o ID 4, preciso buscar o próximo registro existente, que seria 7. A consulta provavelmente se parecerá com algo como

SELECT * FROM foo WHERE id = 4 OFFSET 1

Como posso buscar o registro seguinte / anterior sem buscar todo o conjunto de resultados e iterar manualmente?

Estou usando o MySQL 5.

Jakub Arnold
fonte
8
Você não deve confiar nos IDs para classificar (ou seja: presumindo que seu ID seja uma coluna de incremento automático). Você deve criar outra coluna na tabela que descreva explicitamente a ordem de classificação.
Digno Dabbler

Respostas:

258

Próximo:

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

anterior:

select * from foo where id = (select max(id) from foo where id < 4)
pescoço longo
fonte
7
Eu acho que as subconsultas devem ser (selecione min (id) de foo onde id> 4) e (selecione max (id) de foo onde id <4).
Digno Dabbler
1
você está certo, eu esqueci a cláusula FROM. Obrigado por apontar isso. :) #
30909 longneck
2
Isso não é solução, porque se você remover a linha de "foo", o que acontecerá? : P
Valentin Hristov
@valentinhristov Não entendo o que você está dizendo. Por favor, forneça um exemplo.
longneck
@ longneck Se você postar após a gravação, está tudo bem. Mas se você postar conteúdo após o ID da versão, não será um critério para posição em sequência.
Valentin Hristov
140

Além dos cemkalyoncu's solução :

próximo registro:

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

Registro anterior:

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

edit: Como essa resposta tem recebido alguns votos positivos recentemente, quero enfatizar o comentário que fiz anteriormente sobre o entendimento de que uma coluna de chave primária não se destina a ser uma coluna a ser classificada, porque o MySQL não garante esse aumento automático maior , os valores são necessariamente adicionados posteriormente.

Se você não se importa com isso, e simplesmente precisa do registro com um valor mais alto (ou mais baixo) id, isso será suficiente. Apenas não use isso como um meio para determinar se um registro é realmente adicionado mais tarde (ou mais cedo). Em vez disso, considere usar uma coluna datetime para classificar por, por exemplo.

Dabbler decente
fonte
Eu prefiro muito essa resposta, classifico por carimbo de data e hora, e isso significa que não há subconsultas, portanto, duas consultas no total, em vez de 4.
Maurice
61

Todas as soluções acima requerem duas chamadas ao banco de dados. O código sql abaixo combina duas instruções sql em uma.

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
fonte
3
Definitivamente a melhor solução. Rápido e tudo em uma consulta.
Daemon
Funciona perfeitamente, mesmo em uma consulta muito mais complicada!
taiar 25/07/14
Adicionar DISTINCTantes *tornará ainda melhor, ou seja, é claro que idpode ter 0como valor.
Ejaz
Excelente solução. No primeiro e no último registro retornará um single idao invés de dois.
lubosdz
19
SELECT * FROM foo WHERE id>4 ORDER BY id LIMIT 1
Cem Kalyoncu
fonte
1
+1 Acho que esta é a solução mais limpa. Mas a cláusula LIMIT deve vir por último: SELECT * FROM foo WHERE id> 4 ORDER BY id LIMIT 1
Dabbler decente
e SELECT * FROM foo WHERE id<4 ORDER BY id DESC LIMIT 1para o registro anterior
fwonce 12/03/2015
12

Eu estava tentando fazer algo semelhante a isso, mas precisava dos resultados ordenados por data, pois não posso confiar no campo de ID como uma coluna classificável. Aqui está a solução que eu criei.

Primeiro, descobrimos o índice do registro desejado na tabela, quando ele é classificado como queremos:

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;

Em seguida, diminua o resultado em 2 e coloque-o na declaração de limite. Por exemplo, o acima retornou 21 para mim, então eu corro:

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

Fornece seu registro primário, juntamente com os registros seguintes e anteriores, de acordo com o pedido declarado.

Tentei fazer isso como uma única chamada de banco de dados, mas não consegui que a instrução LIMIT recebesse uma variável como um de seus parâmetros.

Dan
fonte
Estou interessado em sua abordagem. E se você precisar fazer um JOIN também? Digamos, DE artigos a JOIN autores b ON b.este = a.that. Parece-me que o JOIN atrapalha a encomenda. Aqui está um exemplo pastebin.com/s7R62GYV
idrarig 2/13/13
1
Para fazer um JOIN, é necessário definir duas variáveis ​​@ diferentes. `SELECT current.row, current.id, previous.row, previous.id FROM (SELECT @rownum: = @ rownum + 1 linha, a. * FROM artigos a, (SELECT @rownum: = 0) r ORDER BY date, id) como current_row LEFT JOIN (SELECT @ rownum2: = @ rownum2 + 1 linha, a. * FROM artigos a, (SELECT @ rownum2: = 0) r ORDER BY date, id) como previous_row ON (current_row.id = previous_row. id) AND (current_row.row = previous_row.row -1) `
Eduardo Moralles
7

Tente este exemplo.

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; 

Nota: Se o valor não for encontrado, ele retornará nulo .

No exemplo acima, o valor Anterior será Raja e o valor Próximo será nulo porque não há próximo valor.

Ranga Reddy
fonte
7

Usando a abordagem do @Dan, você pode criar JOINs. Basta usar uma variável @ diferente para cada subconsulta.

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)
Eduardo Moralles
fonte
Exatamente o que eu precisava, obrigado. Observe que sua seleção não está correta, current-> current_row& previous-> previous_row.
Ludovic Guillaume
Essa consulta pode ser útil para descobrir se os registros foram excluídos da tabela (a sequência da coluna do ID de auto_increment está sendo interrompida) ou não.
Dharmang
@LudovicGuillaume você pode ser mais específico. Eu não entendo sua observação, desculpe. Mas a consulta não possui elementos na anterior_row. *
Walter Schrabmair
4

Próxima linha

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

Linha anterior

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

amostra da próxima linha

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

amostra da linha anterior

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
fonte
4

Eu tinha o mesmo problema que Dan, então usei sua resposta e a melhorei.

Primeiro selecione o índice da linha, nada de diferente aqui.

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;

Agora use duas consultas separadas. Por exemplo, se o índice da linha for 21, a consulta para selecionar o próximo registro será:

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

Para selecionar o registro anterior, use esta consulta:

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

Lembre-se de que, para a primeira linha (o índice da linha é 1), o limite será -1 e você receberá um erro do MySQL. Você pode usar uma instrução if para evitar isso. Apenas não selecione nada, pois não há registro anterior. No caso do último registro, haverá a próxima linha e, portanto, não haverá resultado.

Lembre-se também de que, se você usar DESC para fazer pedidos, em vez de ASC, as consultas para selecionar as linhas seguinte e anterior ainda serão as mesmas, mas alternadas.

magnetronnie
fonte
3

Esta é uma solução universal para condições com mais resultados iguais.

<?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
fonte
3

Aqui temos uma maneira de buscar registros anteriores e seguintes usando uma única consulta MySQL. Onde 5 é o ID do registro atual.

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) )
Jaskaran Singh
fonte
2

Como obter o registro seguinte / anterior no MySQL e PHP?

Meu exemplo é obter apenas o ID

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;
  }
}
Purvesh Tejani
fonte
Voto negativo porque é vulnerável à injeção de SQL . Eu sei que este é um post antigo, mas desenvolvedores iniciantes devem estar cientes disso.
Ayrtonvwf # 25/18
2

Otimizando a abordagem @Don para usar apenas uma consulta

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

altere CurrentArticleID com o ID atual do artigo, como

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
Zeeshan Anjum
fonte
Esta é a resposta mais útil que encontrei aqui até agora. A única melhoria real que eu sugiro é o uso de variáveis ​​e a movimentação de todas as partes únicas para o topo, para serem facilmente editáveis. Em seguida, pode ser facilmente transformado em uma função ou processo frequentemente referenciado.
liljoshu
1

Há outro truque que você pode usar para mostrar as colunas das linhas anteriores, usando qualquer ordem que desejar, usando uma variável semelhante ao truque @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

Aparentemente, as colunas de seleção são avaliadas em ordem, portanto, primeiro você seleciona as variáveis ​​salvas e depois atualiza as variáveis ​​para a nova linha (selecionando-as no processo).

NB: Não tenho certeza se esse é um comportamento definido, mas eu o usei e funciona.

Willem M.
fonte
1

Se você deseja alimentar mais de umid na sua consulta e obternext_id todos eles ...

Atribua cur_idseu campo de seleção e alimente-o para que a subconsulta chegue ao next_idcampo de seleção. E então selecione apenasnext_id .

Usando longneck reply para calc 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
fonte
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
Piotr Kos
fonte
6
Bem-vindo ao StackOverflow. Respostas consistindo inteiramente de código raramente são úteis. Por favor, considere fornecer algumas explicações sobre o que seu código está fazendo. A documentação em código e / ou nomes de variáveis ​​em inglês também seriam úteis.
Politank Z-
1

Se você tiver uma coluna de índice, digamos id, poderá retornar o ID anterior e o próximo em uma solicitação sql. Substitua: id pelo seu valor

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
Patrice Flao
fonte
0

Minha solução para obter o próximo registro e visualizar também para voltar ao primeiro registro, se eu estiver na última e vice-versa

Não estou usando o ID, estou usando o título para URLs agradáveis

Estou usando o 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
fonte
Voto negativo porque é vulnerável à injeção de SQL . Eu sei que este é um post antigo, mas desenvolvedores iniciantes devem estar cientes disso.
Ayrtonvwf # 25/18
0

Aqui está a minha resposta. Pego uma ideia no ' Decent Dabbler ' e adiciono a parte que está verificando se o id está entre min (id) e max (id). Aqui está a parte para criar minha tabela.

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

);

A próxima etapa é criar um procedimento armazenado responsável por obter o ID anterior.

    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

O primeiro método é bom se os índices estiverem classificados, mas se não estiverem. Por exemplo, se você possui índices: 1,2,7 e precisa obter o número 2 do índice dessa maneira, muito melhor para usar outra abordagem.

    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
Dennis
fonte
1
Procurando o distintivo necromante? Esta pergunta tem uma resposta aceita há 10 anos.
Jakub Arnold
Acabei de encontrar este tópico, porque tive o mesmo problema recentemente.
Dennis
0

100% trabalhando

SELECT * FROM `table` where table_id < 3 ORDER BY `table_id` DESC limit 1
Ragul
fonte
-1

Eu acho que para ter a linha real seguinte ou anterior na tabela SQL, precisamos do valor real com igual, (<ou>) retorne mais de um, se você precisar alterar a posição da linha em uma tabela de pedidos.

precisamos do valor $positionpara pesquisar a neighbourslinha Na minha tabela, criei uma coluna 'position'

e consulta SQL para obter a linha necessária é:

a seguir :

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

para anterior:

 SELECT * 
 FROM my_table 
 WHERE id = (SELECT (id) 
             FROM my_table 
             WHERE `position` = ($position-1)) 
 LIMIT 1
Roussos
fonte
Se uma linha foi excluída, isso será interrompido. por exemplo: se os IDs são o número 1,2,6,7,8.
22430 Kevin Waterson
-2

Selecione o top 1 * de foo em que id> 4 ordena por id asc

Gratzy
fonte
1
O MySQL equivalente é "LIMIT 1": select * from foo where id>4 order by id asc LIMIT 1
mob