EJERCICIO 10 · QUERIES

Queries

Dificultad: 7/10
Ejercicios anteriores

¿Qué tienes construido hasta ahora?

  • Modelo completo: "Maquinaria", "Cabecera Alquiler", "Linea Alquiler" y "Historial Estado Maquina" con triggers, FlowFields, FlowFilters y TableRelations.
  • Interfaz completa: pages de tipo List, Card, ListPart y CardPart con SubPageLink, FactBoxes y SourceTableView.
  • Codeunit "Maquinaria Stats" con comparativa trimestral usando SetRange + CalcFields.
  • Los FlowFields dan totales por máquina, pero no puedes ver un ranking de todas las máquinas a la vez sin recorrerlas una a una.

El codeunit "Maquinaria Stats" del ejercicio de FlowFilters compara trimestres para una sola máquina. Ahora el responsable de flota quiere algo diferente: ver todas las máquinas ordenadas por número de alquileres, saber cuáles llevan más días en total alquiladas y detectar cuáles nunca han sido alquiladas. Con CalcFields en bucle sobre miles de registros eso sería demasiado lento. Las Queries resuelven exactamente este problema.

Antes de empezar, revisa la anatomía de una Query con el modelo del proyecto:

dataitem(Maquinaria; "Maquinaria") tabla raíz
column(No_Maquina; "No.") { } clave de agrupación
column(Descripcion; "Descripcion") { } clave de agrupación
dataitem(LineaAlquiler; "Linea Alquiler") join hijo
DataItemLink = "No. Maquina" = Maquinaria."No."; enlace entre tablas
SqlJoinType = LeftOuterJoin; incluye sin hijos
column(Total_Alquileres; "No. Linea") { Method = Count; } valor calculado
column(Total_Dias; "Dias Alquiler") { Method = Sum; } valor calculado

La tabla muestra las diferencias clave entre el enfoque de FlowFields que ya usas y una Query:

Aspecto FlowField + CalcFields en bucle Query
Resultado de todas las máquinas N peticiones SQL (una por máquina) 1 sola petición SQL
Ordenar por total de alquileres Requiere tabla temporal manual Nativo en la Query
Máquinas sin alquileres (valor 0) Posible con lógica extra LeftOuterJoin lo da gratis
Rendimiento con 50.000 registros Lento (muchos round-trips) Rápido (SQL nativo)
Ejercicio 01
Query · Count · Sum · LeftOuterJoin

Ranking de máquinas por alquileres y días

Objetivo: Crear una Query que una "Maquinaria" con "Linea Alquiler" y devuelva, para cada máquina, el número total de alquileres y la suma de días alquilados. El LeftOuterJoin garantizará que las máquinas que nunca han sido alquiladas también aparezcan con valor 0.

Instrucciones

  • Crea RankingAlquileres.Query.al con ID 50100 y nombre "Ranking Alquileres Maquinaria".
  • El dataitem raíz apunta a "Maquinaria". Añade como columnas de agrupación: "No." (alias No_Maquina) y "Descripcion" (alias Descripcion).
  • Dentro, anida un segundo dataitem para "Linea Alquiler" con: DataItemLink = "No. Maquina" = Maquinaria."No." y SqlJoinType = LeftOuterJoin.
  • En el dataitem hijo añade dos columnas calculadas: "No. Linea" con Method = Count (alias Total_Alquileres) y "Dias Alquiler" con Method = Sum (alias Total_Dias).

Pista de Código

AL — RankingAlquileres.Query.al
query 50100 "Ranking Alquileres Maquinaria"
{
    Caption   = 'Ranking de Alquileres por Máquina';
    QueryType = Normal;

    elements
    {
        dataitem(Maquinaria; "Maquinaria")
        {
            // Sin Method → claves de agrupación
            column(No_Maquina;  "No.")         { Caption = 'Nº Máquina'; }
            column(Descripcion; "Descripcion") { Caption = 'Descripción'; }
            column(Estado;      "Estado")      { Caption = 'Estado'; }

            dataitem(LineaAlquiler; "Linea Alquiler")
            {
                DataItemLink = "No. Maquina" = Maquinaria."No.";
                SqlJoinType  = LeftOuterJoin; // máquinas sin alquileres → 0

                column(Total_Alquileres; "No. Linea")
                {
                    Caption = 'Total Alquileres';
                    Method  = Count;
                }
                column(Total_Dias; "Dias Alquiler")
                {
                    Caption = 'Total Días Alquilada';
                    Method  = Sum;
                }
            }
        }
    }
}
💡 Recuerda: Las columnas sin Method son las claves de agrupación: BC agrupa una fila por cada combinación única de esos valores. Las columnas con Method son los valores calculados que SQL agrega. Si eliminas LeftOuterJoin (o lo dejas como InnerJoin por defecto), las máquinas sin ninguna línea de alquiler no aparecerán en los resultados.
Ejercicio 02
Query · Open · Read · Close · SetFilter

Consume la Query desde "Maquinaria Stats"

