DE .NET, SQLSERVER Y MÁS, APRENDE CONMIGO!✔

Desarrollo de todo tipo de aplicaciones y Administración de Base de datos con Tecnología Microsoft


UNETE

Agrupaciones

0

 

AGRUPACIONES (GROUP BY)

Tras saber cómo filtrar elementos, es buen momento para aprender a agruparlos. Agrupar elementos, como su propio nombre indica, es concentrar los datos de un registro a partir de una característica común. Por ejemplo, saber los pedidos que se corresponden a cada uno de los clientes.

Su sintaxis es la siguiente:

1

2

3

var agrupacion = from p in DataLists.ListaPedidos

                 group p by p.IdCliente into grupo

                 select grupo;

Lo cual nos devolverá un listado de agrupaciones (objeto que implementa la interfaz IGrouping<tipoClave, tipoObjetoAgrupado>) compuesto por dos elementos principales:

§  Key: contiene la clave de la agrupación, es decir, el campo por el cual se está agrupando. En este caso se trataría del valor de p.IdCliente.

§  <Implícito>: el objeto en sí también es un listado compuesto por los objetos sobre los que itera la cláusula from, es decir, contenidos en DataList.ListaPedidos. En este caso sería un listado de objetos de tipo Pedido. Dado que están agrupados, el objeto grupo sólo contendrá aquellos objetos de la clase Pedido cuyo valor Pedido.IdCliente sea el mismo en todos los elementos de la lista.

Así, podríamos anidar perfectamente dos bucles foreach para recorrer con el primero la lista de claves por las que se agrupa (grupo.Key) y utilizar el bucle interno para recorrer la lista de objetos que se ajustan al criterio de agrupación:

1

2

3

4

5

6

foreach (var grupo in agrupacion)

{

    Console.WriteLine("ID Cliente: " + grupo.Key);

    foreach (var objetoAgrupado in grupo)

        Console.Write("\t\tPedido nº " + objetoAgrupado.Id + ": " + objetoAgrupado.FechaPedido + "]" + Environment.NewLine);

}

El resultado, el siguiente:



Agrupar por más de un campo

Por supuesto, esto puede tener utilidades como la de contabilizar el número de pedidos que ha realizado un cliente. Hagamos una pequeña combinación de join y groupby para obtener esta información:

1

2

3

4

5

6

7

8

9

10

11

var agrupacion = from p in DataLists.ListaPedidos

                 join c in DataLists.ListaClientes on p.IdCliente equals c.Id

                 group p by new { p.IdCliente, c.Nombre } into grupo

                 select grupo;

 

foreach (var grupo in agrupacion)

{

    Console.WriteLine("Nombre Cliente: " + grupo.Key.Nombre + " (ID: " + grupo.Key.IdCliente + ")");

    foreach (var objetoAgrupado in grupo)

        Console.Write("\tPedido nº " + objetoAgrupado.Id + ": " + objetoAgrupado.FechaPedido + "]" + Environment.NewLine);

}

Como podemos observar, la clave no tiene por qué ser un valor entero. De hecho, no tiene ni siquiera por qué ser un único elemento: en este caso realizamos una agrupación por ID de cliente y por nombre, pudiendo acceder a esta información dentro de la propia clave.

Debemos intentar «proyectar» nuestra forma de pensar en SQL hacia LINQ. Recordemos que tanto un lenguaje como otro se basan en aritmética relacional, por lo que lo que sea válido para un lenguaje, obligatoriamente ha de resultarlo para el otro. Si sabemos que la siguiente consulta en SQL nos devolverá los pedidos de un usuario en una base de datos:

1

2

3

4

select c.Nombre, p.IdCliente, count(p.Id)

    from Pedidos p

    inner join Clientes c on c.Id = p.IdCliente

    group by p.IdCliente, c.Nombre

Debemos intentar tener claro el concepto de que la clave de agrupación son los elementos por los cuales queremos agrupar. Y así deberá ser también en LINQ. Por lo tanto, la sentencia SQL

1

group by p.IdCliente, c.Nombre

Se corresponderá en LINQ con

