EJERCICIO 10 · QUERIES
Queries
Dificultad: 7/10Ejercicios 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 usandoSetRange+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.alcon ID50100y nombre"Ranking Alquileres Maquinaria". - El
dataitemraíz apunta a"Maquinaria". Añade como columnas de agrupación:"No."(aliasNo_Maquina) y"Descripcion"(aliasDescripcion). - Dentro, anida un segundo
dataitempara"Linea Alquiler"con:DataItemLink = "No. Maquina" = Maquinaria."No."ySqlJoinType = LeftOuterJoin. - En el dataitem hijo añade dos columnas calculadas:
"No. Linea"conMethod = Count(aliasTotal_Alquileres) y"Dias Alquiler"conMethod = Sum(aliasTotal_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.aly añade el procedimientoMaquinasConMasAlquileres(MinAlquileres: Integer). - Declara una variable
QRankingde tipoQuery "Ranking Alquileres Maquinaria". - Aplica el filtro antes de abrir: usa
QRanking.SetFiltersobre la columnaTotal_Alquilerescon el valor'>=' + Format(MinAlquileres). - Llama a
QRanking.Open(), recorre conwhile QRanking.Read()y muestra unMessagepor 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.alcon ID50101y nombre"Resumen Historial Maquinaria". - El
dataitemraíz apunta a"Maquinaria". Columnas de agrupación:"No."y"Descripcion". - Dentro, anida
"Historial Estado Maquina"conDataItemLink = "No. Maquina" = Maquinaria."No."ySqlJoinType = LeftOuterJoin. - En el dataitem hijo añade:
"Entry No."conMethod = Count→ aliasTotal_Cambios."Fecha Cambio"conMethod = Max→ aliasUltimo_Cambio."Tipo Origen"sinMethod→ 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.