Row_number over (Partition by xxx) in Linq?
本文关键字:xxx in Linq by Partition number over Row | 更新日期: 2023-09-27 18:29:42
我有一个DataTable
,它有一个结构和数据:
id | inst | name
------------------------
1 | guitar | john
2 | guitar | george
3 | guitar | paul
4 | drums | ringo
5 | drums | pete
我可以通过检索记录
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 | name | rn
-----------------------------
1 | guitar | john | 1
2 | guitar | george | 2
3 | guitar | paul | 3
4 | drums | ringo | 1
5 | drums | pete | 2
问题:
如何在Linq中做到这一点?
B"H
我知道这是旧的。但为什么解决方案不简单呢?
var o = beatles.GroupBy(x => x.inst)
.SelectMany(g =>
g.Select((j, i) => new { j.inst, j.name, rn = i + 1 })
);
试试这一行:
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
如果可能的话,另一个想法是使用视图。
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 ]
@The_Smalest指出,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]++ } ) );
另一个实现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();
}
有些人可能会在代码中使用来获得正确的索引
.Select((item, i) => new { Item = item, Index = i })
https://github.com/jurioli/Applied
var result = data.GroupBy(a => new { a.inst }).AsPartition()
.Over(p => p.RowNumber(), (a, value) => new { a.inst, a.name, RowNumber = value })
.ToList();
根据@Jon Comtois的回答,如果需要过滤掉特定的行号,可以使用以下扩展方法;
/// <summary>
/// Groups and orders by the data partitioning and returns the list of data with provided rownumber
/// It is the equivalent of SQL's ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)
/// </summary>
/// <typeparam name="TSource">Source type</typeparam>
/// <typeparam name="TGroupingKey">Generic type for grouping property</typeparam>
/// <typeparam name="TOrderKey">Generic type for ordering property</typeparam>
/// <param name="source">Source list to be partitioned</param>
/// <param name="groupingProperty">Grouping property</param>
/// <param name="orderProperty">Ordering property</param>
/// <param name="orderByAsc">Order direction</param>
/// <param name="rowNumber">Rows to be filtered out finally</param>
/// <returns>Partitioned list</returns>
public static List<TSource> FilterByPartitioning<TSource, TGroupingKey, TOrderKey>(this List<TSource> source, Func<TSource, TGroupingKey> groupingProperty, Func<TSource, TOrderKey> orderProperty, bool orderByAsc = true, int rowNumber = 1)
{
var orderedData = orderByAsc ? source.OrderBy(orderProperty) : source.OrderByDescending(orderProperty);
return orderedData.GroupBy(groupingProperty)
.Select(g => new { g, count = g.Count() })
.SelectMany(t => t.g.Select(b => b)
.Zip(Enumerable.Range(1, t.count), (source, i) => new { source, row = i }))
.Where(x => x.row == rowNumber)
.Select(x => x.source).ToList();
}
//Usage
var result = myList.FilterByPartitioning(group => group.PropertyToGroup, order => order.PropertyToOrder, orderByAsc: false, rowNumber: 1);