MySQL pobiera pozycję wiersza w ORDER BY

86

Z następującą tabelą MySQL:

+-----------------------------+
+ id INT UNSIGNED             +
+ name VARCHAR(100)           +
+-----------------------------+

Jak mogę wybrać pojedynczy wiersz ORAZ jego pozycję wśród innych wierszy w tabeli, po posortowaniu według name ASC. Więc jeśli dane w tabeli wyglądają tak, po posortowaniu według nazwy:

+-----------------------------+
+ id | name                   +
+-----------------------------+
+  5 | Alpha                  +
+  7 | Beta                   +
+  3 | Delta                  +
+ .....                       +
+  1 | Zed                    +
+-----------------------------+

Jak mogę wybrać Betawiersz pobierający bieżącą pozycję tego wiersza? Zestaw wyników, którego szukam, wyglądałby mniej więcej tak:

+-----------------------------+
+ id | position | name        +
+-----------------------------+
+  7 |        2 | Beta        +
+-----------------------------+

Mogę zrobić proste, SELECT * FROM tbl ORDER BY name ASCa następnie wyliczyć wiersze w PHP, ale ładowanie potencjalnie dużego zestawu wyników tylko dla jednego wiersza wydaje się marnotrawstwem.

leepowers
źródło
stackoverflow.com/questions/2520357/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Możliwy duplikat MySQL - Uzyskaj numer wiersza przy wyborze
jberryman

Odpowiedzi:

120

Użyj tego:

SELECT x.id, 
       x.position,
       x.name
  FROM (SELECT t.id,
               t.name,
               @rownum := @rownum + 1 AS position
          FROM TABLE t
          JOIN (SELECT @rownum := 0) r
      ORDER BY t.name) x
 WHERE x.name = 'Beta'

... aby uzyskać unikalną wartość pozycji. To:

SELECT t.id,
       (SELECT COUNT(*)
          FROM TABLE x
         WHERE x.name <= t.name) AS position,
       t.name    
  FROM TABLE t      
 WHERE t.name = 'Beta'

... da krawatom tę samą wartość. IE: Jeśli na drugim miejscu znajdują się dwie wartości, obie będą miały pozycję 2, podczas gdy pierwsze zapytanie da pozycję 2 jednej z nich, a 3 drugiej ...

Kucyki OMG
źródło
@actual: Nie ma nic do powiedzenia - nie ma innej alternatywy niż przejście do konkurenta obsługującego funkcje analityczne (PostgreSQL, Oracle, SQL Server, DB2 ...)
OMG Ponies
2
@OMGPonies Po prostu zapomnij o przecinku position, ale jest doskonały.
pierallard
Wiem, że to dość stary post, ale potrzebuję podobnego rozwiązania. Jednak podczas korzystania ze złączeń wewnętrznych, grupowania i porządkowania według, pole „pozycja” ignoruje je, a wartość jest pomieszana. Jakieś rozwiązania?
Daniel
@Daniel powinieneś zadać nowe pytanie i być może odnieść się do tego.
PeerBr
@actual, ponieważ jest to zapytanie dotyczące pojedynczego wiersza, nie powinno tu być większego problemu z wydajnością. Inaczej jest, jeśli próbujesz uzyskać rangi z pełnej listy, ale możesz po prostu „oszukiwać” i używać ukrytej rangi, porządkując według punktów.
AgmLauncher
20

To jedyny sposób, jaki przychodzi mi do głowy:

SELECT `id`,
       (SELECT COUNT(*) FROM `table` WHERE `name` <= 'Beta') AS `position`,
       `name`
FROM `table`
WHERE `name` = 'Beta'
zerkms
źródło
2
+1 Niezła sztuczka ... Jednak prawdopodobnie zechcesz użyć name <= 'Beta'zamiast tego
Daniel Vassallo
Takie podejście da te same positionwartości dla krawatów.
Kucyki OMG
(Usunąłem mój poprzedni komentarz - myliłem się) ... A co jeśli dodasz LIMIT 1tam? W przypadku remisu otrzymujesz tylko jeden wiersz z ostatnią pozycją remisu.
Daniel Vassallo
Jeśli OP może zagwarantować, że namepole jest unikalne - nie ma powodu, aby zapytanie było bardziej złożone. Jeśli nie może - poczekajmy na jego oczekiwania dotyczące wyników dla remisujących nazwisk.
zerkms
8

