Come ottenere il record successivo / precedente in MySQL?


129

Supponiamo che io abbia record con ID 3,4,7,9 e voglio poter passare l'uno dall'altro navigando tramite i collegamenti successivo / precedente. Il problema è che non so come recuperare il record con l'ID più vicino più vicino.

Quindi, quando ho un record con ID 4, devo essere in grado di recuperare il successivo record esistente, che sarebbe 7. La query probabilmente sarebbe simile a

SELECT * FROM foo WHERE id = 4 OFFSET 1

Come posso recuperare il record successivo / precedente senza recuperare l'intero set di risultati e iterare manualmente?

Sto usando MySQL 5.


8
Non devi fare affidamento sugli ID per l'ordinamento (vale a dire: presumere che il tuo ID sia una colonna auto_increment). È necessario creare un'altra colonna nella tabella che descriva esplicitamente l'ordinamento.
Dabbler decente,

Risposte:


258

Il prossimo:

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

precedente:

select * from foo where id = (select max(id) from foo where id < 4)

7
Penso che le sottoquery dovrebbero essere (selezionare min (id) da foo dove id> 4) e (selezionare max (id) da foo dove id <4).
Decent Dabbler,

1
hai ragione, ho dimenticato la clausola FROM. Grazie per segnalarlo. :)
collo lungo,

2
Questa non è una soluzione perché se rimuovi la riga da "pippo", cosa accadrà? : P
Valentin Hristov,

@valentinhristov Non capisco cosa stai dicendo. Fornisci un esempio
collo lungo,

@longneck Se pubblichi post dopo la registrazione, va tutto bene. Ma se pubblichi contenuti dopo l'ID versione non sono criteri per la posizione in sequenza.
Valentin Hristov

140

Oltre a cemkalyoncu soluzione :

prossimo record:

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

record precedente:

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

modifica: poiché ultimamente questa risposta ha ricevuto alcuni voti positivi, voglio davvero sottolineare il commento che ho fatto precedenza sulla comprensione del fatto che una colonna di chiavi primarie non è intesa come una colonna per la quale ordinare, perché MySQL non garantisce che un incremento più elevato, auto-incrementato , i valori vengono necessariamente aggiunti in un secondo momento.

Se non ti interessa, e hai semplicemente bisogno di un record con un valore più alto (o più basso), idquesto sarà sufficiente. Basta non usarlo come mezzo per determinare se un record viene effettivamente aggiunto in seguito (o prima). Invece, considera l'utilizzo di una colonna datetime per ordinare, ad esempio.


Preferisco di gran lunga questa risposta, l'ho ordinata per data e ora, e non significa subquery, quindi 2 query in totale invece di 4.
Maurice

61

Tutte le soluzioni sopra richiedono due chiamate al database. Il seguente codice sql combina due istruzioni sql in una.

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)
      )    

3
Sicuramente la soluzione migliore. Veloce e tutto in una query.
demone

Funziona perfettamente anche in una query molto più complicata!
Taiar,

L'aggiunta DISTINCTprima *lo renderà ancora migliore, vale a dire ovviamente se idpuò avere 0come valore.
Ejaz,

Soluzione eccellente Il primo e l'ultimo record restituiranno singoli idanziché due.
lubosdz,

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

1
+1 Penso che questa sia la soluzione più pulita. Ma la clausola LIMIT dovrebbe venire per ultima: SELEZIONA * DA foo DOVE id> 4 ORDER BY id LIMIT 1
Decent Dabbler

e SELECT * FROM foo WHERE id<4 ORDER BY id DESC LIMIT 1per la cronaca precedente
avanti al

12

Stavo tentando di fare qualcosa di simile a questo, ma avevo bisogno dei risultati ordinati per data poiché non posso fare affidamento sul campo ID come colonna ordinabile. Ecco la soluzione che mi è venuta in mente.

Per prima cosa scopriamo l'indice del record desiderato nella tabella, quando viene ordinato come vogliamo:

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;

Quindi decrementa il risultato di 2 e mettilo nella dichiarazione limite. Ad esempio, il precedente ha restituito 21 per me, quindi corro:

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

Ti dà il tuo record principale insieme ai record successivi e precedenti dato il tuo ordine dichiarato.

Ho provato a farlo come una singola chiamata al database, ma non sono riuscito a ottenere l'istruzione LIMIT per prendere una variabile come uno dei suoi parametri.


Sono interessato al tuo approccio. Che cosa succede se è necessario effettuare anche un JOIN? Ad esempio, DA articoli a ISCRIVITI agli autori b ON b.this = a.that. Mi sembra che il JOIN rovini l'ordinamento. Ecco un esempio pastebin.com/s7R62GYV
idrarig

1
Per fare un JOIN, devi definire due diversi @variables. `SELECT current.row, current.id, previous.row, previous.id FROM (SELECT @rownum: = @ rownum + 1 riga, a. * DA articoli a, (SELECT @rownum: = 0) r ORDINA PER data, id) come current_row LEFT JOIN (SELECT @ rownum2: = @ rownum2 + 1 riga, a. * DA articoli a, (SELECT @ rownum2: = 0) r ORDINA PER data, id) come previous_row ON (current_row.id = previous_row. id) AND (current_row.row = previous_row.row -1) `
Eduardo Moralles,

7

Prova questo esempio.

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 il valore non viene trovato, verrà restituito null .

Nell'esempio sopra, il valore precedente sarà Raja e il valore successivo sarà nullo perché non esiste alcun valore successivo.


7

Utilizzando l'approccio di @Dan, è possibile creare JOIN. Basta usare un @variable diverso per ogni sub query.

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)

