Jak zdobyć następny / poprzedni rekord w MySQL?


132

Powiedzmy, że mam rekordy z identyfikatorami 3, 4, 7, 9 i chcę mieć możliwość przechodzenia od jednego do drugiego, nawigując za pomocą linków następny / poprzedni. Problem w tym, że nie wiem jak pobrać rekord z najbliższym wyższym ID.

Więc kiedy mam rekord o identyfikatorze 4, muszę być w stanie pobrać następny istniejący rekord, który będzie 7. Zapytanie prawdopodobnie wyglądałoby mniej więcej tak

SELECT * FROM foo WHERE id = 4 OFFSET 1

Jak mogę pobrać następny / poprzedni rekord bez pobierania całego zestawu wyników i ręcznej iteracji?

Używam MySQL 5.


8
Nie powinieneś polegać na identyfikatorach przy sortowaniu (czyli zakładając, że twój identyfikator jest kolumną auto_increment). Powinieneś utworzyć kolejną kolumnę w tabeli, która wyraźnie przedstawia porządek sortowania.
Godny Dabbler

Odpowiedzi:


262

Kolejny:

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

poprzedni:

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

7
Myślę, że podzapytania powinny być (wybierz min (id) z foo, gdzie id> 4) i (wybierz max (id) z foo, gdzie id <4).
Godny Dabbler

1
masz rację, zapomniałem klauzuli FROM. dzięki za wskazanie tego. :)
longneck

2
To nie jest rozwiązanie, ponieważ jeśli usuniesz wiersz z „foo”, co się stanie? : P
Valentin Hristov

@valentinhristov Nie rozumiem, co mówisz. Proszę podać przykład.
longneck,

@longneck Jeśli opublikujesz post po nagraniu, wszystko jest w porządku. Ale jeśli publikujesz treść po identyfikatorze wersji, nie jest to kryterium pozycji w kolejności.
Valentin Hristov

143

Oprócz rozwiązania Cemkalyoncu :

następny rekord:

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

poprzednie nagranie:

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

edycja: Ponieważ ta odpowiedź otrzymała ostatnio kilka pozytywnych głosów, naprawdę chcę podkreślić komentarz, który zrobiłem wcześniej, dotyczący zrozumienia, że ​​kolumna klucza podstawowego nie jest przeznaczona jako kolumna do sortowania, ponieważ MySQL nie gwarantuje tego wyższego, automatycznie zwiększanego wartości są koniecznie dodawane w późniejszym czasie.

Jeśli Cię to nie obchodzi i po prostu potrzebujesz rekordu z wyższym (lub niższym), idto wystarczy. Po prostu nie używaj tego jako środka do określenia, czy rekord został faktycznie dodany później (lub wcześniej). Zamiast tego rozważ użycie kolumny z datą i godziną do sortowania według, na przykład.


O wiele bardziej wolę tę odpowiedź, sortuję według sygnatury czasowej i oznacza to brak podzapytań, więc łącznie 2 zapytania zamiast 4.
Maurice

62

Wszystkie powyższe rozwiązania wymagają dwóch wywołań bazy danych. Poniższy kod sql łączy dwie instrukcje sql w jedną.

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
Zdecydowanie najlepsze rozwiązanie. Szybko i wszystko w jednym zapytaniu.
demon

Działa perfekcyjnie nawet w znacznie bardziej skomplikowanym zapytaniu!
taiar

Dodanie DISTINCTwcześniej *uczyni go jeszcze lepszym, to znaczy oczywiście, jeśli idmoże mieć 0wartość.
Ejaz

Doskonałe rozwiązanie. Na pierwszym i ostatnim albumie zwróci singiel idzamiast dwóch.
lubosdz

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

1
+1 Myślę, że to najczystsze rozwiązanie. Ale klauzula LIMIT powinna być ostatnia: SELECT * FROM foo WHERE id> 4 ORDER BY id LIMIT 1
Decent Dabbler

i SELECT * FROM foo WHERE id<4 ORDER BY id DESC LIMIT 1dla poprzedniego rekordu
fwonce

12

