Comment obtenir l'enregistrement suivant / précédent dans MySQL?

129

Disons que j'ai des enregistrements avec des identifiants 3, 4, 7, 9 et que je veux pouvoir passer de l'un à l'autre en naviguant via les liens suivant / précédent. Le problème est que je ne sais pas comment récupérer l'enregistrement avec l'ID le plus proche.

Ainsi, quand j'ai un enregistrement avec l'ID 4, je dois être en mesure de récupérer le prochain enregistrement existant, qui serait 7. La requête ressemblerait probablement à quelque chose comme

SELECT * FROM foo WHERE id = 4 OFFSET 1

Comment puis-je récupérer l'enregistrement suivant / précédent sans récupérer l'ensemble de résultats et l'itérer manuellement?

J'utilise MySQL 5.

Jakub Arnold
la source
8
Vous ne devez pas vous fier aux ID pour le tri (c'est-à-dire en supposant que votre ID est une colonne auto_increment). Vous devez créer une autre colonne dans le tableau qui décrit explicitement l'ordre de tri.
Decent Dabbler le

Réponses:

258

suivant:

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

précédent:

select * from foo where id = (select max(id) from foo where id < 4)
long cou
la source
7
Je pense que les sous-requêtes devraient être (sélectionnez min (id) de foo où id> 4) et (sélectionnez max (id) de foo où id <4).
Decent Dabbler
1
vous avez raison, j'ai oublié la clause FROM. merci de l'avoir signalé. :)
longneck
2
Ce n'est pas une solution car si vous supprimez la ligne de "foo", que se passera-t-il? : P
Valentin Hristov
@valentinhristov Je ne comprends pas ce que vous dites. Veuillez donner un exemple.
longneck
@longneck Si vous postez après l'enregistrement, tout va bien. Mais si vous publiez du contenu après l'ID de version, ce n'est pas un critère de position dans l'ordre.
Valentin Hristov
140

En plus de la solution de cemkalyoncu :

enregistrement suivant:

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

enregistrement précédent:

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

edit: Étant donné que cette réponse a reçu quelques votes positifs ces derniers temps, je tiens vraiment à souligner le commentaire que j'ai fait plus tôt sur la compréhension qu'une colonne de clé primaire n'est pas conçue comme une colonne à trier, car MySQL ne garantit pas que l'incrémentation automatique plus élevée , les valeurs sont nécessairement ajoutées ultérieurement.

Si vous ne vous souciez pas de cela et que vous avez simplement besoin du record avec un plus haut (ou plus bas), idcela suffira. N'utilisez pas cela comme un moyen de déterminer si un enregistrement est réellement ajouté plus tard (ou plus tôt). Au lieu de cela, envisagez d'utiliser une colonne datetime pour trier, par exemple.

Dabbler décent
la source
Je préfère de loin cette réponse, je trie par horodatage, et cela signifie pas de sous-requêtes, donc 2 requêtes au total au lieu de 4.
Maurice
61

Toutes les solutions ci-dessus nécessitent deux appels à la base de données. Le code sql ci-dessous combine deux instructions SQL en une seule.

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
la source
3
Certainement la meilleure solution. Rapide et tout en une seule requête.
démon
Fonctionne parfaitement même dans une requête beaucoup plus compliquée!
taiar
Ajouter DISTINCTavant *le rendra encore meilleur, c'est-à-dire, bien sûr, s'il idpeut avoir une 0valeur.
Ejaz
Excellente solution. Le premier et le dernier enregistrement renverront un single idplutôt que deux.
lubosdz
19
SELECT * FROM foo WHERE id>4 ORDER BY id LIMIT 1
Cem Kalyoncu
la source
1
+1 Je pense que c'est la solution la plus propre. Mais la clause LIMIT devrait venir en dernier: SELECT * FROM foo WHERE id> 4 ORDER BY id LIMIT 1
Decent Dabbler
et SELECT * FROM foo WHERE id<4 ORDER BY id DESC LIMIT 1pour le disque précédent
fwonce
12

J'essayais de faire quelque chose de similaire, mais j'avais besoin des résultats classés par date car je ne peux pas me fier au champ ID en tant que colonne triable. Voici la solution que j'ai trouvée.