1

group p by new { p.IdCliente, c.Nombre } into grupo

Como podemos comprobar, la diferencia es mínima. Y el resultado, como podemos observar, será el siguiente:


Anidar agrupaciones

Gracias a la versatilidad de LINQ, podemos ir todavía un poco más allá y anidar las agrupaciones, de modo que obtengamos todas las líneas de pedido que pertenecen a un pedido y a su vez, todos los pedidos que pertenecen a un cliente.

Aprender este concepto no es difícil, pero anidar código siempre contribuye a la confusión. Por eso iremos poco a poco indicando lo que deberíamos hacer en cada caso. Comenzaremos con la consulta anterior. Ésta consulta nos devolverá un único objeto que implementa la interfaz IGrouping<TKey, TElement>, siendo:

§  TKey: clave del grupo, compuesto por el tipo anónimo {int, string} (Id de cliente y nombre de cliente)

§  TElement: tipo de los valores almacenados, en este caso, Pedido.

Por lo tanto, el siguiente fragmento de código generará un grupo con una clave de dos elementos y una lista de objetos de la clase Pedido:

1

2

3

4

5

6

7

8

9

10

var consultaClientes = from pedido in DataLists.ListaPedidos

               join cliente in DataLists.ListaClientes

                    on pedido.IdCliente equals cliente.Id

               group pedido by new

               {

                   cliente.Id,

                   cliente.Nombre

               } into pedidosPorCliente

               select pedidosPorCliente;    // Key = {Id de cliente, Nombre de cliente}

                                            // Grupo = List

También queremos obtener agrupadas todas las líneas de pedido asociadas a un único pedido, agrupando por el Id del pedido y su fecha de realización. Su estructura será similar a la que acabamos de generar, salvo que en lugar de usar las entidades Cliente y Pedido usaremos las entidades Pedido y LineaPedido. De momento, y para aclararnos, crearemos una nueva consulta LINQ que sea independiente a la anterior.

1

2

3

4

5

6

7

8

9

var consultaPedidos = from lineaPedido in DataLists.ListaLineasPedido

                join pedido in DataLists.ListaPedidos

                    on lineaPedido.IdPedido equals pedido.Id

                group lineaPedido by new

                {

                    pedido.Id,

                    pedido.FechaPedido

                } into lineasPorPedido      // Key = {Id de pedido, Fecha de realización del pedido}

                select lineasPorPedido;     // Grupo = List

La tercera consulta implicada ya no precisa de agrupaciones. Se encargará de realizar un join entre Producto y LineaPedido para poder mostrar en un solo registro la información del producto asociado a la línea, como su nombre y precio. Además, podremos calcular de forma dinámica el precio total multiplicando la cantidad de productos indicados en la línea de producto por el valor unitario de cada producto. Nuevamente, no se trata de una consulta muy compleja:

1

2

3

4

5

6

7

8

9

10

11

var lineaProducto = from linea in DataLists.ListaLineasPedido

                    join producto in DataLists.ListaProductos

                        on linea.IdProducto equals producto.Id

                    select new

                    {

                        IdLineaPedido = linea.Id,

                        Nombre = producto.Descripcion,

                        Cantidad = linea.Cantidad,

                        PrecioUnitario = producto.Precio,

                        PrecioTotal = (producto.Precio * linea.Cantidad)

                    };

Hasta aquí no ha habido mucha complicación: hemos realizado tres consultas que se encuentran conceptualmente relacionadas, pero no hemos establecido una relación entre ellas. Es hora de unirlas en una única consulta. ¿Cómo realizamos esto? Recordemos que lo que realiza select es la sentencia encargada de efectuar las proyecciones. Por lo tanto, si en lugar de indicarle que nos devuelva un objeto de la serie que estamos recorriendo le decimos que nos devuelva otra cosa, creará lo que le digamos. Y esa «cosa» puede estar relacionada con la colección recorrida… y también ser una nueva consulta.