Jeśli zapytanie jest proste, a rozmiar zwracanego zestawu wyników jest potencjalnie duży, możesz spróbować podzielić je na dwa zapytania.

Pierwsze zapytanie z zawężonymi kryteriami filtrowania tylko w celu pobrania danych z tego wiersza, a drugie zapytanie używa klauzuli COUNTwith WHEREdo obliczenia pozycji.

Na przykład w twoim przypadku

Zapytanie 1:

SELECT * FROM tbl WHERE name = 'Beta'

Zapytanie 2:

SELECT COUNT(1) FROM tbl WHERE name >= 'Beta'

Stosujemy to podejście w tabeli z rekordem 2M i jest to o wiele bardziej skalowalne niż podejście OMG Ponies.

Maks
źródło
4

Inne odpowiedzi wydają mi się zbyt skomplikowane.

Oto prosty przykład , powiedzmy, że masz tabelę z kolumnami:

userid | points

i chcesz posortować identyfikatory użytkowników według punktów i uzyskać pozycję w wierszu („ranking” użytkownika), a następnie użyj:

SET @row_number = 0;

SELECT 
    (@row_number:=@row_number + 1) AS num, userid, points
FROM
    ourtable
ORDER BY points DESC

num daje pozycję w wierszu (ranking).

Jeśli masz MySQL 8.0+, możesz użyć ROW_NUMBER ()

Kai Noack
źródło
2

Pozycja wiersza w tabeli określa, ile wierszy jest „lepszych” niż wiersz docelowy.

Więc musisz policzyć te wiersze.

WYBIERZ LICZBĘ (*) + 1 Z tableGDZIE name<„Beta”

W przypadku remisu zwracana jest najwyższa pozycja.

Jeśli dodasz kolejny wiersz o tej samej nazwie „Beta” po istniejącym wierszu „Beta”, zwrócona pozycja będzie nadal wynosić 2, ponieważ zajmowałyby to samo miejsce w klasyfikacji.

Mam nadzieję, że pomoże to ludziom, którzy będą szukać czegoś podobnego w przyszłości, ponieważ uważam, że właściciel pytania już rozwiązał swój problem.

NVG
źródło
2

Mam bardzo podobny problem, dlatego nie zadam tego samego pytania, ale podzielę się tutaj, co zrobiłem, musiałem też skorzystać z grupowania i zamówienia przez AVG. Są studenci, z podpisami i innymi, i musiałem ich uszeregować (innymi słowy, najpierw obliczyłem AVG, potem zamówiłem w DESC, a na koniec musiałem dodać pozycję (ranga dla mnie), więc zrobiłem coś bardzo podobnego do najlepszej odpowiedzi tutaj, z małymi zmianami, które dostosowują się do mojego problemu):

Ostatecznie umieściłem positionkolumnę (ranga dla mnie) w zewnętrznym SELECT

SET @rank=0;
SELECT @rank := @rank + 1 AS ranking, t.avg, t.name
  FROM(SELECT avg(students_signatures.score) as avg, students.name as name
FROM alumnos_materia
JOIN (SELECT @rownum := 0) r
left JOIN students ON students.id=students_signatures.id_student
GROUP BY students.name order by avg DESC) t 
Damián Rafael Lattenero
źródło
Ta odpowiedź była łatwiejsza do zrozumienia niż zaakceptowana. +1
Eric Seastrand
1

Sprawdzałem zaakceptowaną odpowiedź i wydawało mi się to trochę skomplikowane, więc tutaj jest jej uproszczona wersja.

SELECT t,COUNT(*) AS position FROM t      
 WHERE name <= 'search string' ORDER BY name
Davinder Singh
źródło
0

Mam podobne problemy, w których wymagam rangi ( indeksu ) tabeli order by votes desc. Poniższe działa dobrze dla mnie.

Select *, ROW_NUMBER() OVER(ORDER BY votes DESC) as "rank"
From "category_model"
where ("model_type" = ? and "category_id" = ?)
Bedram Tamang
źródło
-9

może być tym, czego potrzebujesz, z dodawaniem składni

LIMIT

więc użyj

SELECT * FROM tbl ORDER BY name ASC LIMIT 1

jeśli potrzebujesz tylko jednego wiersza ...

Eka Rudito
źródło
ta odpowiedź nie rozwiązuje problemu. Możesz rozważyć usunięcie go
Damián Rafael Lattenero