ROW_NUMBER () no MySQL

281

Existe uma maneira agradável no MySQL de replicar a função SQL Server ROW_NUMBER()?

Por exemplo:

SELECT 
    col1, col2, 
    ROW_NUMBER() OVER (PARTITION BY col1, col2 ORDER BY col3 DESC) AS intRow
FROM Table1

Por exemplo, eu poderia adicionar uma condição para limitar intRowa 1 para obter uma única linha com a maior col3para cada (col1, col2)par.

Paulo
fonte
9
Marcado com greatest-n-per-grouppara guiá-lo para perguntas semelhantes.
Bill Karwin
1
Sql-ServerEtiqueta removida, pois era o item com o maior número de votos na pesquisa combinada, mas não é realmente relevante para o SQL Server.
Martin Smith
para uma função número da linha mysql simples, veja datamakessense.com/mysql-rownum-row-number-function
AdrianBR
Para o MySQL, as únicas soluções eficientes são discutidas em mysql.rjweb.org/doc.php/groupwise_max . Muitas soluções divulgadas exigem uma verificação completa da tabela (ou pior).
Rick James
2
Solução promover a auto descaradamente para real ROW_NUMBER função analítica, RANK, DESNSE_RANK no MySQL
Kenneth Xu

Respostas:

102

Eu quero a linha com o único col3 mais alto para cada par (col1, col2).

Esse é o máximo de grupo , uma das perguntas mais frequentes sobre SQL (já que parece ser fácil, mas na verdade não é).

Costumo insistir em uma auto-junção nula:

SELECT t0.col3
FROM table AS t0
LEFT JOIN table AS t1 ON t0.col1=t1.col1 AND t0.col2=t1.col2 AND t1.col3>t0.col3
WHERE t1.col1 IS NULL;

"Obtenha as linhas da tabela para as quais nenhuma outra linha com col1 correspondente, col2 tem um col3 mais alto." (Você notará que essa e a maioria das outras soluções de grupo máximo retornarão várias linhas se mais de uma linha tiver a mesma col1, col2, col3. Se for um problema, talvez seja necessário algum pós-processamento.)

bobince
fonte
2
Mas e se houver dois valores máximos de col3 para um par (col1, col2)? Você terminaria com duas linhas.
Paul
@ Paul: sim! Acabei de adicionar uma observação sobre isso na resposta de um tique atrás. Geralmente, você pode facilmente eliminar linhas extras indesejadas na camada do aplicativo posteriormente, de alguma forma aleatória, mas se você tiver muitas linhas, todas com a mesma col3, isso poderá ser problemático.
22909 bobince
1
bobince, a solução se tornou bastante popular aqui no SO, mas tenho uma pergunta. A solução é basicamente a mesma como se alguém tentasse encontrar o maior ID com a seguinte consulta: SELECT t1.id FROM test t1 LEFT JOIN test t2 ON t1.id>t2.id WHERE t2.id IS NULL;Não requer n*n/2 + n/2comparações IS NULL para encontrar a única linha? Ocorre alguma otimização que não vejo? Tentei fazer a pergunta semelhante a Bill em outro tópico, mas ele parece ter ignorado.
newtover
2
@Paul - Para resolver o caso em que existem várias linhas que correspondem ao máximo por grupo e você deseja capturar apenas uma, você sempre pode adicionar a chave primária na lógica da cláusula ON para quebrar o empate ... SELECT t0.col3 FROM table AS t0 LEFT JOIN tabela AS t1 ON t0.col1 = t1.col1 AND t0.col2 = t1.col2 AND (t1.col3, t1.pk)> (t0.col3, t0.pk) ONDE t1.col1 ESTÁ NULL;
Jon Armstrong - Xgc
2
Isso seria mais legível comoSELECT t0.col3 FROM table AS t0 WHERE NOT EXISTS (select 1 from table AS t1 ON t0.col1=t1.col1 AND t0.col2=t1.col2 AND t1.col3>t0.col3)
wrschneider 22/17/17
204

Não há funcionalidade de classificação no MySQL. O mais próximo que você pode chegar é usar uma variável:

SELECT t.*, 
       @rownum := @rownum + 1 AS rank
  FROM YOUR_TABLE t, 
       (SELECT @rownum := 0) r