Próbowałem zrobić coś podobnego, ale potrzebowałem wyników uporządkowanych według daty, ponieważ nie mogę polegać na polu ID jako kolumnie z możliwością sortowania. Oto rozwiązanie, które wymyśliłem.

Najpierw znajdujemy indeks żądanego rekordu w tabeli, kiedy jest posortowany tak, jak chcemy:

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;

Następnie pomniejsz wynik o 2 i umieść go w instrukcji limitu. Na przykład powyższe zwróciło mi 21, więc biegnę:

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

Daje Ci swój główny rekord wraz z jego następnymi i poprzednimi rekordami w podanej kolejności.

Próbowałem to zrobić jako pojedyncze wywołanie bazy danych, ale nie mogłem uzyskać instrukcji LIMIT, aby przyjąć zmienną jako jeden z jej parametrów.


Interesuje mnie Twoje podejście. A co, jeśli musisz również wykonać JOIN? Powiedz, Z artykułów a DOŁĄCZ do autorów b ON b.this = a.that. Wydaje mi się, że JOIN psuje porządkowanie. Oto przykład pastebin.com/s7R62GYV
idrarig

1
Aby wykonać JOIN, musisz zdefiniować dwie różne @variables. `SELECT current.row, current.id, previous.row, previous.id FROM (SELECT @rownum: = @ rownum + 1 wiersz, a. * Z artykułów a, (SELECT @rownum: = 0) r ORDER BY date, id) as current_row LEFT JOIN (SELECT @ rownum2: = @ rownum2 + 1 wiersz, a. * FROM artykułów 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

7

Wypróbuj ten przykład.

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; 

Uwaga: jeśli wartość nie zostanie znaleziona, zwróci wartość null .

W powyższym przykładzie Poprzednia wartość będzie Raja, a Następna wartość będzie zerowa, ponieważ nie ma następnej wartości.


7

Używając podejścia @Dan, możesz tworzyć JOINs. Po prostu użyj innej @variable dla każdego zapytania podrzędnego.

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)

Właśnie tego potrzebowałem, dzięki. Zauważ, że twój wybór nie jest poprawny, current-> current_row& previous-> previous_row.
Ludovic Guillaume

To zapytanie może być przydatne do ustalenia, czy rekordy zostały usunięte z tabeli (sekwencja kolumny auto_increment id jest zerwana), czy nie.
Dharmang

@LudovicGuillaume, czy możesz być bardziej szczegółowy. Przepraszam, nie rozumiem twojej uwagi. Ale zapytanie nie ma elementów w previous_row. *
Walter Schrabmair

4

Następny rząd

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

Poprzedni rząd

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

próbka następnego rzędu

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

próbka poprzedniego wiersza

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

Miałem ten sam problem co Dan, więc skorzystałem z jego odpowiedzi i poprawiłem ją.

Najpierw wybierz indeks wiersza, nic innego tutaj nie jest.

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;

Teraz użyj dwóch oddzielnych zapytań. Na przykład, jeśli indeks wiersza to 21, zapytanie o wybór następnego rekordu będzie wyglądać następująco:

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

Aby wybrać poprzedni rekord, użyj tego zapytania:

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

Pamiętaj, że dla pierwszego wiersza (indeks wiersza 1) limit wyniesie -1 i pojawi się błąd MySQL. Aby temu zapobiec, możesz użyć instrukcji if. Po prostu nie wybieraj niczego, ponieważ i tak nie ma poprzedniego rekordu. W przypadku ostatniego rekordu będzie następny wiersz i nie będzie żadnego wyniku.

Pamiętaj również, że jeśli używasz DESC do porządkowania, zamiast ASC, zapytania, aby wybrać następny i poprzedni wiersz, są nadal takie same, ale przełączane.


3

To uniwersalne rozwiązanie w warunkach dających takie same rezultaty.

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

Tutaj mamy sposób na pobranie poprzednich i następnych rekordów za pomocą pojedynczego zapytania MySQL. Gdzie 5 to identyfikator bieżącego rekordu.

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

Jak zdobyć następny / poprzedni rekord w MySQL i PHP?

Mój przykład to zdobycie tylko identyfikatora

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

Negocjowany, ponieważ jest podatny na iniekcję SQL . Wiem, że to stary post, ale początkujący programiści powinni o tym wiedzieć.
ayrtonvwf

2

Optymalizacja podejścia @Don, aby używać tylko jednego zapytania

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

zmień CurrentArticleID na bieżący identyfikator artykułu, np

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

To najbardziej przydatna odpowiedź, jaką dotychczas znalazłem. Jedynym prawdziwym ulepszeniem, które zasugerowałbym, jest użycie zmiennych i przeniesienie wszystkich unikalnych części na górę, aby można je było łatwo edytować. Następnie można go łatwo przekształcić w często przywoływaną funkcję lub proces.
liljoshu

1

Jest jeszcze jedna sztuczka, której możesz użyć, aby wyświetlić kolumny z poprzednich wierszy, używając dowolnej kolejności, używając zmiennej podobnej do sztuczki @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

Wygląda na to, że wybrane kolumny są oceniane w kolejności, więc najpierw wybierze zapisane zmienne, a następnie zaktualizuje zmienne do nowego wiersza (wybierając je w procesie).

NB: Nie jestem pewien, czy jest to określone zachowanie, ale użyłem go i działa.


1

Jeśli chcesz podać więcej niż jednoid zapytanie i uzyskać next_idje wszystkie ...

Przypisz cur_idw wybranym polu, a następnie wprowadź je do podzapytania, aby dostać się next_iddo wybranego pola. A następnie wybierz tylko next_id.

Korzystanie z odpowiedzi Longneck do 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
Witamy w StackOverflow. Odpowiedzi składające się w całości z kodu rzadko są przydatne. Zastanów się nad wyjaśnieniem, co robi Twój kod. Pomocna byłaby również dokumentacja w kodzie i / lub nazwy zmiennych w języku angielskim.
Politank-Z

1

Jeśli masz kolumnę indeksu, powiedz id, możesz zwrócić poprzedni i następny identyfikator w jednym żądaniu sql. Zastąp: id swoją wartością

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

Moje rozwiązanie, aby uzyskać następny i podgląd nagrania, aby wrócić do pierwszego rekordu, jeśli jestem na ostatnim i odwrotnie

Nie używam identyfikatora używam tytułu dla ładnych adresów URL

Używam 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;
    }     
}

Negocjowany, ponieważ jest podatny na iniekcję SQL . Wiem, że to stary post, ale początkujący programiści powinni o tym wiedzieć.
ayrtonvwf

0

Oto moja odpowiedź. Biorę pomysł z „ Decent Dabbler ” i dodaję część sprawdzającą, czy id jest między min (id) a max (id). Oto część dotycząca tworzenia mojego stołu.

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

);

Kolejnym krokiem jest utworzenie procedury składowanej odpowiedzialnej za pobranie poprzedniego identyfikatora.

    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

Pierwsza metoda jest dobra, jeśli indeksy są posortowane, ale jeśli tak nie jest. Na przykład, jeśli masz indeksy: 1,2,7 i potrzebujesz uzyskać numer 2 w ten sposób, znacznie lepiej użyj innego podejścia.

    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
Polujesz na odznakę nekromanty? To pytanie ma akceptowaną odpowiedź od 10 lat.
Jakub Arnold

Właśnie znalazłem ten temat, bo ostatnio mam ten sam problem.
Dennis

0

100% sprawne

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

-1

Myślę, że aby mieć rzeczywisty następny lub poprzedni wiersz w tabeli SQL, potrzebujemy wartości rzeczywistej z równymi, (<lub>) zwracającymi więcej niż jeden, jeśli chcesz zmienić pozycję wiersza w tabeli zamówień.

potrzebujemy wartości $positiondo przeszukania neighbourswiersza W mojej tabeli utworzyłem kolumnę 'pozycja'

a zapytanie SQL do uzyskania potrzebnego wiersza to:

w następnym :

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

za poprzednie:

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

Jeśli wiersz został usunięty, to się zepsuje. np .: jeśli ID to 1,2,6,7,8.
Kevin Waterson

-2

Wybierz pierwsze 1 * z foo, gdzie id> 4 uporządkuj według id rosnąco


1
Odpowiednikiem MySQL jest „LIMIT 1”: select * from foo where id>4 order by id asc LIMIT 1
mob
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.