pivote columns linq pivot-table

pivote - c# linq pivot dynamic columns



¿Es posible pivotar datos usando LINQ? (5)

Soy nuevo en LINQ, pero me pregunto si es posible utilizar LINQ para pivotar los datos del siguiente diseño:

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

en algo como esto:

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


¿Algo como esto?

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 en Linq no funciona igual que SQL. En SQL, obtienes la clave y los agregados (forma de fila / columna). En Linq, obtienes la clave y cualquier elemento como elementos secundarios de la clave (forma jerárquica). Para pivotar, debe proyectar la jerarquía en una forma de fila / columna de su elección.


Agrupe sus datos en un mes y luego proyecte en una nueva tabla de datos con columnas para cada mes. La nueva tabla sería tu tabla pivote.


Aquí hay una forma un poco más genérica de cómo pivotar datos usando LINQ:

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(); }

donde ValueKey es una clase especial que representa la clave multidimensional:

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; } }

Este enfoque se puede usar para agrupar por N dimensiones (n> 2) y funcionará bien para conjuntos de datos bastante pequeños. Para grandes conjuntos de datos (hasta 1 mln de registros y más) o para casos en los que la configuración dinámica no se puede codificar, he escrito una biblioteca especial de PivotData (es gratuita):

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 );


El mejor enfoque para esto, creo, es usar una búsqueda:

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(), };


Respondí una pregunta similar usando el método de extensión de linq:

// 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

(+) implementación genérica
(-) definitivamente más lento que David B

¿Alguien puede mejorar mi implementación (es decir, el método realiza el pedido de columnas y filas)?