Objetivo: Ampliar el codeunit "Maquinaria Stats" con un nuevo procedimiento que use la Query del ejercicio anterior para encontrar e imprimir las máquinas que superan un número mínimo de alquileres pasado como parámetro.

Instrucciones

  • Abre MaquinariaStats.Codeunit.al y añade el procedimiento MaquinasConMasAlquileres(MinAlquileres: Integer).
  • Declara una variable QRanking de tipo Query "Ranking Alquileres Maquinaria".
  • Aplica el filtro antes de abrir: usa QRanking.SetFilter sobre la columna Total_Alquileres con el valor '>=' + Format(MinAlquileres).
  • Llama a QRanking.Open(), recorre con while QRanking.Read() y muestra un Message por fila con el número de máquina, descripción, total de alquileres y total de días.
  • Cierra con QRanking.Close() al terminar.

Pista de Código

AL — MaquinariaStats.Codeunit.al (procedimiento nuevo)
    procedure MaquinasConMasAlquileres(MinAlquileres: Integer)
    var
        QRanking: Query "Ranking Alquileres Maquinaria";
    begin
        // Paso 1: filtrar ANTES de abrir
        QRanking.SetFilter(Total_Alquileres, '>=' + Format(MinAlquileres));

        // Paso 2: abre → BC envía UNA sola petición SQL
        QRanking.Open();

        // Paso 3: recorrer resultados fila a fila
        while QRanking.Read() do
            Message(
                '%1 — %2\nAlquileres: %3 | Días totales: %4',
                QRanking.No_Maquina,
                QRanking.Descripcion,
                QRanking.Total_Alquileres,
                QRanking.Total_Dias
            );

        // Paso 4: cerrar siempre para liberar recursos
        QRanking.Close();
    end;
⚠️ Recuerda: El SetFilter debe ir siempre antes de Open(). Una vez abierta la Query, los datos ya están traídos de SQL y los filtros posteriores no tendrán ningún efecto. Es el mismo principio que con SetRange + CalcFields en los FlowFilters.
Ejercicio 03
Query · Join triple · Average · Max

Query de resumen de cambios de estado por máquina

Objetivo: Crear una segunda Query que una "Maquinaria" con "Historial Estado Maquina" y calcule, para cada máquina, el número total de cambios de estado, la fecha del último cambio y un desglose por tipo de origen (Alquiler vs Manual) usando Method = Count y Method = Max.

Instrucciones

  • Crea ResumenHistorial.Query.al con ID 50101 y nombre "Resumen Historial Maquinaria".
  • El dataitem raíz apunta a "Maquinaria". Columnas de agrupación: "No." y "Descripcion".
  • Dentro, anida "Historial Estado Maquina" con DataItemLink = "No. Maquina" = Maquinaria."No." y SqlJoinType = LeftOuterJoin.
  • En el dataitem hijo añade:
    • "Entry No." con Method = Count → alias Total_Cambios.
    • "Fecha Cambio" con Method = Max → alias Ultimo_Cambio.
    • "Tipo Origen" sin Method → columna de agrupación adicional para desglosar Alquiler vs Manual.

Pista de Código

AL — ResumenHistorial.Query.al
query 50101 "Resumen Historial Maquinaria"
{
    Caption   = 'Resumen de Cambios de Estado por Máquina';
    QueryType = Normal;

    elements
    {
        dataitem(Maquinaria; "Maquinaria")
        {
            column(No_Maquina;  "No.")         { Caption = 'Nº Máquina'; }
            column(Descripcion; "Descripcion") { Caption = 'Descripción'; }

            dataitem(Historial; "Historial Estado Maquina")
            {
                DataItemLink = "No. Maquina" = Maquinaria."No.";
                SqlJoinType  = LeftOuterJoin;

                // Columna de agrupación adicional: desglose por tipo
                column(Tipo_Origen; "Tipo Origen") { Caption = 'Tipo Origen'; }

                column(Total_Cambios; "Entry No.")
                {
                    Caption = 'Total Cambios de Estado';
                    Method  = Count;
                }
                column(Ultimo_Cambio; "Fecha Cambio")
                {
                    Caption = 'Fecha Último Cambio';
                    Method  = Max;
                }
            }
        }
    }
}
💡 Recuerda: Al añadir "Tipo Origen" como columna sin Method, la Query agrupa por la combinación No_Maquina + Descripcion + Tipo_Origen. Cada máquina generará hasta dos filas: una para Alquiler y otra para Manual. Si solo quisieras el total sin desglose, bastaría con eliminar esa columna.
💡 Bonus: Ahora que tienes dos Queries ("Ranking Alquileres Maquinaria" y "Resumen Historial Maquinaria"), podrías añadir una acción VerRanking en "Maquinaria List" que ejecute "Maquinaria Stats".MaquinasConMasAlquileres(5) para mostrar al usuario las máquinas con 5 o más alquileres con un solo clic, cerrando así el ciclo completo del proyecto: desde los datos hasta la interfaz pasando por la lógica de negocio.
← Volver a Ejercicios