MySQL obtém a posição da linha em ORDER BY

86

Com a seguinte tabela MySQL:

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

Como posso selecionar uma única linha E sua posição entre as outras linhas da tabela, quando classificado por name ASC. Portanto, se os dados da tabela forem assim, quando classificados por nome:

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

Como posso selecionar a Betalinha obtendo a posição atual dessa linha? O conjunto de resultados que procuro seria algo assim:

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

Posso fazer um SELECT * FROM tbl ORDER BY name ASCprocedimento simples e, em seguida, enumerar as linhas em PHP, mas parece um desperdício carregar um conjunto de resultados potencialmente grande apenas para uma única linha.

leepowers
fonte
stackoverflow.com/questions/2520357/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Possível duplicata do MySQL - Obter número da linha na seleção
jberryman

Respostas:

120

Usa isto:

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'

... para obter um valor de posição único. Isto:

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

... dará aos laços o mesmo valor. IE: Se houver dois valores em segundo lugar, ambos terão uma posição 2 quando a primeira consulta dará uma posição 2 para um deles e 3 para o outro ...

Pôneis OMG
fonte
@actual: Não há nada a dizer - não há alternativa, a não ser mudar para um concorrente que ofereça suporte a funções analíticas (PostgreSQL, Oracle, SQL Server, DB2 ...)
OMG Ponies
2
@OMGPonies Apenas esqueça uma vírgula depois position, mas é perfeito.
pierallard,
Eu sei que é um post bastante antigo, mas preciso de uma solução semelhante. No entanto, ao usar junções internas, agrupar por e ordenar por, o campo "posição" os ignora e o valor fica todo misturado. Alguma solução?
Daniel
@Daniel, você deve fazer uma nova pergunta e talvez referir-se a esta.
PeerBr
@actual, uma vez que esta é uma consulta para uma única linha, não deve haver uma preocupação significativa de desempenho aqui. É diferente se você está tentando obter as classificações de uma lista completa, mas você pode apenas "trapacear" e usar uma classificação implícita ordenando por pontos.
AgmLauncher
20

Esta é a única maneira que consigo pensar:

SELECT `id`,
       (SELECT COUNT(*) FROM `table` WHERE `name` <= 'Beta') AS `position`,
       `name`
FROM `table`
WHERE `name` = 'Beta'
zerkms
fonte
2
+1 Belo truque ... No entanto, você provavelmente gostaria de usar em seu name <= 'Beta'lugar
Daniel Vassallo
Esta abordagem dará os mesmos positionvalores para empates.
Pôneis OMG
(Excluí meu comentário anterior - estava errado) ... E se você adicionar um LIMIT 1lá? Em caso de empate, você teria apenas uma linha com a última posição do empate.
Daniel Vassallo
Se o OP pode garantir que o namecampo é exclusivo - então não há razão para tornar a consulta mais complexa. Se ele não puder - então vamos esperar suas expectativas de resultado para nomes empatados.
zerkms 01 de
8

Se a consulta for simples e o tamanho do conjunto de resultados retornado for potencialmente grande, você pode tentar dividi-lo em duas consultas.

A primeira consulta com um critério de filtragem restrito apenas para recuperar os dados dessa linha, e a segunda consulta usa COUNTa WHEREcláusula com para calcular a posição.

Por exemplo, no seu caso

Consulta 1:

SELECT * FROM tbl WHERE name = 'Beta'

Consulta 2:

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

Usamos essa abordagem em uma tabela com registro de 2M e é muito mais escalável do que a abordagem dos pôneis OMG.

Max
fonte
4

As outras respostas parecem muito complicadas para mim.

Aqui está um exemplo fácil , digamos que você tenha uma tabela com colunas:

userid | points

e você deseja classificar as IDs de usuário por pontos e obter a posição da linha (a "classificação" do usuário), então você usa:

SET @row_number = 0;

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

num fornece a posição da linha (classificação).

Se você tem MySQL 8.0+ então você pode querer usar ROW_NUMBER ()

Kai Noack
fonte
2

A posição de uma linha na tabela representa quantas linhas são "melhores" do que a linha desejada.

Portanto, você deve contar essas linhas.

SELECIONE A CONTAGEM (*) + 1 DE tableONDE name<'Beta'

Em caso de empate, a posição mais alta é devolvida.

Se você adicionar outra linha com o mesmo nome de "Beta" após a linha "Beta" existente, a posição retornada ainda será 2, pois eles compartilham o mesmo lugar na classificação.

Espero que ajude pessoas que vão buscar algo semelhante no futuro, pois acredito que o dono da questão já resolveu o problema.

NVG
fonte
2

Estou com um problema muito parecido, por isso não vou fazer a mesma pergunta, mas vou compartilhar aqui o que eu fiz, tive que usar também um agrupar por e ordenar por AVG. Existem alunos, com assinaturas e socore, e eu tive que classificá-los (em outras palavras, eu primeiro calculei o AVG, depois ordenei em DESC e, finalmente, precisei adicionar a posição (classificação para mim). algo muito parecido com a melhor resposta aqui, com algumas pequenas mudanças que se ajustam ao meu problema):

Eu coloquei finalmente a positioncoluna (classificação para mim) no SELECT externo

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
fonte
Essa resposta foi mais fácil de entender do que a aceita. +1
Eric Seastrand
1

Eu estava analisando a resposta aceita e parecia um pouco complicado, então aqui está a versão simplificada dela.

SELECT t,COUNT(*) AS position FROM t      
 WHERE name <= 'search string' ORDER BY name
Davinder Singh
fonte
0

Tenho tipos semelhantes de problemas em que exijo classificação ( índice ) da mesa order by votes desc. O seguinte funciona bem para mim.

Select *, ROW_NUMBER() OVER(ORDER BY votes DESC) as "rank"
From "category_model"
where ("model_type" = ? and "category_id" = ?)
Bedram Tamang
fonte
-9

pode ser o que você precisa é adicionar sintaxe

LIMIT

então use

SELECT * FROM tbl ORDER BY name ASC LIMIT 1

se você só precisa de uma linha ..

Eka Rudito
fonte
esta resposta não resolve o problema aqui. Você pode considerar excluí-lo
Damián Rafael Lattenero