Tout d'abord, nous trouvons l'index de l'enregistrement souhaité dans la table, quand il est trié comme nous le voulons:

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;

Puis décrémentez le résultat de 2 et placez-le dans l'instruction limit. Par exemple, ce qui précède a renvoyé 21 pour moi, alors je lance:

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

Vous donne votre enregistrement principal avec ses enregistrements suivants et précédents selon votre ordre indiqué.

J'ai essayé de le faire en un seul appel à la base de données, mais je n'ai pas pu obtenir l'instruction LIMIT pour prendre une variable comme l'un de ses paramètres.

Dan
la source
Je suis intéressé par votre approche. Et si vous avez également besoin de faire un JOIN? Dites, FROM articles a JOIN auteurs b ON b.this = a.that. Il me semble que le JOIN gâche la commande. Voici un exemple pastebin.com/s7R62GYV
idrarig
1
Pour faire un JOIN, vous devez définir deux @variables différentes. `SELECT current.row, current.id, previous.row, previous.id FROM (SELECT @rownum: = @ rownum + 1 row, a. * FROM articles a, (SELECT @rownum: = 0) r ORDER BY date, id) comme current_row LEFT JOIN (SELECT @ rownum2: = @ rownum2 + 1 ligne, a. * FROM articles a, (SELECT @ rownum2: = 0) r ORDER BY date, id) comme previous_row ON (current_row.id = previous_row. id) AND (current_row.row = previous_row.row -1) `
Eduardo Moralles
7

Essayez cet exemple.

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; 

Remarque: si la valeur n'est pas trouvée, elle retournera null .

Dans l'exemple ci-dessus, la valeur précédente sera Raja et la valeur suivante sera nulle car il n'y a pas de valeur suivante.

Ranga Reddy
la source
7

En utilisant l'approche de @Dan, vous pouvez créer des JOIN. Utilisez simplement une variable @ différente pour chaque sous-requête.

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
la source
Juste ce dont j'avais besoin, merci. Notez que votre sélection n'est pas correcte, current-> current_row& previous-> previous_row.
Ludovic Guillaume
Cette requête peut être utile pour savoir si les enregistrements ont été supprimés de la table (la séquence de la colonne auto_increment id est rompue) ou non.
Dharmang
@LudovicGuillaume pouvez-vous être plus précis. Je ne comprends pas votre remarque, désolé. Mais la requête ne contient aucun élément dans previous_row. *
Walter Schrabmair
4

Ligne suivante

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

Ligne précédente

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

échantillon de la ligne suivante

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

échantillon de la ligne précédente

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
la source
4

J'ai eu le même problème que Dan, alors j'ai utilisé sa réponse et je l'ai améliorée.

Sélectionnez d'abord l'index de ligne, rien de différent ici.

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;

Utilisez maintenant deux requêtes distinctes. Par exemple, si l'index de ligne est 21, la requête pour sélectionner l'enregistrement suivant sera:

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

Pour sélectionner l'enregistrement précédent, utilisez cette requête:

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

Gardez à l'esprit que pour la première ligne (l'index de ligne est 1), la limite passera à -1 et vous obtiendrez une erreur MySQL. Vous pouvez utiliser une instruction if pour éviter cela. Ne sélectionnez rien, car il n'y a de toute façon aucun enregistrement précédent. Dans le cas du dernier enregistrement, il y aura la ligne suivante et il n'y aura donc aucun résultat.

Gardez également à l'esprit que si vous utilisez DESC pour le classement, au lieu de ASC, les requêtes pour sélectionner les lignes suivante et précédente sont toujours les mêmes, mais commutées.

magnétronnie
la source
3

C'est une solution universelle pour les conditions avec plus de mêmes résultats.

<?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
la source
3

Ici, nous avons un moyen de récupérer les enregistrements précédents et suivants en utilisant une seule requête MySQL. Où 5 est l'identifiant de l'enregistrement actuel.

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
la source
2

Comment obtenir l'enregistrement suivant / précédent dans MySQL et PHP?

Mon exemple est d'obtenir l'identifiant uniquement

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
la source
Évalué car il est vulnérable à l' injection SQL . Je sais que c'est un ancien article, mais les développeurs novices devraient en être conscients.
ayrtonvwf
2