Por tanto, queda bastante claro que es en a continuación de select donde debemos conectar nuestras consultas. Comenzaremos uniendo las dos primeras, haciendo que en global, la sentencia nos devuelva una agrupación de agrupaciones de líneas de pedido en lugar de que nos devuelva una simple agrupación de pedidos.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

var consulta = from pedido in DataLists.ListaPedidos

                            join cliente in DataLists.ListaClientes

                                 on pedido.IdCliente equals cliente.Id

                            group pedido by new

                            {

                                cliente.Id,

                                cliente.Nombre

                            } into pedidosPorCliente

                            select

                                 from lineaPedido in DataLists.ListaLineasPedido

                                 join pedido in DataLists.ListaPedidos

                                     on lineaPedido.IdPedido equals pedido.Id

                                 group lineaPedido by new

                                 {

                                     pedido.Id,

                                     pedido.FechaPedido

                                 } into lineasPorPedido

                                 select lineasPorPedido;

Como podemos observar, hemos conectado vilmente el contenido de la segunda consulta y lo hemos concatenado a continuación de la primera. Sin embargo, lo que nos devolverá esta consulta será algo con la siguiente estructura:

§  IGroupable<{int, string}, IGroupable<{int, DateTime}, LineaPedido>

Es decir, un grupo con clave (int, string) que contiene una lista de grupos (int, DateTime) que contiene una lista de objetos de la clase LineaPedido. Un poco lioso, ¿verdad? Tranquilos, lo arreglaremos cambiando el valor que devuelve la primera select. En lugar de que devuelva un grupo en bruto, ¿qué tal si hacemos que devuelva un tipo anónimo que sea un poco más legible? Por ejemplo, haremos que el tipo anónimo contenga el ID del cliente, el nombre del cliente y, ya al final, el listado de agrupaciones, al que le asignaremos un nombre para poder dirigirnos a él como es debido. Por lo tanto, el fragmento anterior quedará transformado de la siguiente forma:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

var consulta = from pedido in DataLists.ListaPedidos

                             join cliente in DataLists.ListaClientes

                                  on pedido.IdCliente equals cliente.Id

                             group pedido by new

                             {

                                 cliente.Id,

                                 cliente.Nombre

                             } into pedidosPorCliente

                             select new

                             {

                                 IdCliente = pedidosPorCliente.Key.Id,                          // Id del cliente

                                 NombreCliente = pedidosPorCliente.Key.Nombre,                  // Nombre del cliente

                                 ListaPedidos = from lineaPedido in DataLists.ListaLineasPedido // Grupo de líneas de pedido

                                                join pedido in DataLists.ListaPedidos

                                                    on lineaPedido.IdPedido equals pedido.Id

                                                group lineaPedido by new

                                                {

                                                    pedido.Id,

                                                    pedido.FechaPedido

                                                } into lineasPorPedido

                                                select lineasPorPedido

                 };

La forma de esta estructura ya es mucho más sencilla de manejar. Hacemos lo propio con la segunda select, a la que añadiremos también el ID del pedido y su fecha, que forman parte de la clave de la agrupación.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

 

 

 

 

 

 

var consulta = from pedido in DataLists.ListaPedidos

                             join cliente in DataLists.ListaClientes

                                  on pedido.IdCliente equals cliente.Id

                             group pedido by new

                             {

                                 cliente.Id,

                                 cliente.Nombre

                             } into pedidosPorCliente

                             select new

                             {

                                 IdCliente = pedidosPorCliente.Key.Id,                          // Id del cliente

                                 NombreCliente = pedidosPorCliente.Key.Nombre,                  // Nombre del cliente

                                 ListaPedidos = from lineaPedido in DataLists.ListaLineasPedido // Grupo de líneas de pedido

                                                join pedido in DataLists.ListaPedidos

                                                    on lineaPedido.IdPedido equals pedido.Id

                                                group lineaPedido by new

                                                {

                                                    pedido.Id,

                                                    pedido.FechaPedido

                                                } into lineasPorPedido

                                                select new {

                                                    IdPedido = lineasPorPedido.Key.Id,             // Id del pedido

                                                    FechaPedido = lineasPorPedido.Key.FechaPedido, // Fecha del pedido

                                                    ListaLineas = lineasPorPedido                  // Listado de objetos de la clase LineaPedido

                                               }

                 };