então, como isso funcionaria no meu caso? Eu precisaria de duas variáveis, uma para cada col1 e col2? Col2 precisaria ser redefinido de alguma forma quando col1 mudou ..?

Sim. Se fosse o Oracle, você poderia usar a função LEAD para atingir o pico no próximo valor. Felizmente, Quassnoi cobre a lógica do que você precisa implementar no MySQL .

Pôneis OMG
fonte
1
Hmm .... então, como isso funcionaria no meu caso? Eu precisaria de duas variáveis, uma para cada col1 e col2? Col2 precisaria ser redefinido de alguma forma quando col1 mudou ..?
Paul
Obrigado ... como eu disse acima, esta resposta é igualmente aceite bobince de, mas eu só pode marcar a :-)
Paul
9
A atribuição e leitura de variáveis ​​definidas pelo usuário na mesma instrução não é confiável. isso está documentado aqui: dev.mysql.com/doc/refman/5.0/en/user-variables.html : "Como regra geral, você nunca deve atribuir um valor a uma variável do usuário e ler o valor na mesma instrução. Você pode obter os resultados esperados, mas isso não é garantido. A ordem de avaliação das expressões que envolvem variáveis ​​do usuário é indefinida e pode mudar com base nos elementos contidos em uma determinada instrução ".
Roland Bouman
1
@Roland: testei apenas em pequenos conjuntos de dados, não tive nenhum problema. Pena MySQL ainda tem que lidar com a funcionalidade - o pedido tenha sido em desde 2008
OMG Pôneis
2
Esse parece ser um comportamento indefinido, como observa Roland. por exemplo, isso dá resultados totalmente incorretas para uma tabela Tentei:SELECT @row_num:=@row_num+1 AS row_number, t.id FROM (SELECT * FROM table1 WHERE col = 264 ORDER BY id) t, (SELECT @row_num:=0) var;
jberryman
81

Eu sempre acabo seguindo esse padrão. Dada esta tabela:

+------+------+
|    i |    j |
+------+------+
|    1 |   11 |
|    1 |   12 |
|    1 |   13 |
|    2 |   21 |
|    2 |   22 |
|    2 |   23 |
|    3 |   31 |
|    3 |   32 |
|    3 |   33 |
|    4 |   14 |
+------+------+

Você pode obter este resultado:

+------+------+------------+
|    i |    j | row_number |
+------+------+------------+
|    1 |   11 |          1 |
|    1 |   12 |          2 |
|    1 |   13 |          3 |
|    2 |   21 |          1 |
|    2 |   22 |          2 |
|    2 |   23 |          3 |
|    3 |   31 |          1 |
|    3 |   32 |          2 |
|    3 |   33 |          3 |
|    4 |   14 |          1 |
+------+------+------------+

Executando esta consulta, que não precisa de nenhuma variável definida:

SELECT a.i, a.j, count(*) as row_number FROM test a
JOIN test b ON a.i = b.i AND a.j >= b.j
GROUP BY a.i, a.j

Espero que ajude!

Mosty Mostacho
fonte
1
se as colunas são VARCHAR ou CHAR, como você pode lidar com isso com essa estrutura?
Tushar
3
Você é incrível Mosty, eu estou olhando exatamente para isso
luckykrrish
Acabei de dar essa resposta usando sua lógica para row_number. Obrigado.
Utsav
@Tushar os operadores <, >, <=, >=CHAR punho e tipos de dados VARCHAR em ordem alfabética; Espero, é exatamente o que você está procurando.
18717 alex
1
@AlmazVildanov, você deve poder usar esta consulta simplesmente como uma subconsulta para filtrar row_numbers <= 2 E muito obrigado por essa resposta Mosty, é perfeito!
Zax
61
SELECT 
    @i:=@i+1 AS iterator, 
    t.*
FROM 
    tablename AS t,
    (SELECT @i:=0) AS foo
