web-dev-qa-db-ja.com

MySQLのROW_NUMBER()

MySQLにSQL Serverの関数ROW_NUMBER()を複製するための良い方法はありますか?

例えば:

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

それから、例えば、intRowを1に制限して、各col3ペアに対して最高の(col1, col2)を持つ単一行を取得する条件を追加できます。

255
Paul

(col1、col2)の各ペアに対して、単一の最大のcol3を持つ行が必要です。

これは グループ単位の最大値 です。これは最も一般的なSQL質問の1つです(それは簡単なはずですが、実際にはそうではないようです).

私はしばしばnull自己結合をふくよかに思っています:

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;

「一致するcol1、col2を持つ他の行がそれより高いcol3を持たないようなテーブルの行を取得します。」これに気付くでしょう。 、col3。それが問題であれば、後処理が必要になるかもしれません。)

94
bobince

MySQLにはランキング機能はありません。一番近いのは変数を使うことです。

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

それでは、それは私の場合どのように機能するのでしょうか。 col1とcol2のそれぞれに1つずつ、合計2つの変数が必要ですか。 col1が変更されたとき、Col2はどういうわけかリセットする必要があります..?

はい。それがOracleであれば、LEAD関数を使って次の値でピークに達することができます。ありがたいことに、Quassnoiは MySQLで実装する必要があるもののロジック をカバーしています。

194
OMG Ponies

私はいつもこのパターンに従うことになります。このテーブルを考えます:

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

あなたはこの結果を得ることができます:

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

このクエリを実行すると、変数を定義する必要がなくなります。

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

それが役立つことを願っています!

78
Mosty Mostacho
SELECT 
    @i:=@i+1 AS iterator, 
    t.*
FROM 
    tablename AS t,
    (SELECT @i:=0) AS foo
58
Peter Johnson

この記事をチェックして、MySQLでSQL ROW_NUMBER()とパーティションを模倣する方法を示してください。私はWordPressの実装でもこれと同じシナリオに遭遇しました。 ROW_NUMBER()が必要でしたが、そこにはありませんでした。

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

この記事の例では、フィールドによる単一のパーティションを使用しています。追加のフィールドで分割するには、次のようにします。

  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 

Concat_wsを使用すると、nullが処理されます。 int、date、およびvarcharを使用して、これを3つのフィールドに対してテストしました。お役に立てれば。このクエリを分割して説明している記事をチェックしてください。

26
birch

MySQL 8.0.0以上からはウィンドウ関数をネイティブに使うことができます。

1.4 MySQL 8.0の新機能

ウィンドウ関数

MySQLは現在、クエリからの各行に対して、その行に関連する行を使用して計算を実行するウィンドウ関数をサポートしています。これらには、RANK()、LAG()、NTILE()などの関数が含まれます。さらに、いくつかの既存の集約関数がウィンドウ関数として使用できるようになりました。たとえば、SUM()やAVG()などです。

ROW_NUMBER()over_clause

パーティション内の現在の行の番号を返します。行番号の範囲は1からパーティション行数までです。

ORDER BYは、行が番号付けされる順序に影響します。 ORDER BYがないと、行番号は不定です。

デモ:

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デモ

18
Lukasz Szozda

また、Mosty Mostachoのクエリコードを少し変更したソリューションにも投票します。

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

どちらも同じ結果になります。

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

テーブル用:

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

唯一の違いは、クエリがJOINとGROUP BYを使用しないことです。代わりに、ネストしたselectを使用します。

15
abcdn

関数を定義します。

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

それから私はすることができます:

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

今、あなたは副問い合わせを持っていません、それはあなたがビューの中に持つことはできません。

11
Quincy

MySQLにはrownumrow_num()のような関数はありませんが、回避方法は以下のとおりです。

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

mysqlでrow_numberを問い合わせる

set @row_number=0;
select (@row_number := @row_number +1) as num,id,name from sbs
6
user5528503

冠番機能は模倣することはできません。あなたはあなたが期待する結果を得るかもしれませんが、あなたはたぶんある段階でがっかりするでしょう。これはmysqlのドキュメントが言うことです:

SELECTなどの他のステートメントでは、期待どおりの結果が得られる可能性がありますが、これは保証されていません。次の文では、MySQLが最初に@aを評価してから2番目の代入を行うと考えるかもしれません。SELECT @a、@a:= @ a + 1、...;ただし、ユーザー変数を含む式の評価順序は未定義です。

よろしく、Georgi。

4
user3503199

私が一番うまくいくとわかった解決策は、このような副照会を使用することでした。

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

PARTITION BY列は、 '='と比較され、ANDで区切られます。 ORDER BY列は '<'または '>'と比較され、ORで区切られます。

少しコストがかかるとしても、私はこれが非常に柔軟であることがわかりました。

4
snydergd

MariaDB 10.2はRANK()、ROW_NUMBER()およびその他のいくつかのものを含む "ウィンドウ関数"を実装しています。

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

今月のPercona Liveでの講演に基づいて、彼らはかなり最適化されています。

構文はQuestionのコードと同じです。

3
Rick James