Optimiser l'approche @Don pour n'utiliser qu'une seule requête

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

changer CurrentArticleID avec l'ID d'article actuel comme

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
la source
C'est la réponse la plus utile que j'ai trouvée ici jusqu'à présent. La seule véritable amélioration que je suggérerais consiste à utiliser des variables et à déplacer toutes les pièces uniques vers le haut pour qu'elles soient facilement modifiables. Ensuite, peut facilement être transformé en une fonction ou un processus souvent référencé.
liljoshu
1

Il existe une autre astuce que vous pouvez utiliser pour afficher les colonnes des lignes précédentes, en utilisant l'ordre de votre choix, en utilisant une variable similaire à l'astuce @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

Apparemment, les colonnes de sélection sont évaluées dans l'ordre, donc cela sélectionnera d'abord les variables enregistrées, puis mettra à jour les variables vers la nouvelle ligne (en les sélectionnant dans le processus).

NB: Je ne suis pas sûr que ce soit un comportement défini, mais je l'ai utilisé et cela fonctionne.

Willem M.
la source
1

Si vous souhaitez en nourrir plusieursid pour votre requête et obtenir next_idpour toutes ...

Attribuez-le cur_iddans votre champ de sélection, puis transmettez-le à la sous-requête en entrant next_iddans le champ de sélection. Et puis sélectionnez juste next_id.

Utilisation de la réponse à long cou pour calculer 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
la source
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
la source
6
Bienvenue dans StackOverflow. Les réponses entièrement constituées de code sont rarement utiles. Veuillez envisager de fournir une explication de ce que fait votre code. Une documentation dans le code et / ou des noms de variables en anglais seraient également utiles.
Politank-Z
1

Si vous avez une colonne d'index, disons id, vous pouvez renvoyer l'identifiant précédent et suivant dans une requête SQL. Remplacez: id par votre valeur

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
la source
0

Ma solution pour obtenir le prochain enregistrement et les aperçus également pour revenir au premier enregistrement si je suis par le dernier et vice versa

Je n'utilise pas l'identifiant J'utilise le titre pour de belles URL

J'utilise 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
la source
Évalué car il est vulnérable à l' injection SQL . Je sais que c'est un ancien article, mais les développeurs novices devraient en être conscients.
ayrtonvwf
0

Voici ma réponse. Je prends une idée de ' Decent Dabbler ' et ajoute la partie qui vérifie si l' id est entre min (id) et max (id). Voici la partie pour créer ma table.

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

);

L'étape suivante consiste à créer une procédure stockée chargée d'obtenir l'ID précédent.

    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

La première méthode est bonne si les index sont triés, mais s'ils ne le sont pas. Par exemple, si vous avez des index: 1, 2, 7 et que vous devez obtenir l'index numéro 2 de cette manière, il est préférable d'utiliser une autre approche.

    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
la source
1
Vous cherchez l'insigne de nécromancien? Cette question a une réponse acceptée depuis 10 ans.
Jakub Arnold
Je viens de trouver ce sujet, car j'ai eu le même problème récemment.
Dennis
0

100% de travail

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

Je pense que pour avoir la vraie ligne suivante ou précédente dans la table SQL, nous avons besoin de la valeur réelle avec égal, (<ou>) renvoie plus d'un si vous devez changer la position de la ligne dans une table de commande.

nous avons besoin de la valeur $positionpour rechercher la neighboursligne Dans ma table, j'ai créé une colonne 'position'

et la requête SQL pour obtenir la ligne nécessaire est:

pour la prochaine:

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

pour le précédent:

 SELECT * 
 FROM my_table 
 WHERE id = (SELECT (id) 
             FROM my_table 
             WHERE `position` = ($position-1)) 
 LIMIT 1
Roussos
la source
Si une ligne a été supprimée, cela se cassera. par exemple: si les identifiants sont les nombres 1, 2, 6, 7, 8.
Kevin Waterson
-2

Sélectionnez le top 1 * de foo où id> 4 order by id asc

Gratzy
la source
1
MySQL équivalent est "LIMIT 1": select * from foo where id>4 order by id asc LIMIT 1
mob