2009-07-25 6 views
7

多くのGoogleの検索とコードの実験の後、SQLで複雑なC#LINQからオブジェクトへの問題ROW_NUMBER()... PARTITION BY関数とサブクエリまたは2つのペアで簡単に解決できます。グループ内のLINQからオブジェクトへのインデックス+別のグループ(別名:ROW_NUMBER、PARTITION BYの同等のもの)

  1. まず、グループリストを(Document.Title、文書によって:

    は、ここで私はリストから重複したドキュメントを削除して基本的な要件をcode--で何をしようとしている、言葉で、です。このようなグループ内では、各ドキュメントにインデックスを割り当てます(例:Index 0 ==このソースからこのタイトルを持つ最初のドキュメント、Index 1 =これを持つ2番目のドキュメント)。このソースからのタイトルなど)。私はSQLのROW_NUMBER()と同等のことが好きです!

  2. ここで、インデックスはステップ#2で計算された(Document.Title、Index)によってグループ化されます。グループごとに、最低のDocument.SourceIdを持つものを1つだけ返します。

ステップ#1(例えばcodepronet.blogspot.com/2009/01/group-by-in-linq.html)簡単ですが、私は、ステップ#2、#3に困惑して取得しています。私は、3つのステップすべてを解決するために、赤い波打ちのないC#LINQクエリを構築することはできません。

Anders Heilsbergさんの投稿this threadに私が上記のステップ#2と#3の答えが文法を正しく理解できればと思います。

slodge.blogspot.com/2009/01/adding-row-number-using-linq-to-objects.htmlで推奨されているように、インデックス計算を行うために外部ローカル変数を使用しないことをお勧めします。外部変数が変更された場合、その解決策が破損するためです。

最適なのは、「内部」グループ化(まずインデックスを計算するSource、重複を除外するIndex)は、それぞれの小さなオブジェクトで操作できます各タイトルのグループの文書数は通常100未満ですので、「タイトルごと」グループになります。私は本当にNを望んでいません。N 解決方法!

これはネストされたforeachループではっきりと解決できますが、LINQでは単純な問題のようです。

アイデア?

答えて

5

jpbochiはあなたのグループを値のペア(タイトル+ソースID、タイトル+インデックス)で欲しいと思っていなかったと思います。ここでLINQクエリです(主に)解決策:タイトル+ソースID(コンパイラは、グループのルックアップのための良好なハッシュコードを作成しますので、私は匿名型を使用)によって

var selectedFew = 
    from doc in docs 
    group doc by new { doc.Title, doc.SourceId } into g 
    from docIndex in g.Select((d, i) => new { Doc = d, Index = i }) 
    group docIndex by new { docIndex.Doc.Title, docIndex.Index } into g 
    select g.Aggregate((a,b) => (a.Doc.SourceId <= b.Doc.SourceId) ? a : b); 

まずグループ。次に、Selectを使用して、グループ化されたインデックスをドキュメントに添付します。これは、2番目のグループで使用します。最後に、各グループについて、最も低いSourceIdを選択します。私はこの出力を得る

var docs = new[] { 
    new { Title = "ABC", SourceId = 0 }, 
    new { Title = "ABC", SourceId = 4 }, 
    new { Title = "ABC", SourceId = 2 }, 
    new { Title = "123", SourceId = 7 }, 
    new { Title = "123", SourceId = 7 }, 
    new { Title = "123", SourceId = 7 }, 
    new { Title = "123", SourceId = 5 }, 
    new { Title = "123", SourceId = 5 }, 
}; 

は、この入力を考えると

{ Doc = { Title = ABC, SourceId = 0 }, Index = 0 } 
{ Doc = { Title = 123, SourceId = 5 }, Index = 0 } 
{ Doc = { Title = 123, SourceId = 5 }, Index = 1 } 
{ Doc = { Title = 123, SourceId = 7 }, Index = 2 } 

を更新:私はちょうど最初のタイトルでグループ化についてのご質問を見ました。あなたのタイトルのグループに副問合せを使用してこの操作を行うことができます。

var selectedFew = 
    from doc in docs 
    group doc by doc.Title into titleGroup 
    from docWithIndex in 
     (
      from doc in titleGroup 
      group doc by doc.SourceId into idGroup 
      from docIndex in idGroup.Select((d, i) => new { Doc = d, Index = i }) 
      group docIndex by docIndex.Index into indexGroup 
      select indexGroup.Aggregate((a,b) => (a.Doc.SourceId <= b.Doc.SourceId) ? a : b) 
     ) 
    select docWithIndex; 
+0

ちょっとダルビーキー - これは素晴らしいです!あなたの解決策はよく見えます。今私は初めて自分自身を理解することができないということについて悪くはない。私はSelect-with-indexのオーバーロードを発見しましたが、LINQクエリにそれを取得する方法を理解できませんでした。いくつかの黒帯のコードはあなたの目的のために、助けと可能なことの教育に感謝します。 –

3

正直言って、私はあなたの質問に非常に混乱しています。あなたが解決しようとしていることを説明する必要があるかもしれません。とにかく、私が理解したことに答えようとします。

1)まず、Title + SourceIdでグループ化されたドキュメントのリストがあるとします。テスト目的のために、私は次のようにリストをハードコード:

