Com o MySQL, como posso gerar uma coluna contendo o índice de registro em uma tabela?


100

Existe alguma maneira de obter o número real da linha de uma consulta?

Eu quero ser capaz de ordenar uma mesa chamada league_girl por um campo chamado score; e retornar o nome de usuário e a posição real da linha desse nome de usuário.

Estou querendo classificar os usuários para que eu possa dizer onde um determinado usuário está, ou seja, Joe está na posição 100 de 200, ou seja,

User Score Row
Joe  100    1
Bob  50     2
Bill 10     3

Eu vi algumas soluções aqui, mas tentei a maioria delas e nenhuma delas realmente retorna o número da linha.

Eu tentei isso:

SELECT position, username, score
FROM (SELECT @row := @row + 1 AS position, username, score 
       FROM league_girl GROUP BY username ORDER BY score DESC) 

Como derivado

... mas não parece retornar a posição da linha.

Alguma ideia?


linha é um nome de campo ou deseja ordenar por chave primária?
Sarfraz

No SQL, os números das linhas não são realmente importantes. O que você deve fazer é adicionar uma chave primária de incremento automático à sua tabela.
simendsjo

9
A chave primária NUNCA deve ser um identificador de linha, pois eles não são confiáveis ​​para a posição real da linha.
TheBounder

3
Além disso, como o número da linha seria uma função da pontuação que eu suponho não ser um valor estático, torná-lo um valor incrementado automaticamente (chave primária ou não) não daria o resultado pretendido.
kasperjj

você pode querer salvar o feio para uma função personalizada, consulte datamakessense.com/mysql-rownum-row-number-function
AdrianBR

Respostas:


174

Você pode tentar o seguinte:

SELECT  l.position, 
        l.username, 
        l.score,
        @curRow := @curRow + 1 AS row_number
FROM    league_girl l
JOIN    (SELECT @curRow := 0) r;

A JOIN (SELECT @curRow := 0)parte permite a inicialização da variável sem exigir um SETcomando separado .

Caso de teste:

CREATE TABLE league_girl (position int, username varchar(10), score int);
INSERT INTO league_girl VALUES (1, 'a', 10);
INSERT INTO league_girl VALUES (2, 'b', 25);
INSERT INTO league_girl VALUES (3, 'c', 75);
INSERT INTO league_girl VALUES (4, 'd', 25);
INSERT INTO league_girl VALUES (5, 'e', 55);
INSERT INTO league_girl VALUES (6, 'f', 80);
INSERT INTO league_girl VALUES (7, 'g', 15);

Consulta de teste:

SELECT  l.position, 
        l.username, 
        l.score,
        @curRow := @curRow + 1 AS row_number
FROM    league_girl l
JOIN    (SELECT @curRow := 0) r
WHERE   l.score > 50;

Resultado:

+----------+----------+-------+------------+
| position | username | score | row_number |
+----------+----------+-------+------------+
|        3 | c        |    75 |          1 |
|        5 | e        |    55 |          2 |
|        6 | f        |    80 |          3 |
+----------+----------+-------+------------+
3 rows in set (0.00 sec)

19
Você também pode inicializar @curRowsubstituindo a JOINinstrução por uma vírgula, como esta:FROM league_girl l, (SELECT @curRow := 0) r
Mike

2
@Mike: Isso é verdade. E isso provavelmente também é mais puro. Obrigado por compartilhar essa dica.
Daniel Vassallo

2
@smhnaji MySQL requer que cada "tabela derivada" receba um nome. Decidi chamá-lo de "r" :) ... Tem pouca utilidade neste caso, mas normalmente você o usaria para fazer referência a atributos da tabela derivada, como se fosse uma tabela real.
Daniel Vassallo

20
As pessoas devem estar cientes de que esse número de linha é calculado antes que qualquer pedido ocorra, portanto, os números podem ficar confusos se a ordem mudar a ordem das linhas.
Grim ...

7
Existe uma maneira de calcular o número da linha após o ORDER BY?
Pierre de LESPINAY

38
SELECT @i:=@i+1 AS iterator, t.*
FROM tablename t,(SELECT @i:=0) foo