Peter Johnson
fonte
1
O primeiro: = parece estar ausente na resposta do @OMG Ponies. Obrigado por postar este Peter Johnson.
sholsinger
Eu acho que (SELECT @i: = 0) AS foo deve ser a primeira tabela na instrução FROM, especialmente se outras tabelas usarem sub-selects
andig
Por que você ainda precisa do '.. as foo'?
Tom Chiverton
@TomChiverton Se estiver faltando, você obtém: "Código de erro: 1248. Toda tabela derivada deve ter seu próprio alias"
ExStackChanger
1
A atribuição classificação aqui é completamente indefinido e este nem sequer responder à pergunta
jberryman
27

Confira este artigo, ele mostra como imitar o SQL ROW_NUMBER () com uma partição no MySQL. Encontrei esse mesmo cenário em uma implementação do WordPress. Eu precisava de ROW_NUMBER () e não estava lá.

http://www.explodybits.com/2011/11/mysql-row-number/

O exemplo no artigo está usando uma única partição por campo. Para particionar por campos adicionais, você pode fazer algo assim:

  SELECT  @row_num := IF(@prev_value=concat_ws('',t.col1,t.col2),@row_num+1,1) AS RowNumber
         ,t.col1 
         ,t.col2
         ,t.Col3
         ,t.col4
         ,@prev_value := concat_ws('',t.col1,t.col2)
    FROM table1 t,
         (SELECT @row_num := 1) x,
         (SELECT @prev_value := '') y
   ORDER BY t.col1,t.col2,t.col3,t.col4 

O uso de concat_ws manipula nulos. Testei isso em três campos usando int, date e varchar. Espero que isto ajude. Confira o artigo, que detalha essa consulta e a explica.