var docs = new [] { 
    new { Title = "ABC", SourceId = 0 }, 
    new { Title = "ABC", SourceId = 4 }, 
    new { Title = "ABC", SourceId = 2 }, 
    new { Title = "123", SourceId = 7 }, 
    new { Title = "123", SourceId = 5 }, 
}; 

2)すべての項目にインデックスを入れて取得するには、のFuncセレクター機能を渡し、Select拡張メソッドを使用することができます。このように:

var docsWithIndex 
    = docs 
    .Select((d, i) => new { Doc = d, Index = i }); 

3)私が理解したものから、次のステップは、Titleによってグループに最後の結果だろう。ここでそれを行う方法は次のとおりです。

var docsGroupedByTitle 
    = docsWithIndex 
    .GroupBy(a => a.Doc.Title); 

(上記使用)GROUPBY機能がIEnumerable<IGrouping<string,DocumentWithIndex>>を返します。グループも列挙できるので、列挙可能な列挙型を持つようになりました。

4)上記の各グループについて、最小SourceIdのアイテムのみを取得します。この操作を行うには、2レベルの再帰が必要です。 LINQは、(最低SourceIdでアイテムを取得する)外側のレベル(各群について、その項目のいずれかを取得)選択で、内レベルは集合体である:

var selectedFew 
    = docsGroupedByTitle 
    .Select(
     g => g.Aggregate(
      (a, b) => (a.Doc.SourceId <= b.Doc.SourceId) ? a : b 
     ) 
    ); 

だけで、それを確実にするために作品は、私はシンプルなforeachでそれをテストした:

foreach (var a in selectedFew) Console.WriteLine(a); 
//The result will be: 
//{ Doc = { Title = ABC, SourceId = 0 }, Index = 0 } 
//{ Doc = { Title = 123, SourceId = 5 }, Index = 4 } 

私はそれはあなたが望んだか分かりません。もしそうでなければ、答えをコメントしてください。私は答えを修正することができます。私はこれが役立つことを願っています

私のテストで使用されたすべてのクラスはanonymousでした。したがって、実際にDocumentWithIndexタイプを定義する必要はありません。実際、私はDocumentクラスも宣言していません。

+0

こんにちはjpochi - dahlbyのソリューションは正しいものでした。申し訳ありませんが、私はあなたに戻って明確にすることができませんでした。これはスタックオーバーフローに関する私の最初の質問でした。私は日曜日に2時間以内に2つの回答を得ることは決してありませんでした。次回はもっと早くチェックします! :-)とにかく、助けてくれてありがとう。 –

+0

問題ありません。私はあなたがその時受け入れられたように彼の答えに印を付けるべきだと思います。 – jpbochi

1

メソッドベースの構文:

var selectedFew = docs.GroupBy(doc => new {doc.Title, doc.SourceId}, doc => doc) 
         .SelectMany((grouping) => grouping.Select((doc, index) => new {doc, index})) 
           .GroupBy(anon => new {anon.doc.Title, anon.index}) 
           .Select(grouping => grouping.Aggregate((a, b) => a.doc.SourceId <= b.doc.SourceId ? a : b)); 

あなたは上記の同等のメソッドベースの構文であると思いますか?

+0

これは、上記のDahlbyKのLINQ-y構文と同じ(正しい)結果を出します。 Dahlbyの更新されたクエリを参照してください(おそらくタイトルでグループ化するほうが効率的かもしれません)。ソート/集計は小さなセットで発生する可能性があります。もし10億の文書があれば、すべてをロードする必要はないそれらを一度にRAMに書き込む。さらに、ほとんどのタイトルに重複はありません...私は、BCLが1つのメンバーのソートとグループ化操作を最適化することを願っています。 :-) –

1

拡張メソッドを実装しました。これは、複数の注文条件と同様にフィールド別に複数のパーティションをサポートします。

public static IEnumerable<TResult> Partition<TSource, TKey, TResult>(
    this IEnumerable<TSource> source, 
    Func<TSource, TKey> keySelector, 
    Func<IEnumerable<TSource>, IOrderedEnumerable<TSource>> sorter, 
    Func<TSource, int, TResult> selector) 
{ 
    AssertUtilities.ArgumentNotNull(source, "source"); 

    return source 
     .GroupBy(keySelector) 
     .Select(arg => sorter(arg).Select(selector)) 
     .SelectMany(arg => arg); 
} 

使用法:

var documents = new[] 
{ 
    new { Title = "Title1", SourceId = 1 }, 
    new { Title = "Title1", SourceId = 2 }, 
    new { Title = "Title2", SourceId = 15 }, 
    new { Title = "Title2", SourceId = 14 }, 
    new { Title = "Title3", SourceId = 100 } 
}; 

var result = documents 
    .Partition(
     arg => arg.Title, // partition by 
     arg => arg.OrderBy(x => x.SourceId), // order by 
     (arg, rowNumber) => new { RowNumber = rowNumber, Document = arg }) // select 
    .Where(arg => arg.RowNumber == 0) 
    .Select(arg => arg.Document) 
    .ToList(); 

結果:

{ Title = "Title1", SourceId = 1 }, 
{ Title = "Title2", SourceId = 14 }, 
{ Title = "Title3", SourceId = 100 } 
関連する問題