2011-09-15 26 views
7

Estoy usando LINQ-to-Entities y me gustaría realizar un pivote.Giro en Linq

Para exampe, tengo esta tabla:

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

Y quiero a pivotar en los siguientes resultados:

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

me gustaría hacer esto en LINQ, sin necesidad de hacer procesamiento del lado del cliente.

He visto estas publicaciones de SO, pero no abordan exactamente la situación anterior (por lo que puedo decir).



Nota He intentado el siguiente, pero se queja de que no puedo usar Skip() en una colección desordenada, y no hacer ver una forma de obtener ordenada la información 'data2' contraída del grupo.

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

El segundo es un duplicado más cercano de esto: [is-it-possible-to-pivot-data-using-linq] (http: // stackoverflow.com/questions/167304/is-it-possible-to-pivot-data-using-linq) – nawfal

Respuesta

0

Hmm, esto parece funcionar, aunque me pregunto qué tan eficiente es.

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

El SQL generado correspondiente (de LINQPad) es:

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

¿No 'FirstOrDefault(). data2' lanzará una NullReferenceException si devuelve el valor predeterminado? –

+0

@ Bala: punto interesante. Sin embargo, parece funcionar. Acabo de probarlo en la tabla exacta que describí (hice una tabla de prueba), y de hecho resulta en que 'tercero' es 'nulo'. Ninguna excepción lanzada. Lo atribuyo a LINQ magia detrás de escena. – jwd

+0

debe publicar otra pregunta al respecto: p –

0

que suponer que podría tener más de tres columnas del campo data2?

Si es así, no hay forma de hacer una consulta que devuelva un tipo anónimo con un número variable de propiedades. Debe devolver una matriz o algún tipo de lista para el conjunto de valores data2.

creo que este es el tipo de cosas que se pueden hacer:

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

Esto produce una serie de tipo anónimo con la matriz data2 siendo todos del mismo tamaño para ajustarse al número máximo de resultados para cualquiera de los campos data1.

La consulta se sigue ejecutando como una sola consulta SQL. Y el procesamiento en memoria es rápido.

¿Esto funciona para usted?


EDITAR

Puesto que usted sabe que tiene un número fijo de columnas (según el comentario) puede cambiar fácilmente mi consulta results para satisfacer sus requisitos:

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

No he probado su código, pero definitivamente quiero evitar un 'ToArray()' en el lado del cliente. De hecho, solo me importa una cantidad fija de columnas, no necesito el caso súper genérico. – jwd

+0

@jwd: ¿por qué quieres evitar 'ToArray()'? – Enigmativity

+0

@jwd: he editado mi solución para incluir una consulta que devuelve columnas fijas. – Enigmativity

Cuestiones relacionadas