Ist es möglich, LINQ to Pivot Daten mit?
-
03-07-2019 - |
Frage
Ich frage mich, ob es möglich ist, LINQ zu verwenden, um Daten aus dem folgende Layout zu schwenken:
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 etwa so aus:
CustID | Jan- 2008 | Feb- 2008 | Mar - 2008 |
1 | 100 | 350 | 250
2 | 200 | 221 | 2150
Lösung
So etwas wie das?
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 die gleiche wie SQL. In SQL erhalten Sie die Schlüssel und Aggregate (Zeilen / Spalten-Form). In Linq, erhalten Sie die Schlüssel und alle Elemente als Kinder des Schlüssels (hierarchische Form). Zu schwenken, müssen Sie die Hierarchie Projekt wieder in eine Zeile / Spalte Form Ihrer Wahl.
Andere Tipps
Ich antwortete ähnliche Frage 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 (das heißt die Methode macht die Reihenfolge der Spalten und Zeilen)?
Der sauberste Ansatz für das, glaube ich, ist es, eine Suche verwendet werden:
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(),
};
Hier ist etwas allgemeine Weise, wie Daten mithilfe von LINQ 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 ist ein Sonderklasse, 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;
}
}
Dieser Ansatz kann für die Gruppierung von N-Dimensionen verwendet wird (n> 2) und wird für eher kleinen Datensätze 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 besondere geschrieben
Dies ist die effizienteste Art und Weise: Überprüfen Sie die folgende Vorgehensweise. Anstatt jedes Mal für jeden Monat durch den Kunden Gruppe iteriert. Oder diese: Komplettlösung: var query = myList
.GroupBy(c => c.CustId)
.Select(g => {
var results = new CustomerStatistics();
foreach (var customer in g)
{
switch (customer.OrderDate.Month)
{
case 1:
results.Jan += customer.Qty;
break;
case 2:
results.Feb += customer.Qty;
break;
case 3:
results.March += customer.Qty;
break;
default:
break;
}
}
return new
{
CustId = g.Key,
results.Jan,
results.Feb,
results.March
};
});
var query = myList
.GroupBy(c => c.CustId)
.Select(g => {
var results = g.Aggregate(new CustomerStatistics(), (result, customer) => result.Accumulate(customer), customerStatistics => customerStatistics.Compute());
return new
{
CustId = g.Key,
results.Jan,
results.Feb,
results.March
};
});
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp
{
internal class Program
{
private static void Main(string[] args)
{
IEnumerable<CustData> myList = GetCustData().Take(100);
var query = myList
.GroupBy(c => c.CustId)
.Select(g =>
{
CustomerStatistics results = g.Aggregate(new CustomerStatistics(), (result, customer) => result.Accumulate(customer), customerStatistics => customerStatistics.Compute());
return new
{
CustId = g.Key,
results.Jan,
results.Feb,
results.March
};
});
Console.ReadKey();
}
private static IEnumerable<CustData> GetCustData()
{
Random random = new Random();
int custId = 0;
while (true)
{
custId++;
yield return new CustData { CustId = custId, OrderDate = new DateTime(2018, random.Next(1, 4), 1), Qty = random.Next(1, 50) };
}
}
}
public class CustData
{
public int CustId { get; set; }
public DateTime OrderDate { get; set; }
public int Qty { get; set; }
}
public class CustomerStatistics
{
public int Jan { get; set; }
public int Feb { get; set; }
public int March { get; set; }
internal CustomerStatistics Accumulate(CustData customer)
{
switch (customer.OrderDate.Month)
{
case 1:
Jan += customer.Qty;
break;
case 2:
Feb += customer.Qty;
break;
case 3:
March += customer.Qty;
break;
default:
break;
}
return this;
}
public CustomerStatistics Compute()
{
return this;
}
}
}
Gruppieren Sie Ihre Daten auf den Monat und es dann mit Spalten für jeden Monat in eine neue Datentabelle projizieren. Die neue Tabelle Pivot-Tabelle sein würde.