web-dev-qa-db-ja.com

LinqのRow_numberオーバー(xxxによるパーティション)?

この構造とデータを持つDataTableがあります:

 id | inst |名前
 ------------------------ 
 1 |ギター| john 
 2 |ギター| george 
 3 |ギター| paul 
 4 |ドラム| ringo 
 5 |ドラム|ピート

次のようなレコードを取得できます。

IEnumerable <Beatle>...

class Beatle
{
  int id;
  string inst;
  string name;
}

さまざまな楽器を演奏する人の内部秩序を取得したいと思います。 MSSQLで使用します

SELECT 
    *
    ,Row_Number() OVER (PARTITION BY inst ORDER BY id) AS rn
FROM Beatles

このクエリは返します

 id | inst |名前| rn 
 ----------------------------- 
 1 |ギター|ジョン| 1 
 2 |ギター|ジョージ| 2 
 3 |ギター|ポール| 3 
 4 |ドラム|リンゴ| 1 
 5 |ドラム|ピート| 2 

Linqでどうすればいいですか?

編集。(回答が承認された後)

完全に機能するコード:

var beatles = (new[] { new { id=1 , inst = "guitar" , name="john" },
    new { id=2 , inst = "guitar" , name="george" },
    new { id=3 , inst = "guitar" , name="paul" },
    new { id=4 , inst = "drums" , name="ringo" },
    new { id=5 , inst = "drums" , name="pete" }
});

var o = beatles.OrderBy(x => x.id).GroupBy(x => x.inst)
               .Select(g => new { g, count = g.Count() })
               .SelectMany(t => t.g.Select(b => b)
                                   .Zip(Enumerable.Range(1, t.count), (j, i) => new { j.inst, j.name, rn = i }));

foreach (var i in o)
{
    Console.WriteLine("{0} {1} {2}", i.inst, i.name, i.rn);
}
35
Royi Namir

B "H

私はこれが古いことを知っています。しかし、なぜ解決策は単純ではないのですか?

var o = beatles.GroupBy(x => x.inst)
               .SelectMany(g =>
                   g.Select((j, i) => new { j.inst, j.name, rn = i + 1 })
               );
15
Rabbi

このライナーを試してください:

var o = beatles
    .OrderBy( x => x.id )
    .GroupBy( x => x.inst )
    .Select( group => new { Group = group, Count = group.Count() } )
    .SelectMany( groupWithCount =>
        groupWithCount.Group.Select( b => b)
        .Zip(
            Enumerable.Range( 1, groupWithCount.Count ),
            ( j, i ) => new { j.inst, j.name, RowNumber = i }
        )
    );

foreach (var i in o)
{
    Console.WriteLine( "{0} {1} {2}", i.inst, i.name, i.RowNumber );
}

出力:

Guitar John 1
Guitar George 2
Guitar Paul 3
drums Ringo 1
drums Pete 2
33
Jon Comtois

別のアイデアは、可能であればビューを使用することです。

5
leppie

オブジェクトへのLinqの別のソリューションは次のとおりです。

var result = beatles
            .GroupBy(g => g.inst)
            // PARTITION BY ^^^^
            .Select(c => c.OrderBy(o => o.id).Select((v, i) => new { i, v }).ToList())
            //                   ORDER BY ^^
            .SelectMany(c => c)
            .Select(c => new { c.v.id, c.v.inst, c.v.name, rn = c.i + 1 })
            .ToList();

[ C# Demo ]

3
shA.t

@The_Smallestが指摘しているように、行番号はLINQでサポートされていません。ただし、探しているものを取得する方法は次のとおりです。

var grouped = beatles.OrderBy( x => x.id )
  .ToList()   // required because SelectMany below doesn't evaluate to SQL
  .GroupBy( x => x.inst );
var rns = grouped.ToDictionary( x => x.Key, x => 1 );
var result = grouped
  .SelectMany( x => x.Select( 
    y => new { inst = y.inst, name = y.name, rn = rns[y.inst]++ } ) );
3
Ethan Brown

適切なインデックスを取得するためにコードで使用すると便利な場合があります

.Select((item, i) => new { Item = item, Index = i })
2
Tomasz Maj

RANK()OVER(PARTITION BY "partitionBy" ORDER BY "orderBy" DESC)と同等の別のソリューション:

 DataTable Rank(DataTable dt, string partitionBy, string orderBy, int whichRank)
   {

        DataView dv = new DataView(dt);
        dv.Sort = partitionBy + ", " + orderBy + " DESC";

        DataTable rankDt = dv.ToTable();
        rankDt.Columns.Add("Rank");
        int rank = 1;

        for (int i = 0; i < rankDt.Rows.Count - 1; i++)
        {
            rankDt.Rows[i]["Rank"] = rank;
            DataRow thisRow = rankDt.Rows[i];
            DataRow nextRow = rankDt.Rows[i + 1];

            if (thisRow[partitionBy].ToString() != nextRow[partitionBy].ToString())
                rank = 1;
            else
                rank++;
        }

        DataView selectRankdv = new DataView(rankDt);
        selectRankdv.RowFilter = "rank = " + whichRank;
        return selectRankdv.ToTable();
  }
1