Ahora mismo tendríamos la siguiente estructura:

  • Grupo

§  TKey: {int, string}

§  TElement: <tipo anónimo>

      • IdCliente (int)
      • NombreCliente (string)
      • Grupo

§  TKey: {int, fecha}

§  TElement: <tipo anónimo>

§  IdPedido (int)

§  FechaPedido (DateTime)

§  ListaLineas (IEnumerable<LineaPedido>)

Por lo tanto, para extraer la información del producto, lo único que tendremos que hacer será sustituir el valor de ListaLineas por la tercera consulta individual que declaramos más arriba, en la que realizábamos un join entre LineaPedido y Producto y devolvíamos como resultado un nuevo tipo anónimo con los valores deseados. Buscamos, por lo tanto, la siguiente estructura:

  • Grupo

§  TKey: {int, string}

§  TElement: <tipo anónimo>

      • IdCliente (int)
      • NombreCliente (string)
      • Grupo

§  TKey: {int, fecha}

§  TElement: <tipo anónimo>

§  IdPedido (int)

§  FechaPedido (DateTime)

§  ListaLineas <tipo anónimo>

§  IdLineaPedido (int)

§  Nombre (string)

§  Cantidad (int)

§  PrecioUnitario (float)

§  PrecioTotal (float)

El código final para nuestra consulta será el siguiente:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

var consulta = from pedido in DataLists.ListaPedidos

                join cliente in DataLists.ListaClientes

                     on pedido.IdCliente equals cliente.Id

                group pedido by new

                {

                    cliente.Id,

                    cliente.Nombre

                } into pedidosPorCliente

                select new

                {

                    IdCliente = pedidosPorCliente.Key.Id,

                    NombreCliente = pedidosPorCliente.Key.Nombre,

                    ListaPedidos = from lineaPedido in DataLists.ListaLineasPedido

                                   join pedido in DataLists.ListaPedidos

                                       on lineaPedido.IdPedido equals pedido.Id

                                   group lineaPedido by new

                                   {

                                       pedido.Id,

                                       pedido.FechaPedido

                                   } into lineasPorPedido

                                   select new

                                   {

                                       IdPedido = lineasPorPedido.Key.Id,

                                       FechaPedido = lineasPorPedido.Key.FechaPedido,

                                       ListaLineas = from linea in lineasPorPedido

                                                     join producto in DataLists.ListaProductos

                                                       on linea.IdProducto equals producto.Id

                                                     select new

                                                     {

                                                         IdLineaPedido = linea.Id,

                                                         Nombre = producto.Descripcion,

                                                         Cantidad = linea.Cantidad,

                                                         PrecioUnitario = producto.Precio,

                                                         PrecioTotal = (producto.Precio * linea.Cantidad)

                                                     }

                                   }

                };

Ahora podremos iterar tranquilamente para extraer la información de nuestro objeto. El siguiente código se encargará de ello:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

foreach (var cliente in consulta)

{

    Console.WriteLine(string.Format("CLIENTE: {0}. Ha realizado {1} Pedidos",

        cliente.NombreCliente, cliente.ListaPedidos.Count()));

 

    foreach (var pedido in cliente.ListaPedidos)

    {

        Console.WriteLine(string.Format("\tPEDIDO NUMERO {0} ({1}).

Lineas de pedido: {2}",

            pedido.IdPedido, pedido.FechaPedido,

pedido.ListaLineas.Count()));

 

        foreach(var lineaPedido in pedido.ListaLineas)

        {

            Console.WriteLine(string.Format("\t\tPRODUCTO:

{0}. Precio {1} x {2} uds. = {3}",

            lineaPedido.Nombre, lineaPedido.Cantidad,

lineaPedido.PrecioUnitario, lineaPedido.PrecioTotal));

        }

    }

    Console.WriteLine("---------------------------------------------------");

}

Console.ReadLine();

El resultado de este código lo podemos ver a continuación:



 


Tal vez te interesen estas entradas

No hay comentarios