2011-09-15 7 views
7

Sto utilizzando LINQ-to-Entities e vorrei eseguire un pivot.Pivoting in Linq

Per exampe, ho questa tabella:

| data1 | data2 | 
+-------+-------+ 
| 1 | A | 
| 1 | B | 
| 2 | P | 
| 2 | Q | 
| 2 | R | 
+---------------+ 

E voglio ruotare nei seguenti risultati:

| data1 | first | second | third | 
+-------+-------+--------+-------+ 
| 1 | A | B | NULL | 
| 2 | P | Q | R | 
+--------------------------------+ 

mi piacerebbe farlo in LINQ, senza bisogno di fare elaborazione lato client.

Ho visto questi messaggi SO, ma essi non affrontano abbastanza la situazione di cui sopra (per quanto posso dire).



Nota Ho provato il seguito, ma si lamenta che non posso usare Skip() su una collezione non ordinata, e non fare vedere un modo per ottenere le informazioni "dati2" compresse del gruppo ordinate.

from item in MyTable 
group item by item.data1 into g 
select new 
{ 
    data1 = g.Key, 
    first = g.Skip(0).FirstOrDefault().data2, 
    second = g.Skip(1).FirstOrDefault().data2, 
    third = g.Skip(2).FirstOrDefault().data2, 
}; 
+0

Il secondo è un duplicato più vicino di questo: [is-it-possible-to-pivot-data-using-linq] (http: // stackoverflow.it/questions/167304/is-it-possible-to-pivot-data-using-linq) – nawfal

risposta

0

Hmm, questo sembra funzionare, anche se mi chiedo come è efficiente.

from item in MyTable 
group item by item.data1 into g 
select new 
{ 
    data1 = g.Key, 
    first = g.OrderBy(x => x.data2).Skip(0).FirstOrDefault().data2, 
    second = g.OrderBy(x => x.data2).Skip(1).FirstOrDefault().data2, 
    third = g.OrderBy(x => x.data2).Skip(2).FirstOrDefault().data2, 
}; 

Lo SQL corrispondente generato (da LINQPad) è:

SELECT [t1].[data1], (
    SELECT [t5].[data2] 
    FROM (
     SELECT TOP (1) [t4].[data2] 
     FROM (
      SELECT [t3].[data2], [t3].[ROW_NUMBER] 
      FROM (
       SELECT ROW_NUMBER() OVER (ORDER BY [t2].[data2]) AS [ROW_NUMBER], [t2].[data2] 
       FROM [MyTable] AS [t2] 
       WHERE [t1].[data1] = [t2].[data1] 
       ) AS [t3] 
      WHERE [t3].[ROW_NUMBER] > @p0 
      ) AS [t4] 
     ORDER BY [t4].[ROW_NUMBER] 
     ) AS [t5] 
    ) AS [first], (
    SELECT [t10].[data2] 
    FROM (
     SELECT TOP (1) [t9].[data2] 
     FROM (
      SELECT [t8].[data2], [t8].[ROW_NUMBER] 
      FROM (
       SELECT ROW_NUMBER() OVER (ORDER BY [t7].[data2]) AS [ROW_NUMBER], [t7].[data2] 
       FROM (
        SELECT [t6].[data2] 
        FROM [MyTable] AS [t6] 
        WHERE [t1].[data1] = [t6].[data1] 
        ) AS [t7] 
       ) AS [t8] 
      WHERE [t8].[ROW_NUMBER] > @p1 
      ) AS [t9] 
     ORDER BY [t9].[ROW_NUMBER] 
     ) AS [t10] 
    ) AS [second], (
    SELECT [t15].[data2] 
    FROM (
     SELECT TOP (1) [t14].[data2] 
     FROM (
      SELECT [t13].[data2], [t13].[ROW_NUMBER] 
      FROM (
       SELECT ROW_NUMBER() OVER (ORDER BY [t12].[data2]) AS [ROW_NUMBER], [t12].[data2] 
       FROM (
        SELECT [t11].[data2] 
        FROM [MyTable] AS [t11] 
        WHERE [t1].[data1] = [t11].[data1] 
        ) AS [t12] 
       ) AS [t13] 
      WHERE [t13].[ROW_NUMBER] > @p2 
      ) AS [t14] 
     ORDER BY [t14].[ROW_NUMBER] 
     ) AS [t15] 
    ) AS [third] 
FROM (
    SELECT [t0].[data1] 
    FROM [MyTable] AS [t0] 
    GROUP BY [t0].[data1] 
    ) AS [t1] 
+1

Non sarebbe 'FirstOrDefault(). data2' lancia una NullReferenceException se restituisce il valore predefinito? –

+0

@Bala: punto interessante. Eppure sembra funzionare. L'ho appena provato sulla tabella esatta che ho descritto (ho creato una tabella di test), e in effetti risulta che "terzo" è "null". Nessuna eccezione generata. Lo do a LINQ dietro la magia delle scene. – jwd

+0

dovresti postare un'altra domanda al riguardo: p –

0

suppongo che si potrebbe avere più di tre colonne dal campo data2?

Se così non c'è modo di fare una query che restituisce un tipo anonimo con un numero variabile di proprietà. È necessario restituire un array o una sorta di elenco per il set di valori data2.

penso che questo è il genere di cosa che si può fare:

var query = 
    from mt in MyTable 
    group mt.data2 by mt.data1 into gmts 
    let d2 = gmts.ToArray() 
    select new 
    { 
     data1 = gmts.Key, 
     data2 = d2, 
     length = d2.Length, 
    }; 

var pending = query.ToArray(); 

var maxLength = pending.Max(p => p.length); 

Func<string[], string[]> extend = xs => 
{ 
    var r = new string[maxLength]; 
    xs.CopyTo(r, 0); 
    return r; 
}; 

var results = 
    from p in pending 
    select new 
    { 
     p.data1, 
     data2 = extend(p.data2), 
    }; 

Questo produce una serie di tipo anonimo con la matrice data2 tutto l'essere della stessa dimensione per adattarsi al numero massimo di risultati per una delle i campi data1.

La query viene ancora eseguita come una singola query SQL. E l'elaborazione in memoria è veloce.

Funziona per voi?


EDIT

Dal momento che sai di avere un numero fisso di colonne (come da commento) si può facilmente cambiare il mio results query per soddisfare le vostre esigenze:

var results = 
    from p in pending 
    let d2s = extend(p.data2) 
    select new 
    { 
     p.data1, 
     first = d2s[0], 
     second = d2s[1], 
     third = d2s[2], 
    }; 
+0

Non ho provato il tuo codice, ma voglio assolutamente evitare un 'ToArray()' sul lato client. In effetti, mi interessa solo un numero fisso di colonne, non ho bisogno del caso super-generico. – jwd

+0

@jwd - perché vuoi evitare 'ToArray()'? – Enigmativity

+0

@jwd - Ho modificato la mia soluzione per includere una query che restituisce colonne fisse. – Enigmativity