私は「PARTITION BY」の部分をカバーする簡単な答えを見ていないので、ここに私のものがあります:

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
  • ORDER BY句は、ROW_NUMBERのニーズを反映している必要があります。したがって、すでに明確な制限があります。このフォームの複数のROW_NUMBER「エミュレーション」を同時に持つことはできません。
  • 「計算列」の順序 問題。 mysqlでこれらの列を別の順序で計算している場合は、機能しない可能性があります。
  • この単純な例では、1つだけ入れましたが、いくつかの "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
    

もう少し遅くなりましたが、今日も私は同じニーズを抱えていたので、Googleで検索し、ついにPinal Daveの記事 http://blog.sqlauthority.com/2014/にある簡単な一般的なアプローチを試しました03/09/mysql-reset-row-group-by-row-number /

私はパウロの最初の質問(それは私の問題でもありました)に集中したかったので、私の解決策を実例としてまとめました。

2列に分割したいので、反復中に新しいグループが開始されたかどうかを識別するためにSET変数を作成します。

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

3は、MAKE_SETの最初のパラメーターで、SETに両方の値が必要であることを意味します(3 = 1 | 2)。もちろん、グループを構成する2つ以上の列がない場合は、MAKE_SET操作を削除できます。構造はまったく同じです。これは必要に応じて私のために働いています。明確なデモンストレーションをしてくれたPinal Daveに感謝します。

1
Miklos Krivan

これは解決策であるかもしれません:

SET @row_number = 0;

SELECT 
    (@row_number:=@row_number + 1) AS num, firstName, lastName
FROM
    employees
1
Rishabh Pandey

少し遅れますが、答えを探す人にも役立つかもしれません...

行/行番号の間の例 - 任意の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
1
Art

これにより、ROW_NUMBER()AND PARTITION BYと同じ機能を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
1
Alankar

クエリ内変数を使用しているほとんどの場合/すべてのここでの回答は、ドキュメントに記載されている事実を無視しているようです(言い換え)。

SELECTリストの項目を上から順に評価してはいけません。あるSELECT項目に変数を代入して別のSELECT項目に使用しないでください

そのようなものとして、彼らは通常間違った答えを見つけ出す危険があります。

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

これらがボトムアップで評価されると、行番号は機能しなくなります(パーティションなし)。

ですから、実行順序が保証されたものを使用する必要があります。 CASE WHEN WHEN:

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

概説すると、prevcolの割り当ての順序は重要です - prevcolは現在の行から値を割り当てる前に現在の行の値と比較されなければなりません(そうでなければ前の行のcol値ではなく現在の列のcol値になります) 。

これがどのように組み合わさるかです:

  • 最初のWHENが評価されます。この行のcolが前の行のcolと同じ場合、@ rがインクリメントされてCASEから返されます。この返り値は@rに格納されます。 MySQLの特徴は、assignmentが@rに代入されたものの新しい値を結果行に返すことです。

  • 結果セットの最初の行では、@ prevcolはNULL(副照会ではNULLに初期化されている)なので、この述部はfalseです。この最初の述語も、colが変更されるたびにfalseを返します(現在の行は前の行とは異なります)。これにより、2番目のWHENが評価されます。

  • 2番目のWHEN述語は常に偽であり、純粋に@prevcolに新しい値を代入するために存在します。この行のcolは前の行のcolとは異なるため(同じであれば最初のWHENが使用されていたはずです)、次回のテスト用に新しい値を割り当てる必要があります。代入が行われ、代入の結果がnullと比較され、nullと見なされるものがすべてfalseであるため、この述語は常にfalseです。しかし、少なくともそれを評価することは、この行からcolの値を保つという仕事をしたので、次の行のcol値に対して評価することができます。

  • 2番目のWHENはfalseなので、(col)で分割している列が変更された状況では、@から新しい値を与え、1から番号付けをやり直すのはELSEです。

これにより、次のような状況になります。

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

一般形式は次のとおりです。

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

脚注:

  • Pcolのpは "partition"を意味し、ocolのoは "order"を意味します - 一般的な形式では、見た目の乱れを減らすために変数名から "prev"を削除しました。

  • (@pcolX := colX) = nullを囲む括弧は重要です。これらがないと@pcolXにnullが代入されて物事はうまくいきません

  • 前の列の比較がうまくいくように、結果セットもパーティション列によって順序付けする必要があることは妥協です。このようにサブクエリでこれを解決することはできるかもしれませんが、LIMITが使用されていない限りサブクエリの順序付けは無視され、これが影響を与える可能性があると文書には書いてあります。パフォーマンス

  • このメソッドが機能するかどうかをテストする以外は詳しく調べていませんが、2番目のWHENの述語が最適化されてしまう危険がある場合(nullと比較してnull/falseなので、代入を実行するのは面倒) 、それも停止します。これは私の経験では起こらないようですが、それが合理的に起こり得るならば私は喜んでコメントを受け入れて、解決策を提案します。

  • @pcolX変数を作成するサブクエリで、@ pcolXを作成するnullを実際の列の型にキャストするのが賢明です。viz:select @pcol1 := CAST(null as INT), @pcol2 := CAST(null as DATE)

0
Caius Jard

MySQLはROW_NUMBER()をサポートしています バージョン 8.0以降

MySQL 8.0以降を使用している場合は、ROW_NUMBER()関数を調べてください。そうでなければ、ROW_NUMBER()関数をエミュレートしたことになります。

Row_number()は、最初の行の1から始まる行の連続番号を返すランク付け関数です。

旧バージョンの場合

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