2008-10-03 6 views
148

Ich bin neu in LINQ, aber ich frage mich, ob es möglich ist, LINQ zu verwenden, um Daten aus dem folgende Layout zu schwenken:Ist es möglich, Daten mit LINQ Pivot?

CustID | OrderDate | Qty 
1  | 1/1/2008 | 100 
2  | 1/2/2008 | 200 
1  | 2/2/2008 | 350 
2  | 2/28/2008 | 221 
1  | 3/12/2008 | 250 
2  | 3/15/2008 | 2150 

in so etwas wie:

CustID | Jan- 2008 | Feb- 2008 | Mar - 2008 | 
1  | 100  | 350  | 250 
2  | 200  | 221  | 2150 

Antwort

166

So etwas ähnliches?

List<CustData> myList = GetCustData(); 

var query = myList 
    .GroupBy(c => c.CustId) 
    .Select(g => new { 
     CustId = g.Key, 
     Jan = g.Where(c => c.OrderDate.Month == 1).Sum(c => c.Qty), 
     Feb = g.Where(c => c.OrderDate.Month == 2).Sum(c => c.Qty), 
     March = g.Where(c => c.OrderDate.Month == 3).Sum(c => c.Qty) 
    }); 

GroupBy in Linq funktioniert nicht das gleiche wie SQL. In SQL erhalten Sie den Schlüssel und die Aggregate (Zeilen-/Spaltenform). In Linq erhalten Sie den Schlüssel und alle Elemente als untergeordnete Elemente des Schlüssels (hierarchische Form). Um zu schwenken, müssen Sie die Hierarchie zurück in eine Zeilen-/Spaltenform Ihrer Wahl projizieren.

-4

Gruppe auf Ihren Daten Monat, und projizieren Sie es dann in eine neue Datentabelle mit Spalten für jeden Monat. Die neue Tabelle wäre Ihre Pivot-Tabelle.

9

Ich antwortete similar question Linq Erweiterung Methode:

// order s(ource) by OrderDate to have proper column ordering 
var r = s.Pivot3(e => e.custID, e => e.OrderDate.ToString("MMM-yyyy") 
    , lst => lst.Sum(e => e.Qty)); 
// order r(esult) by CustID 

(+) generische Implementierung
(-) auf jeden Fall langsamer als David B

Kann jemand meine Implementierung verbessern (dh die Methode funktioniert die Bestellung der Spalten & Zeilen)?

0

Hier ist etwas allgemeinere Art und Weise, wie Daten mithilfe von LINQ zu schwenken:

IEnumerable<CustData> s; 
var groupedData = s.ToLookup( 
     k => new ValueKey(
      k.CustID, // 1st dimension 
      String.Format("{0}-{1}", k.OrderDate.Month, k.OrderDate.Year // 2nd dimension 
     ))); 
var rowKeys = groupedData.Select(g => (int)g.Key.DimKeys[0]).Distinct().OrderBy(k=>k); 
var columnKeys = groupedData.Select(g => (string)g.Key.DimKeys[1]).Distinct().OrderBy(k=>k); 
foreach (var row in rowKeys) { 
    Console.Write("CustID {0}: ", row); 
    foreach (var column in columnKeys) { 
     Console.Write("{0:####} ", groupedData[new ValueKey(row,column)].Sum(r=>r.Qty)); 
    } 
    Console.WriteLine(); 
} 

wo ValueKey ein Sonderklasse ist die multidimensionale Schlüssel darstellt:

public sealed class ValueKey { 
    public readonly object[] DimKeys; 
    public ValueKey(params object[] dimKeys) { 
     DimKeys = dimKeys; 
    } 
    public override int GetHashCode() { 
     if (DimKeys==null) return 0; 
     int hashCode = DimKeys.Length; 
     for (int i = 0; i < DimKeys.Length; i++) { 
      hashCode ^= DimKeys[i].GetHashCode(); 
     } 
     return hashCode; 
    } 
    public override bool Equals(object obj) { 
     if (obj==null || !(obj is ValueKey)) 
      return false; 
     var x = DimKeys; 
     var y = ((ValueKey)obj).DimKeys; 
     if (ReferenceEquals(x,y)) 
      return true; 
     if (x.Length!=y.Length) 
      return false; 
     for (int i = 0; i < x.Length; i++) { 
      if (!x[i].Equals(y[i])) 
       return false; 
     } 
     return true;    
    } 
} 

für die Gruppierung Dieser Ansatz verwendet werden kann, durch N-Dimensionen (n> 2) und wird für ziemlich kleine Datensätze gut funktionieren. Bei großen Datenmengen (bis zu 1 Mio. Datensätze und mehr) oder für Fälle, in denen Dreh Konfiguration nicht fest einprogrammiert werden kann, habe ich geschrieben spezielle PivotData Bibliothek (kostenlos):

var pvtData = new PivotData(new []{"CustID","OrderDate"}, new SumAggregatorFactory("Qty")); 
pvtData.ProcessData(s, (o, f) => { 
    var custData = (TT)o; 
    switch (f) { 
     case "CustID": return custData.CustID; 
     case "OrderDate": 
     return String.Format("{0}-{1}", custData.OrderDate.Month, custData.OrderDate.Year); 
     case "Qty": return custData.Qty; 
    } 
    return null; 
}); 
Console.WriteLine(pvtData[1, "1-2008"].Value); 
1

Der sauberste Ansatz für diese, ich glaube, , ist eine Suche zu verwenden:

var query = 
    from c in myList 
    group c by c.CustId into gcs 
    let lookup = gcs.ToLookup(y => y.OrderDate.Month, y => y.Qty) 
    select new 
    { 
     CustId = gcs.Key, 
     Jan = lookup[1].Sum(), 
     Feb = lookup[2].Sum(), 
     Mar = lookup[3].Sum(), 
    };