क्या LINQ का उपयोग करके डेटा को पिवेट करना संभव है?


171

मुझे आश्चर्य हो रहा है कि निम्न लेआउट से डेटा पिवट करने के लिए LINQ का उपयोग करना संभव है या नहीं:

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

कुछ इस तरह से:

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

जवाबों:


190

कुछ इस तरह?

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

GroupByLinq में SQL के समान काम नहीं करता है। SQL में, आपको कुंजी और समुच्चय (पंक्ति / स्तंभ आकार) मिलता है। Linq में, आपको कुंजी (पदानुक्रमित आकार) के बच्चों के रूप में कुंजी और किसी भी तत्व मिलते हैं। धुरी के लिए, आपको पदानुक्रम को अपने चयन की पंक्ति / स्तंभ रूप में वापस करना होगा।


क्या सूची को लागू करने से पहले आपको एक IEnumerable होना चाहिए? या यह ईएफ से एक IQueryable पर भी किया जा सकता है (मेमोरी में सूची को भौतिक किए बिना)?
रोब वर्म्यूलेन

@RobVermeulen मैं उस क्वेरी को sql में अनुवाद कर सकता हूं, इसलिए मुझे उम्मीद है कि EF इसे भी अनुवाद करने में सक्षम होगा। यह एक कोशिश मुझे लगता है दे?
एमी बी

मैंने इसका परीक्षण किया, और यह काम करता है। हालाँकि SQL Profiler से पता चलता है कि EF इसे (तेज़) धुरी क्वेरी में नहीं बल्कि धीमी उप प्रश्नों के एक जोड़े में अनुवादित करेगा।
रोब वर्म्यूलेन

12

मैंने 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

(+) सामान्य कार्यान्वयन
(-) निश्चित रूप से एमी बी की तुलना में धीमा है

क्या कोई मेरे कार्यान्वयन में सुधार कर सकता है (अर्थात स्तंभ और पंक्तियों का क्रम क्या है)?


7

इसके लिए सबसे साफ दृष्टिकोण, मुझे लगता है कि लुकअप का उपयोग करना है:

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

2

यहाँ एक और अधिक सामान्य तरीका है कि 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();
}

जहाँ ValueKey एक विशेष वर्ग है जो बहुआयामी कुंजी का प्रतिनिधित्व करता है:

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

इस दृष्टिकोण का उपयोग एन-आयाम (एन> 2) द्वारा समूहीकरण के लिए किया जा सकता है और छोटे डेटासेट के लिए ठीक काम करेगा। बड़े डेटासेट (रिकॉर्ड और अधिक के 1 मिलीलीटर तक ) या उन मामलों के लिए जब पिवट कॉन्फ़िगरेशन को हार्डकोड नहीं किया जा सकता है मैंने विशेष पिवट्टा लाइब्रेरी (यह मुफ़्त है) लिखा है:

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

2

यह सबसे कारगर तरीका है:

निम्नलिखित दृष्टिकोण की जाँच करें। प्रत्येक महीने के लिए प्रत्येक बार ग्राहकों के समूह के माध्यम से पुनरावृत्ति करने के बजाय।

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

-4

अपने डेटा को महीने पर समूहीकृत करें, और फिर उसे हर महीने के कॉलम के साथ एक नए डेटाटेबल में प्रोजेक्ट करें। नई तालिका आपकी धुरी तालिका होगी।


मैं परिकल्पना नहीं कर सकता कि यह कैसे काम करेगा, लेकिन मैं यह अनुरोध करने के लिए पर्याप्त उत्सुक हूं कि आप कुछ उदाहरण कोड शामिल करें।
जोश
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.