existe uma maneira de fazer isso para que a coluna do iterador seja um número inteiro e não um decimal?
kraftydevil

7

Aí vem a estrutura do template que usei:

  select
          /*this is a row number counter*/
          ( select @rownum := @rownum + 1 from ( select @rownum := 0 ) d2 ) 
          as rownumber,
          d3.*
  from 
  ( select d1.* from table_name d1 ) d3

E aqui está meu código de trabalho:

select     
           ( select @rownum := @rownum + 1 from ( select @rownum := 0 ) d2 ) 
           as rownumber,
           d3.*
from
(   select     year( d1.date ), month( d1.date ), count( d1.id )
    from       maindatabase d1
    where      ( ( d1.date >= '2013-01-01' ) and ( d1.date <= '2014-12-31' ) )
    group by   YEAR( d1.date ), MONTH( d1.date ) ) d3

perfeito, funciona como encanto e template podem ser reutilizados com subconsulta como parâmetro ..
Zavael

Esta solução também funciona se sua consulta de base usar GROUP BY
Dave R

4

Você também pode usar

SELECT @curRow := ifnull(@curRow,0) + 1 Row, ...

para inicializar a variável do contador.


3
@curRowpode ainda ter um valor da última vez que você executou esta consulta na sessão atual.
Bill Karwin

Verdadeiro, mas apenas se você estiver consultando novamente na mesma instância de conexão. Variáveis ​​locais são descartadas automaticamente assim que a conexão é fechada.
Hearth

Sim, é isso que eu quis dizer quando disse "na sessão atual".
Bill Karwin

3

Supondo que o MySQL seja compatível, você pode fazer isso facilmente com uma subconsulta SQL padrão:

select 
    (count(*) from league_girl l1 where l2.score > l1.score and l1.id <> l2.id) as position,
    username,
    score
from league_girl l2
order by score;

Para grandes quantidades de resultados exibidos, isso será um pouco lento e você desejará alternar para uma autojunção.


3

Se você deseja apenas saber a posição de um usuário específico após a ordem de pontuação de campo, você pode simplesmente selecionar todas as linhas de sua tabela onde a pontuação de campo é maior do que a pontuação do usuário atual. E use o número da linha retornado + 1 para saber qual posição deste usuário atual.

Supondo que sua tabela league_girle seu campo primário sejam id, você pode usar isto:

SELECT count(id) + 1 as rank from league_girl where score > <your_user_score>

0

Achei a resposta original extremamente útil, mas também queria pegar um determinado conjunto de linhas com base nos números das linhas que estava inserindo. Como tal, envolvi toda a resposta original em uma subconsulta para que pudesse fazer referência ao número da linha que estava inserindo.

SELECT * FROM 
( 
    SELECT *, @curRow := @curRow + 1 AS "row_number"
    FROM db.tableName, (SELECT @curRow := 0) r
) as temp
WHERE temp.row_number BETWEEN 1 and 10;

Ter uma subconsulta em uma subconsulta não é muito eficiente, então valeria a pena testar se você obtém um resultado melhor fazendo com que seu servidor SQL trate essa consulta ou busque a tabela inteira e faça com que o aplicativo / servidor da web manipule as linhas após o fato .

Pessoalmente, meu servidor SQL não está muito ocupado, portanto, é preferível que ele lide com as subconsultas aninhadas.


0

Eu sei que o OP está pedindo uma mysqlresposta, mas como descobri que as outras respostas não estão funcionando para mim,

  • A maioria deles falham com order by
  • Ou eles são simplesmente muito ineficientes e tornam sua consulta muito lenta para uma tabela gorda

Portanto, para economizar tempo para outras pessoas como eu, apenas indexe a linha após recuperá-la do banco de dados

exemplo em PHP:

$users = UserRepository::loadAllUsersAndSortByScore();

foreach($users as $index=>&$user){
    $user['rank'] = $index+1;
}

exemplo em PHP usando deslocamento e limite para paginação:

$limit = 20; //page size
$offset = 3; //page number

$users = UserRepository::loadAllUsersAndSortByScore();

foreach($users as $index=>&$user){
    $user['rank'] = $index+1+($limit*($offset-1));
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.