Proprio quello di cui avevo bisogno, grazie. Si noti che la selezione non è corretta, current-> current_row& previous-> previous_row.
Ludovic Guillaume,

Questa query può essere utile per scoprire se i record sono stati eliminati dalla tabella (la sequenza della colonna ID auto_increment non funziona).
Dharmang,

@LudovicGuillaume puoi essere più specifico. Non capisco la tua osservazione, scusa. Ma la query non ha elementi in previous_row. *
Walter Schrabmair,

4

Riga successiva

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

Riga precedente

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

campione riga successiva

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

campione riga precedente

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

4

Ho avuto lo stesso problema di Dan, quindi ho usato la sua risposta e l'ho migliorata.

Prima seleziona l'indice di riga, niente di diverso qui.

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;

Ora usa due query separate. Ad esempio, se l'indice di riga è 21, la query per selezionare il record successivo sarà:

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

Per selezionare il record precedente utilizzare questa query:

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

Tieni presente che per la prima riga (l'indice di riga è 1), il limite andrà a -1 e otterrai un errore MySQL. È possibile utilizzare un'istruzione if per impedirlo. Basta non selezionare nulla, dal momento che non esiste alcun record precedente. Nel caso dell'ultimo record, ci sarà la riga successiva e quindi non ci saranno risultati.

Inoltre, tieni presente che se si utilizza DESC per l'ordinamento, anziché ASC, le query per selezionare le righe successiva e precedente saranno sempre le stesse, ma cambiate.


3

Questa è una soluzione universale per condizioni con più stessi risultati.

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

3

Qui abbiamo un modo per recuperare i record precedenti e successivi usando una singola query MySQL. Dove 5 è l'id del record corrente.

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

Come ottenere il record successivo / precedente in MySQL e PHP?

Il mio esempio è ottenere solo l'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;
  }
}

Sottovalutato perché vulnerabile a SQL Injection . So che questo è un vecchio post, ma gli sviluppatori alle prime armi dovrebbero esserne consapevoli.
ayrtonvwf,

2

Ottimizzare l'approccio @Don per utilizzare solo una query

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

cambia CurrentArticleID con l'ID articolo corrente come

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

Questa è la risposta più utile che ho trovato qui finora. L'unico vero miglioramento che suggerirei è usare le variabili e spostare tutte le parti uniche verso l'alto per essere facilmente modificabili. Quindi può essere facilmente trasformato in una funzione o processo spesso referenziato.
liljoshu,

1

C'è un altro trucco che puoi usare per mostrare le colonne delle righe precedenti, usando qualsiasi ordine tu voglia, usando una variabile simile al trucco @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

Apparentemente, le colonne selezionate vengono valutate in ordine, quindi questo selezionerà prima le variabili salvate, quindi aggiornerà le variabili nella nuova riga (selezionandole nel processo).

NB: Non sono sicuro che questo sia un comportamento definito, ma l'ho usato e funziona.


1

Se vuoi alimentare più di unoid alla tua query e ottenerli next_idtutti ...

Assegnare cur_idil campo selezionato e quindi inviarlo alla sottoquery entrando next_idnel campo Seleziona. E quindi selezionare solo next_id.

Utilizzando la risposta dal collo lungo per 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;

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
Benvenuto in StackOverflow. Le risposte costituite interamente da codice sono raramente utili. Considera di fornire alcune spiegazioni su cosa sta facendo il tuo codice. Sarebbe utile anche la documentazione in-code e / o i nomi delle variabili in inglese.
Politank-Z,

1

Se hai una colonna di indice, ad esempio id, puoi restituire l'id precedente e successivo in una richiesta sql. Sostituisci: id con il tuo valore

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

La mia soluzione per ottenere il prossimo record e le anteprime anche per tornare al primo record se sono l'ultimo e viceversa

Non sto usando l'id che sto usando il titolo di bello URL

Sto usando 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;
    }     
}

Sottovalutato perché vulnerabile a SQL Injection . So che questo è un vecchio post, ma gli sviluppatori alle prime armi dovrebbero esserne consapevoli.
ayrtonvwf,

0

Ecco la mia risposta Prendo un'idea da ' Dabbler decente ' e aggiungo la parte che sta controllando se id è tra min (id) e max (id). Ecco la parte per creare la mia tabella.

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

);

Il passo successivo è la creazione di una procedura memorizzata responsabile dell'ottenimento dell'ID precedente.

    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

Il primo metodo è valido se gli indici sono ordinati, ma se non lo sono. Ad esempio, se hai indici: 1,2,7 e devi ottenere l'indice numero 2 in questo modo molto meglio per usare un altro approccio.

    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
A caccia del distintivo del negromante? Questa domanda ha avuto una risposta accettata per 10 anni.
Jakub Arnold,

Ho appena trovato questo argomento, perché ho avuto lo stesso problema di recente.
Dennis,

0

100% funzionante

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

-1

Penso che per avere la riga successiva o precedente reale nella tabella SQL sia necessario il valore reale con uguale, (<o>) restituisce più di una se è necessario modificare la posizione della riga in una tabella di ordinamento.

abbiamo bisogno del valore $positionper cercare la neighboursriga Nella mia tabella ho creato una colonna 'position'

e la query SQL per ottenere la riga necessaria è:

per il prossimo :

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

per precedente:

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

Se una riga è stata eliminata, si interromperà. es: se gli ID sono il numero 1,2,6,7,8.
Kevin Waterson,

-2

Seleziona in alto 1 * da foo dove id> 4 ordina per id asc


1
MySQL equivalente è "LIMIT 1": select * from foo where id>4 order by id asc LIMIT 1
mob,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.