Avanzado y Rendimiento
Queries
Dificultad: 7/10En el mundo real con millones de registros, una Query es el único objeto de AL capaz de interactuar directamente con el motor SQL de forma rápida y eficiente, devolviendo datos ya agrupados y calculados sin necesidad de recorrerlos uno a uno desde AL.
Estructura base de una Query
Una Query se declara como un objeto independiente con su propio ID, igual que una tabla o una página:
query 50100 "Mi Primera Query"
{
Caption = 'Mi Primera Query';
QueryType = Normal;
elements
{
dataitem(Socio; Socio) // "Lee la tabla Socio"
{
column(Num_Socio; "No.") // "Quiero ver el campo No."
{
Caption = 'Número de Socio';
}
column(Nombre; Nombre) // "Quiero ver el campo Nombre"
{
Caption = 'Nombre';
}
}
}
}
¿Qué es una Query?
Imagina que tienes una lista de socios abierta en pantalla y necesitas saber: ¿cuántos días en total lleva prestando libros cada socio? Para responder a eso, tendrías que mirar cada préstamo de cada socio, sumar los días uno a uno y apuntarlo en algún sitio. Con 500 socios y 3.000 préstamos, eso es inviable a mano.
Una Query hace ese trabajo por ti: recorre los datos, los agrupa y calcula los totales de forma automática y muy eficiente, todo desde código AL, sin pantallas ni botones.
Query vs Page — ¿cuándo usar cada uno?
| Situación | Usa esto |
|---|---|
| El usuario necesita ver y editar registros | Page |
| Necesitas calcular totales o agrupar datos | Query |
| Quieres datos para un proceso en segundo plano | Query |
| Necesitas mostrar un listado con filtros al usuario | Page |
| Quieres saber cuántos préstamos tiene cada socio | Query |
Rendimiento puro frente a Pages
Si recolectas datos a mano con el estilo clásico de AL (abrir un Record mediante bucle → si pasa mis
filtros lo guardo → actualizo mi total), estás ejecutando muchos ciclos de procesador y memoria RAM con
SetFilter y FindSet(). Esto destruirá las instancias del servidor a escala.
Una Query toma todo lo que le has definido (varias tablas a unir simultáneamente) y compacta la petición en una sola orden SQL nativa. SQL retorna las tablas ya calculadas infinitamente más rápido.
Piénsalo así: una Page es como una ventanilla de atención al cliente. Una Query es como el empleado del almacén que va, cuenta todo el stock y te trae el número exacto sin interrupciones.
Métodos de agrupación (Method)
La clave de un Query está en la propiedad Method. Cuando la pones en una columna numérica,
le dices a BC que no te traiga cada valor por separado, sino que los agregue. Cuando usas
Method en una columna, el Query agrupa automáticamente el resultado por todas las demás
columnas sin Method.
| Method | Qué hace |
|---|---|
Method = Sum |
Suma todos los valores de esa columna |
Method = Count |
Cuenta cuántas filas hay |
Method = Average |
Calcula la media de los valores |
Method = Min |
Devuelve el valor más pequeño |
Method = Max |
Devuelve el valor más grande |
query 50100 "Ventas Por Almacen"
{
elements
{
dataitem(Venta; "Sales Invoice Header")
{
column(Location_Code; "Location Code") { } // columna de agrupación
column(TotalEuros; "Amount")
{
Method = Sum; // SQL suma directamente, sin bucles en AL
}
}
}
}
Ejemplo completo: días de préstamo por socio
Una Query que une dos tablas y calcula totales. Las columnas sin Method son las claves de
agrupación; BC agrupa los resultados por ellas automáticamente:
query 50101 "Dias Totales Prestamo por Socio"
{
Caption = 'Días Totales de Préstamo por Socio';
QueryType = Normal;
elements
{
dataitem(Socio; Socio)
{
// Sin Method → claves de agrupación
column(Num_Socio; "No.") { Caption = 'Nº Socio'; }
column(Nombre_Socio; Nombre) { Caption = 'Nombre'; }
dataitem(LineaPrestamo; "Linea Prestamo")
{
// Enlace entre tablas: cada línea con su socio
DataItemLink = "No. Socio" = Socio."No.";
// LeftOuterJoin: incluye socios sin préstamos (aparecen con 0)
SqlJoinType = LeftOuterJoin;
column(Total_Dias; "Dias Prestamo")
{
Caption = 'Total Días';
Method = Sum; // suma todos los días por socio
}
column(Num_Prestamos; "No. Prestamo")
{
Caption = 'Número de Préstamos';
Method = Count; // cuenta cuántos préstamos tiene cada socio
}
}
}
}
}
Usar una Query desde código AL
Una vez definida la Query, se usa desde un Codeunit siguiendo siempre el mismo flujo: filtrar → abrir → leer → cerrar.
⚠️ Orden obligatorio
Los filtros con SetFilter se deben poner antes de
Open(). Si los pones después, ya no tienen efecto porque BC ya ha traído los datos.
procedure MostrarDiasPorSocio()
var
QDias : Query "Dias Totales Prestamo por Socio";
Mensaje: Text;
begin
// Paso 1: aplicar filtros ANTES de abrir
QDias.SetFilter(Num_Socio, 'S001..S050');
// Paso 2: abrir la Query (BC va a buscar los datos en este momento)
QDias.Open();
// Paso 3: recorrer los resultados fila a fila
while QDias.Read() do begin
Mensaje := StrSubstNo(
'Socio: %1 | Nombre: %2 | Días: %3 | Préstamos: %4',
QDias.Num_Socio,
QDias.Nombre_Socio,
QDias.Total_Dias,
QDias.Num_Prestamos
);
Message(Mensaje);
end;
// Paso 4: cerrar siempre al terminar
QDias.Close();
end;
💡 Tips
- Una Query no tiene pantalla. Solo lee datos y los devuelve ya calculados. Para
mostrarlos al usuario usa una Page con
SourceTableTemporary = true. - Las columnas sin
Methodson las claves de agrupación. Las columnas conMethodson los valores calculados. SqlJoinType = LeftOuterJoinincluye registros del padre aunque no tengan hijos (aparecen con 0 o vacío). El valor por defecto esInnerJoin, que los excluye.- Siempre llama a
QDias.Close()al terminar. Es una buena práctica que libera recursos en el servidor. - Usa Queries cuando necesites rendimiento a escala: agrupaciones, sumas de millones de registros o uniones de varias tablas simultáneamente.