bétula
fonte
1
Impressionante. Isso realmente faz o particionamento. Muito útil
Stuart Watt
1
Comparando com a junção automática, isso é muito mais eficiente, mas há um problema com a lógica, a ordem deve ocorrer antes da computação row_num, concat também não é necessário. `` SELECT @row_num: = IF (@ prev_col1 = t.col1 AND @ prev_col2 = t.col2), @ row_num + 1, 1) AS RowNumber, t.col1, t.col2, t.col3, t.col4 , @ prev_col1: = t.col1, @ prev_col2: = t.col2 FROM (SELECT * FROM tabela1 ORDER BY col1, col2, col3) t, (SELECT @row_num: = 1, @ prev_col1: = '', @ prev_col2: = '') var `` `
Kenneth Xu
Se você precisar colocar isso em uma subconsulta, adicione limit 18446744073709551615a order bycláusula for force .
precisa saber é o seguinte
concat_wscom a corda vazia ''é perigoso: concat_ws('',12,3) = concat_ws('',1,23). Melhor usar algum separador '_'ou usar a solução @Kenneth Xu.
precisa saber é o seguinte
o link do op está morto; arquivo de link aqui
user2426679
25

De MySQL 8.0.0 e acima, você pode usar funções nativas em janelas.

1.4 O que há de novo no MySQL 8.0 :

Funções da janela.

O MySQL agora suporta funções de janela que, para cada linha de uma consulta, executam um cálculo usando linhas relacionadas a essa linha. Isso inclui funções como RANK (), LAG () e NTILE (). Além disso, várias funções agregadas existentes agora podem ser usadas como funções de janela; por exemplo, SUM () e AVG ().

Over_clause ROW_NUMBER () :

Retorna o número da linha atual dentro de sua partição. Os números das linhas variam de 1 ao número de linhas da partição.

ORDER BY afeta a ordem na qual as linhas são numeradas. Sem ORDER BY, a numeração das linhas é indeterminada.

Demo:

CREATE TABLE Table1(
  id INT AUTO_INCREMENT PRIMARY KEY, col1 INT,col2 INT, col3 TEXT);

INSERT INTO Table1(col1, col2, col3)
VALUES (1,1,'a'),(1,1,'b'),(1,1,'c'),
       (2,1,'x'),(2,1,'y'),(2,2,'z');

SELECT 
    col1, col2,col3,
    ROW_NUMBER() OVER (PARTITION BY col1, col2 ORDER BY col3 DESC) AS intRow
FROM Table1;

DBFiddle Demo

Lukasz Szozda
fonte
1
suspiro ... finalmente!
Used_By_Already
15

Também votaria na solução de Mosty Mostacho com pequenas modificações em seu código de consulta:

SELECT a.i, a.j, (
    SELECT count(*) from test b where a.j >= b.j AND a.i = b.i
) AS row_number FROM test a

O que dará o mesmo resultado:

+------+------+------------+
|    i |    j | row_number |
+------+------+------------+
|    1 |   11 |          1 |
|    1 |   12 |          2 |
|    1 |   13 |          3 |
|    2 |   21 |          1 |
|    2 |   22 |          2 |
|    2 |   23 |          3 |
|    3 |   31 |          1 |
|    3 |   32 |          2 |
|    3 |   33 |          3 |
|    4 |   14 |          1 |
+------+------+------------+

para a mesa:

+------+------+
|    i |    j |
+------+------+
|    1 |   11 |
|    1 |   12 |
|    1 |   13 |
|    2 |   21 |
|    2 |   22 |
|    2 |   23 |
|    3 |   31 |
|    3 |   32 |
|    3 |   33 |
|    4 |   14 |
+------+------+

Com a única diferença de que a consulta não usa JOIN e GROUP BY, dependendo da seleção aninhada.

abcdn
fonte
Isso deveria ser melhor? Ambos parecem susceptíveis de ser quadrática, mas não tenho certeza de como interprate a saída EXPLICAR
jberryman
De fato, sabe-se que as seleções aninhadas não são muito bem otimizadas no MySQL, portanto, essa resposta é apenas para demonstração de uma técnica de consulta. Os exemplos baseados em variáveis ​​acima funcionam melhor para a maioria dos casos práticos, suponho.
precisa saber é
1
Eu não estou convencido de que nenhuma das respostas variáveis com base estão realmente usando comportamento definido ...
jberryman
Sinto muito, não sei se entendi o que você quis dizer com "comportamento definido". Você quer dizer que não funciona para você ou está apenas preocupado que não esteja documentado?
abcdn
1
"Comportamento indefinido" significa que ele não está documentado para funcionar e / ou não está garantido para funcionar. Veja citações e links da documentação nos comentários desta página. Ele pode devolver o que um (unsoundly) quer / suposições / hypothesizes / fantasizes. Para certas versões da implementação, certas expressões de consulta usando CASE incrementando e usando variáveis ​​foram mostradas para trabalhar pelos programadores da Percona, observando o código. Isso pode mudar com qualquer versão.
philipxy
12

Eu definiria uma função:

delimiter $$
DROP FUNCTION IF EXISTS `getFakeId`$$
CREATE FUNCTION `getFakeId`() RETURNS int(11)
    DETERMINISTIC
begin
return if(@fakeId, @fakeId:=@fakeId+1, @fakeId:=1);
end$$

então eu poderia fazer:

select getFakeId() as id, t.* from table t, (select @fakeId:=0) as t2;

Agora você não tem uma subconsulta, que não pode ter em visualizações.

Quincy
fonte
Funciona com uma limitação: se você executar a consulta várias vezes, você receberá fakeIds cada vez maior para o mesmo conjunto de resultados
Stephan Richter
você pode enviar set @fakeId = 0; cada vez que você deseja executar a consulta, não ideal, mas funciona
jmpeace
Um problema realmente estranho acontece se você remover o DETERMINISTIC. Em seguida, o fakeId está incorreto ao usar a ordem de. Por que é isso?
31419 Chris Muench
8

consulta para row_number no mysql

set @row_number=0;
select (@row_number := @row_number +1) as num,id,name from sbs
user5528503
fonte
Isso pode ser usado em consultas UPDATE? Estou tentando, mas recebo um erro "dados truncados para coluna ...".
Diego
1
Se alguém estiver interessado em usá-lo em UPDATE, ele deverá ser usado como uma subconsulta para funcionar. UPDATE <table> SET <field> = (SELECT \ @row_number: = \ @row_number +1) ORDER BY <sua coluna de pedidos>; A coluna de ordem determina a ordem dos valores das linhas.
Diego
8

Não existe nenhuma função rownum, como row_num()no MySQL, mas o caminho a seguir é como abaixo:

select 
      @s:=@s+1 serial_no, 
      tbl.* 
from my_table tbl, (select @s:=0) as s;
Md. Kamruzzaman
fonte
4

A solução que achei melhor funcionou foi usar uma subconsulta como esta:

SELECT 
    col1, col2, 
    (
        SELECT COUNT(*) 
        FROM Table1
        WHERE col1 = t1.col1
        AND col2 = t1.col2
        AND col3 > t1.col3
    ) AS intRow
FROM Table1 t1

As colunas PARTITION BY são comparadas com '=' e separadas por AND. As colunas ORDER BY seriam comparadas com '<' ou '>' e separadas por OR.

Eu achei isso muito flexível, mesmo que seja um pouco caro.

snydergd
fonte
4

A funcionalidade de número de proprietário não pode ser imitada. Você pode obter os resultados esperados, mas provavelmente ficará decepcionado em algum momento. Aqui está o que a documentação do mysql diz:

Para outras instruções, como SELECT, você pode obter os resultados esperados, mas isso não é garantido. Na declaração a seguir, você pode pensar que o MySQL avaliará @a primeiro e depois fará uma atribuição segundo: SELECT @a, @a: = @ a + 1, ...; No entanto, a ordem de avaliação para expressões envolvendo variáveis ​​de usuário é indefinida.

Atenciosamente, Georgi.

user3503199
fonte
Eu não sigo. Como "@i: = @i + 1 como posição" não substitui diretamente "ROW_NUMBER () over (ordem pela soma (pontuação) desc) como posição"?
Tom Chiverton
1
@TomChiverton Porque seu comportamento não está definido, como diz o manual ali.
Philipxy
4

O MariaDB 10.2 está implementando "Funções da Janela", incluindo RANK (), ROW_NUMBER () e várias outras coisas:

https://mariadb.com/kb/en/mariadb/window-functions/

Com base em uma palestra no Percona Live este mês, eles estão razoavelmente bem otimizados.

A sintaxe é idêntica ao código na pergunta.

Rick James
fonte
2

Não vejo uma resposta simples que cubra a parte "PARTITION BY", então aqui está a minha:

SELECT
    *
FROM (
    select
        CASE WHEN @partitionBy_1 = l THEN @row_number:=@row_number+1 ELSE @row_number:=1 END AS i
        , @partitionBy_1:=l AS p
        , t.*
    from (
        select @row_number:=0,@partitionBy_1:=null
    ) as x
    cross join (
        select 1 as n, 'a' as l
        union all
        select 1 as n, 'b' as l    
        union all
        select 2 as n, 'b' as l    
        union all
        select 2 as n, 'a' as l
        union all
        select 3 as n, 'a' as l    
        union all    
        select 3 as n, 'b' as l    
    ) as t
    ORDER BY l, n
) AS X
where i > 1
  • A cláusula ORDER BY deve refletir sua necessidade de ROW_NUMBER. Portanto, já existe uma limitação clara: você não pode ter várias "emulações" de ROW_NUMBER desse formulário ao mesmo tempo.
  • A ordem da "coluna computada" é importante . Se o mysql computar essas colunas em outra ordem, pode não funcionar.
  • Neste exemplo simples, coloquei apenas um, mas você pode ter várias partes "PARTITION BY"

        CASE WHEN @partitionBy_1 = part1 AND @partitionBy_2 = part2 [...] THEN @row_number:=@row_number+1 ELSE @row_number:=1 END AS i
        , @partitionBy_1:=part1 AS P1
        , @partitionBy_2:=part2 AS P2
        [...] 
    FROM (
        SELECT @row_number:=0,@partitionBy_1:=null,@partitionBy_2:=null[...]
    ) as x
Serge Profafilecebook
fonte
1

Um pouco tarde, mas também pode ajudar alguém que procura respostas ...

Exemplo entre linhas / número da linha - consulta recursiva que pode ser usada em qualquer SQL:

WITH data(row_num, some_val) AS 
(
 SELECT 1 row_num, 1 some_val FROM any_table --dual in Oracle
  UNION ALL
 SELECT row_num+1, some_val+row_num FROM data WHERE row_num < 20 -- any number
)
SELECT * FROM data
 WHERE row_num BETWEEN 5 AND 10
/

ROW_NUM    SOME_VAL
-------------------
5           11
6           16
7           22
8           29
9           37
10          46
Arte
fonte
2
Desculpe, mas até onde eu sei, o MySQL não suporta expressões de tabela comuns .
Álvaro González
faz agora ... @ ÁlvaroGonzález MySQL 8 suporta apenas as funções CTE e janelas, assim que esta resposta não faz muito sentido para uso em versões mais antigas do MySQL ..
Raymond Nijland
1

Isso permite que a mesma funcionalidade que ROW_NUMBER () AND PARTITION BY fornece seja alcançada no MySQL

SELECT  @row_num := IF(@prev_value=GENDER,@row_num+1,1) AS RowNumber
       FirstName, 
       Age,
       Gender,
       @prev_value := GENDER
  FROM Person,
      (SELECT @row_num := 1) x,
      (SELECT @prev_value := '') y
  ORDER BY Gender, Age DESC
Alankar
fonte
1

Também um pouco tarde, mas hoje eu tinha a mesma necessidade, então pesquisei no Google e finalmente uma abordagem geral simples encontrada aqui no artigo de Pinal Dave http://blog.sqlauthority.com/2014/03/09/mysql-reset-row -número-para-cada-grupo-partição-por-número-de-linha /

Eu queria focar na pergunta original de Paul (que também era meu problema), então resumi minha solução como um exemplo de trabalho.

Porque queremos particionar em duas colunas, eu criaria uma variável SET durante a iteração para identificar se um novo grupo foi iniciado.

SELECT col1, col2, col3 FROM (
  SELECT col1, col2, col3,
         @n := CASE WHEN @v = MAKE_SET(3, col1, col2)
                    THEN @n + 1 -- if we are in the same group
                    ELSE 1 -- next group starts so we reset the counter
                END AS row_number,
         @v := MAKE_SET(3, col1, col2) -- we store the current value for next iteration
    FROM Table1, (SELECT @n := 0, @v := NULL) r -- helper table for iteration with startup values
   ORDER BY col1, col2, col3 DESC -- because we want the row with maximum value
) x WHERE row_number = 1 -- and here we select exactly the wanted row from each group

O 3 significa, no primeiro parâmetro de MAKE_SET, que eu quero os dois valores no SET (3 = 1 | 2). Obviamente, se não tivermos duas ou mais colunas construindo os grupos, podemos eliminar a operação MAKE_SET. A construção é exatamente a mesma. Isso está funcionando para mim, conforme necessário. Muito obrigado a Pinal Dave por sua demonstração clara.

Miklos Krivan
fonte
1
Observe que ORDER BYem uma subconsulta pode ser ignorado (consulte mariadb.com/kb/en/mariadb/… ). A solução sugerida para isso é adicionar LIMIT 18446744073709551615à subconsulta, o que força uma classificação. No entanto, isto pode causar problemas de desempenho e não é válida para realmente em pânico enormes mesas :)
pnomolos
1

Isso também pode ser uma solução:

SET @row_number = 0;

SELECT 
    (@row_number:=@row_number + 1) AS num, firstName, lastName
FROM
    employees
Rishabh Pandey
fonte
Ele não faz qualquer particionamento, porém, e não é significativamente diferente a um maior citada resposta
Caio Jard
1

O MySQL suporta o ROW_NUMBER () desde a versão 8.0+ .

Se você usa o MySQL 8.0 ou posterior, verifique a função ROW_NUMBER (). Caso contrário, você emulará a função ROW_NUMBER ().

O número da linha () é uma função de classificação que retorna um número seqüencial de uma linha, iniciando em 1 para a primeira linha.

para versão mais antiga,

SELECT t.*, 
       @rowid := @rowid + 1 AS ROWID
  FROM TABLE t, 
       (SELECT @rowid := 0) dummy;
Mohideen bin Mohammed
fonte
1

Importante: Considere a atualização para o MySQL 8+ e use a função ROW_NUMBER () definida e documentada e evite hacks antigos vinculados a uma versão antiga limitada do MySQL com um recurso

Agora, aqui está um desses hacks:

As respostas aqui que usam variáveis ​​na consulta principalmente / todas parecem ignorar o fato de que a documentação diz (paráfrase):

Não confie nos itens da lista SELECT que estão sendo avaliados em ordem de cima para baixo. Não atribua variáveis ​​em um item SELECT e use-as em outro

Como tal, existe o risco de que eles apresentem a resposta errada, porque geralmente fazem uma

select
  (row number variable that uses partition variable),
  (assign partition variable)

Se alguma vez for avaliado de baixo para cima, o número da linha deixará de funcionar (sem partições)

Portanto, precisamos usar algo com uma ordem de execução garantida. Digite CASE QUANDO:

SELECT
  t.*, 
  @r := CASE 
    WHEN col = @prevcol THEN @r + 1 
    WHEN (@prevcol := col) = null THEN null
    ELSE 1 END AS rn
FROM
  t, 
  (SELECT @r := 0, @prevcol := null) x
ORDER BY col

Como esboço ld, a ordem de atribuição do prevcol é importante - o prevcol deve ser comparado ao valor da linha atual antes de atribuirmos um valor a partir da linha atual (caso contrário, seria o valor da coluna da linha atual, não o valor da coluna da linha anterior) .

Veja como isso se encaixa:

  • O primeiro WHEN é avaliado. Se a coluna dessa linha for igual à coluna da linha anterior, @r será incrementado e retornado do CASE. Esses valores de led de retorno são armazenados em @r. É um recurso do MySQL que a atribuição retorna o novo valor do que é atribuído ao @r nas linhas de resultado.

  • Para a primeira linha do conjunto de resultados, @prevcol é nulo (inicializado como nulo na subconsulta), portanto, esse predicado é falso. Esse primeiro predicado também retorna false toda vez que a coluna é alterada (a linha atual é diferente da linha anterior). Isso faz com que o segundo WHEN seja avaliado.

  • O segundo predicado WHEN é sempre falso e existe apenas para atribuir um novo valor a @prevcol. Como o col da linha é diferente do col da linha anterior (sabemos disso porque, se fosse o mesmo, o primeiro WHEN teria sido usado), precisamos atribuir o novo valor para mantê-lo para teste na próxima vez. Como a atribuição é feita e, em seguida, o resultado da atribuição é comparado com nulo, e qualquer coisa equiparada a nulo é falsa, esse predicado é sempre falso. Mas, pelo menos, avaliar se fez o trabalho de manter o valor de col nessa linha, para que possa ser avaliado com relação ao valor de col da próxima linha

  • Como o segundo WHEN é falso, significa que nas situações em que a coluna pela qual particionamos (col) foi alterada, é o ELSE que fornece um novo valor para @r, reiniciando a numeração de 1

Nós chegamos a uma situação em que:

SELECT
  t.*, 
  ROW_NUMBER() OVER(PARTITION BY pcol1, pcol2, ... pcolX ORDER BY ocol1, ocol2, ... ocolX) rn
FROM
  t

Tem a forma geral:

SELECT
  t.*, 
  @r := CASE 
    WHEN col1 = @pcol1 AND col2 = @pcol2 AND ... AND colX = @pcolX THEN @r + 1 
    WHEN (@pcol1 := pcol1) = null OR (@pcol2 := col2) = null OR ... OR (@pcolX := colX) = null THEN null
    ELSE 1 
  END AS rn
FROM
  t, 
  (SELECT @r := 0, @pcol1 := null, @pcol2 := null, ..., @pcolX := null) x
ORDER BY pcol1, pcol2, ..., pcolX, ocol1, ocol2, ..., ocolX

Notas de rodapé:

  • OP in pcol significa "partição", o o ocol significa "ordem" - na forma geral, removi o "prev" do nome da variável para reduzir a desordem visual

  • Os suportes ao redor (@pcolX := colX) = nullsão importantes. Sem eles, você atribuirá null ao @pcolX e as coisas param de funcionar

  • É um compromisso que o conjunto de resultados também tenha que ser ordenado pelas colunas da partição, para que a comparação anterior funcione. Portanto, você não pode ter seu número de ordenador ordenado de acordo com uma coluna, mas seu conjunto de resultados ordenado para outro. Você pode resolver isso com subconsultas, mas acredito que os documentos também afirmam que a ordenação de subconsultas pode ser ignorada, a menos que LIMIT seja usado e isso possa afetar desempenho

  • Eu não investiguei além do teste de que o método funciona, mas se houver o risco de que os predicados no segundo WHEN sejam otimizados (qualquer coisa comparada a null é nula / falsa, por que se preocupar em executar a atribuição) e não executada , também para. Parece que isso não aconteceu na minha experiência, mas aceitarei com prazer comentários e proponho uma solução se isso ocorrer razoavelmente

  • Pode ser aconselhável converter os nulos que criam @pcolX nos tipos reais de suas colunas, na subconsulta que cria as variáveis ​​@pcolX, a saber: select @pcol1 := CAST(null as INT), @pcol2 := CAST(null as DATE)

Caius Jard
fonte
Não há justificativa para isso. Assim como as outras respostas atribuídas e lidas a partir da mesma variável.
philipxy
Você pode fornecer mais detalhes phil?
Caio Jard
Veja meus outros comentários nesta página. Googling 'site: stackoverflow.com variável mysql "philipxy" (definir OR atribuir OU atribuição OU escrever) ler': Uma resposta minha e um relatório de bug vinculado em um comentário meu nesta pergunta em que a resposta aceita cita o manual ainda imediatamente afirma que não há problema em fazer algo em contradição. Leia o manual de variáveis ​​e atribuição.
Philipxy
Eu entendo a sua preocupação
Caio Jard
0

Essa não é a solução mais robusta - mas se você está apenas procurando criar uma classificação particionada em um campo com apenas alguns valores diferentes, pode não ser difícil usar alguns casos quando for lógico com tantas variáveis ​​quantas forem necessárias.

Algo assim funcionou para mim no passado:

SELECT t.*, 
   CASE WHEN <partition_field> = @rownum1 := @rownum1 + 1 
     WHEN <partition_field> = @rownum2 := @rownum2 + 1 
     ...
     END AS rank
FROM YOUR_TABLE t, 
   (SELECT @rownum1 := 0) r1, (SELECT @rownum2 := 0) r2
ORDER BY <rank_order_by_field>
;

Espero que faça sentido / ajude!

bibzzzz
fonte
-1

Isso funciona perfeitamente para eu criar RowNumber quando temos mais de uma coluna. Neste caso, duas colunas.

SELECT @row_num := IF(@prev_value= concat(`Fk_Business_Unit_Code`,`NetIQ_Job_Code`), @row_num+1, 1) AS RowNumber, 
    `Fk_Business_Unit_Code`,   
    `NetIQ_Job_Code`,  
    `Supervisor_Name`,  
    @prev_value := concat(`Fk_Business_Unit_Code`,`NetIQ_Job_Code`)  
FROM (SELECT DISTINCT `Fk_Business_Unit_Code`,`NetIQ_Job_Code`,`Supervisor_Name`         
      FROM Employee    
      ORDER BY `Fk_Business_Unit_Code`, `NetIQ_Job_Code`, `Supervisor_Name` DESC) z,  
(SELECT @row_num := 1) x,  
(SELECT @prev_value := '') y  
ORDER BY `Fk_Business_Unit_Code`, `NetIQ_Job_Code`,`Supervisor_Name` DESC
ceregala
fonte
-3
set @i = 1;  
INSERT INTO ARG_VALUE_LOOKUP(ARG_VALUE_LOOKUP_ID,ARGUMENT_NAME,VALUE,DESCRIPTION,UPDATE_TIMESTAMP,UPDATE_USER,VER_NBR,OBJ_ID) 
select @i:= @i+1 as ARG_VALUE_LOOKUP_ID,ARGUMENT_NAME,VALUE,DESCRIPTION,CURRENT_TIMESTAMP,'admin',1,UUID() 
FROM TEMP_ARG_VALUE_LOOKUP 
order by ARGUMENT_NAME;
user4605644
fonte
1
Tente formatar as respostas e forneça algum contexto adicional sobre o que você está tentando fazer. No momento, não passa de um texto mal formatado.
Yannick Meeus
2
Parece que isso não tem nenhuma relação com a pergunta original. Se você tiver alguma dúvida, pergunte-a separadamente.
Jeroen Mostert
-5
SELECT 
    col1, col2, 
    count(*) as intRow
FROM Table1
GROUP BY col1,col2
ORDER BY col3 desc
Nickson Nyabote
fonte