Definición
El árbol completo de la definición de un filtro de eventos — bloques de consulta, proyección, filtros de columna, de registro y de tabla relacionada, ventana temporal y agregación.
Qué es esta página
La definición de un filtro de eventos es el árbol que describe qué eventos selecciona. Es idéntica en todos los endpoints que la reciben o la devuelven, así que se documenta una sola vez acá:
| Endpoint | Dónde aparece la definición |
|---|---|
POST /event-criterias | En el cuerpo, bajo queryBlocks |
PATCH /event-criterias/:id | En el cuerpo, bajo queryBlocks (opcional) |
GET /event-criterias/:id | En la respuesta, bajo queryBlocks |
POST /generate-sql | En el cuerpo, bajo queryBlocks |
POST /validate-from-body | En el cuerpo, bajo queryBlocks |
POST /preview-from-body | En el cuerpo, bajo queryBlocks |
Los nombres de tablas y columnas que aparecen en los ejemplos salen del catálogo, que es la fuente de qué se puede filtrar.
queryBlocks[] — bloque de consulta
Mínimo 1 elemento. Cada bloque (QueryBlockDto) es un statement SQL completo; los
bloques se combinan entre sí con setOperator.
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
order | number (entero) | Sí | Posición del bloque; entero ≥ 0. Único dentro del filtro. |
setOperator | SetOperatorEnum | No | Combina este bloque con el anterior. Ausente en el bloque order = 0; obligatorio en el resto. |
from | FromDto | Sí | Fuente de datos: tabla principal + joins. |
select | SelectDto | Sí | Proyección (columnas y/o agregaciones). |
timeWindow | TimeWindowDto | No | Ventana temporal del bloque (unión discriminada por timeOperator). |
where | WhereDto | Sí | Árbol de filtros. Obligatorio aunque sea un grupo vacío. |
groupBy | ColumnRefDto[] | No | Columnas de agrupación. |
having | HavingDto | No | Filtro sobre agregados. |
Un filtro de eventos no se compone con otros filtros: no existe composedWith. El
setOperator de un bloque combina bloques dentro del mismo filtro, y es el único
nivel de operación de conjunto que hay.
ColumnRefDto — referencia a columna
Se usa en todo el árbol (select, filtros de columna, group by, joins, agregados, ventana temporal):
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
table | string | Sí | Tabla del catálogo. No vacío. |
column | string | Sí | Columna. No vacío. Puede ser un path de STRUCT no repetido (p. ej. "pricing.final_amount"). |
Toda columna se cualifica siempre con su table para evitar ambigüedad. La excepción son
los sub-árboles relativos —el interior de un filtro de registro o de tabla
relacionada—, donde el operando es un field suelto sin tabla (ver
Sub-árbol relativo).
from — fuente de datos (FromDto)
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
table | string | Sí | Tabla principal del catálogo. No vacío. |
joins | JoinDto[] | No | Cruces con otras tablas. Default []. |
Cada JoinDto:
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
type | JoinTypeEnum | Sí | INNER | LEFT | RIGHT | FULL. |
table | string | Sí | Tabla a unir (del catálogo). No vacío. |
on | JoinPredicateDto[] | Sí | Condiciones de unión. |
Cada JoinPredicateDto es un par de columnas: { left: ColumnRefDto, right: ColumnRefDto }.
Ambos lados deben ser columnas escalares.
Solo las tablas que devuelve
GET /tables pueden participar del from.
La tabla destino de un semi-join no es una tabla del catálogo: no entra al from ni
es destino de un join, y solo se alcanza con un
filtro de tabla relacionada.
select — proyección (SelectDto)
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
distinct | boolean | Sí | Aplica SELECT DISTINCT. |
items | (SelectColumnItem | SelectAggregateItem)[] | Sí | No vacío. Unión discriminada por el campo kind. |
Los items se discriminan por kind:
-
Columna —
kind: "column":Campo Tipo Requerido Descripción kind"column"Sí Discriminador. columnColumnRefDtoSí Columna proyectada. Debe ser escalar. aliasstring No Debe matchear ^[A-Za-z_][A-Za-z0-9_]*$. -
Agregado —
kind: "aggregate":Campo Tipo Requerido Descripción kind"aggregate"Sí Discriminador. aggregateAggregateDtoSí Expresión de agregación. aliasstring Sí Aquí el alias es obligatorio. Mismo regex de identificador.
AggregateDto:
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
aggregationOperator | AggregationOperatorEnum | Sí | COUNT | SUM | AVG | MIN | MAX. |
operand | object | Sí | Lo que se agrega. Unión discriminada por kind: column, star o arithmetic (ver abajo). |
distinct | boolean | No | Aplica DISTINCT dentro del agregado. COUNT(DISTINCT *) no está permitido. |
operand —lo que se agrega— se discrimina por kind:
kind | Campos adicionales | Significado |
|---|---|---|
column | column: ColumnRefDto | Una columna del catálogo. |
star | (ninguno) | *: cuenta filas. Solo es válido con COUNT. |
arithmetic | arithmeticOperator: ArithmeticOperatorEnum, left, right | Expresión binaria. Cada lado es otro operando: una column o un arithmetic anidado (nunca star). |
ArithmeticOperatorEnum: ADD | SUBTRACT | MULTIPLY | SAFE_DIVIDE (una división que da
NULL cuando el denominador es cero, en vez de hacer fallar la consulta). Las columnas de una
expresión aritmética tienen que ser numéricas (INT64, NUMERIC, BIGNUMERIC, FLOAT64), y las
expresiones se anidan hasta 3 niveles de arithmetic, contando el exterior.
Una columna RECORD REPEATED (attributes, items) no puede proyectarse, agruparse,
agregarse ni usarse en el ON de un join: no se compara por sí misma. Intentarlo responde
400 con La columna "<tabla>.<columna>" es una columna repetida y no puede usarse en <contexto>.
Su lugar es el filtro de registro.
where — árbol de filtros (WhereDto)
El where es un árbol recursivo. El nodo raíz es siempre un grupo, y sus hojas son de
tres tipos:
kind | Qué filtra |
|---|---|
group | No es una hoja: agrupa hijos con AND / OR. Se pueden anidar grupos. |
predicate | Una columna escalar de una tabla del from. |
recordPredicate | Una columna RECORD REPEATED: existe ≥1 elemento que cumple el sub-filtro. |
semiJoinPredicate | Un semi-join del catálogo: existe ≥1 match en la tabla relacionada. |
Grupo — kind: "group"
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
kind | "group" | Sí | Discriminador. |
logicalOperator | LogicalOperatorEnum (AND | OR) | Sí | Conectivo entre los hijos. |
children | (Grupo | Hoja)[] | Sí | Hijos del grupo; pueden ser grupos anidados. |
logicalOperator es obligatorio siempre en un grupo, pero solo tiene efecto con 2 o
más hijos. Con menos de 2 hijos su valor es indiferente (genera el mismo SQL), pero igual
debe enviarse. Un where sin filtros se expresa como un grupo vacío:
{ "kind": "group", "logicalOperator": "AND", "children": [] }.
Hoja de columna — kind: "predicate"
Compara una columna escalar cualificada por su tabla.
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
kind | "predicate" | Sí | Discriminador. |
column | ColumnRefDto | Sí | Columna del predicado. |
comparisonOperator | ComparisonOperatorEnum | Sí | Operador de comparación. |
value | PredicateValue | Depende | Su forma depende del operador. |
{
"kind": "predicate",
"column": { "table": "event_transactions", "column": "status" },
"comparisonOperator": "EQUALS",
"value": "Facturada"
}Hoja de registro — kind: "recordPredicate"
Filtra por una columna RECORD REPEATED (un array de structs, como attributes o
items). Significa existe al menos un elemento del array que cumple el sub-filtro.
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
kind | "recordPredicate" | Sí | Discriminador. |
column | ColumnRefDto | Sí | Columna RECORD REPEATED de una tabla del from. |
subWhere | Sub-árbol relativo | Sí | Condiciones sobre los sub-campos del elemento. No puede quedar vacío. |
{
"kind": "recordPredicate",
"column": { "table": "event_transactions", "column": "attributes" },
"subWhere": {
"kind": "group",
"logicalOperator": "AND",
"children": [
{ "kind": "predicate", "field": "key", "comparisonOperator": "EQUALS", "value": "documento_tipo" },
{ "kind": "predicate", "field": "value", "comparisonOperator": "EQUALS", "value": "factura" }
]
}
}Las condiciones del subWhere se evalúan sobre un mismo elemento del array, no sobre
el array entero. El ejemplo de arriba selecciona eventos que tienen un atributo cuya
key es documento_tipo y cuyo value es factura — no eventos que tengan un
atributo con esa key y otro, distinto, con ese value.
Hoja de tabla relacionada — kind: "semiJoinPredicate"
Filtra los eventos por la existencia de un match en otra tabla, siguiendo un semi-join que el catálogo declara. El camino entre las dos tablas lo resuelve el catálogo: la solicitud solo nombra la tabla dueña y el semi-join.
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
kind | "semiJoinPredicate" | Sí | Discriminador. |
table | string | Sí | Tabla dueña del semi-join. Debe participar del from del bloque. |
semiJoin | string | Sí | Nombre del semi-join que esa tabla declara en el catálogo. |
subWhere | Sub-árbol relativo | Sí | Condiciones sobre los campos de la tabla relacionada. No puede quedar vacío. |
{
"kind": "semiJoinPredicate",
"table": "event_transactions",
"semiJoin": "products",
"subWhere": {
"kind": "group",
"logicalOperator": "AND",
"children": [
{
"kind": "predicate",
"field": "categorization.category_name",
"comparisonOperator": "IN",
"value": ["Food Service", "HORECA"]
}
]
}
}Un semi-join no se puede anidar dentro de otro: el subWhere de una hoja de tabla
relacionada admite hojas escalares y de registro, pero no otra hoja
semiJoinPredicate.
Sub-árbol relativo
El subWhere de un filtro de registro y el de uno de tabla relacionada tienen la misma
forma: un grupo AND/OR cuyas hojas se resuelven contra los campos que declara el
scope —los sub-campos del record, o los campos de la tabla relacionada.
La diferencia con el árbol de primer nivel es una sola, y es la fuente de error más común:
Dentro de un sub-árbol relativo los operandos no llevan tabla: se escribe
"field": "item_id", no "column": { "table": "items", "column": "item_id" }. La hoja
usa la clave field (un string), no column.
Hojas admitidas dentro de un sub-árbol relativo:
- Hoja escalar —
{ "kind": "predicate", "field": string, "comparisonOperator": …, "value"?: … }. Elfielddebe ser un sub-campo escalar del scope. - Registro anidado —
{ "kind": "recordPredicate", "field": string, "subWhere": … }. Un record dentro de otro record (por ejemploitems[].attributes[]); se anida sin límite declarado, siempre que el catálogo declare cada nivel. - Grupo — la misma forma de grupo, para combinar las hojas anteriores con
AND/OR.
Forma de value según comparisonOperator
| Operadores | Forma de value | Ejemplo |
|---|---|---|
IS_NULL, IS_NOT_NULL | Sin value (se omite) | (se omite) |
IN, NOT_IN | Lista no vacía ScalarValue[] | ["Food Service", "HORECA"] |
BETWEEN | Par [min, max] | [10, 20] |
El resto (EQUALS, NOT_EQUALS, GREATER_THAN, LESS_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN_OR_EQUAL, CONTAINS, NOT_CONTAINS, STARTS_WITH, ENDS_WITH) | Escalar único | "Facturada" · 42 |
ScalarValue = string | number | boolean. La regla vale igual para las hojas de
columna y para las hojas escalares de un sub-árbol relativo.
Operadores admitidos por tipo de columna
Cada operador debe además ser compatible con el tipo de la columna que declara el
catálogo. Un operador fuera de esta tabla responde 400 con
Operator <op> is not allowed for <tipo> operands.
| Tipo de columna | Operadores admitidos |
|---|---|
STRING | EQUALS · NOT_EQUALS · CONTAINS · NOT_CONTAINS · STARTS_WITH · ENDS_WITH · IN · NOT_IN · IS_NULL · IS_NOT_NULL |
BOOLEAN | EQUALS · NOT_EQUALS · IS_NULL · IS_NOT_NULL |
FLOAT64, INT64, NUMERIC, BIGNUMERIC | EQUALS · NOT_EQUALS · GREATER_THAN · LESS_THAN · GREATER_THAN_OR_EQUAL · LESS_THAN_OR_EQUAL · BETWEEN · IN · NOT_IN · IS_NULL · IS_NOT_NULL |
TIMESTAMP | EQUALS · NOT_EQUALS · GREATER_THAN · LESS_THAN · GREATER_THAN_OR_EQUAL · LESS_THAN_OR_EQUAL · BETWEEN · IS_NULL · IS_NOT_NULL |
El filtrado por rango de fechas no va por estos operadores: lo cubre el campo
timeWindow.
groupBy — columnas de agrupación
Lista opcional de ColumnRefDto[]. Cuando el bloque agrega (declara groupBy o tiene un
agregado en el select), el groupBy debe coincidir exactamente con las columnas no
agregadas del select.
having — filtro sobre agregados (HavingDto)
Misma estructura recursiva que el where —grupos AND/OR anidados y la misma regla de
value por operador—, pero la hoja opera sobre un agregado, no sobre una columna:
- Grupo — idéntico al del
where. - Hoja —
{ "kind": "predicate", "aggregate": AggregateDto, "comparisonOperator": …, "value"?: … }.
having exige contexto de agregación: el bloque debe declarar groupBy o tener un
agregado en el select. Sin eso responde 400 con
HAVING requires an aggregation context (GROUP BY or an aggregate in the SELECT).
timeWindow — ventana temporal (TimeWindowDto)
Unión discriminada por timeOperator. Acota el bloque a un período de tiempo sobre una
columna TIMESTAMP del catálogo (reference). Es un campo hermano del where, no un
nodo del árbol de filtros.
timeOperator | Campos adicionales |
|---|---|
ALL_TIME | (ninguno) — sin acotar por tiempo. |
IN_THE_LAST | reference: ColumnRefDto, unit: TimeUnitEnum, value: entero > 0 |
SINCE | reference: ColumnRefDto, from: string (YYYY-MM-DD) |
BETWEEN | reference: ColumnRefDto, from: string (YYYY-MM-DD), to: string (YYYY-MM-DD) |
TimeUnitEnum: DAYS | WEEKS | MONTHS | YEARS.
Semántica de cada operador:
ALL_TIME— sin ventana; considera todo el histórico.IN_THE_LAST— ventana rodante relativa a "ahora": los últimosvalueunit(p. ej. últimos30 DAYS). Se recomputa en cada corrida.SINCE— desde la fecha civilfrom(inclusiva) en adelante.BETWEEN— entrefromyto, ambas fechas civiles inclusivas de su día completo.
IN_THE_FIRST es un miembro válido de TimeOperatorEnum, pero no está soportado en
este recurso: mide la vida de un sujeto y un filtro de eventos no lo tiene. Enviarlo
responde 400 con
timeWindow IN_THE_FIRST is not supported: the catalog does not declare a lifetime origin.
Las fechas civiles YYYY-MM-DD (SINCE, BETWEEN) se resuelven al inicio del día en
la zona horaria del tenant. El detalle de zona horaria es interno; basta con enviar la
fecha civil.
Reglas de validación
La definición pasa por dos capas de validación, ambas devuelven 400 Bad Request:
- Validación del DTO (estructura, tipos, enums, campos requeridos).
messagees un arreglo de strings y se rechazan propiedades desconocidas. - Validación de negocio (la definición debe generar SQL válido y coherente contra el
catálogo).
messagees un string único con el mensaje de la regla incumplida, y se detiene en el primer invariante violado.
Reglas de negocio verificadas, con el mensaje exacto del API:
Catálogo y referencias
| Regla | Mensaje |
|---|---|
| La tabla debe pertenecer al catálogo. | Table "<t>" is not in the catalog |
Cada (table, column) debe pertenecer al catálogo. | Column "<t>.<c>" is not in the catalog |
Toda columna referenciada debe pertenecer a una tabla del from. | Column "<t>.<c>" references a table that is not in the query block FROM |
| Una columna repetida no puede usarse fuera de un filtro de registro. | La columna "<t>.<c>" es una columna repetida y no puede usarse en <contexto> |
| Una hoja del árbol debe declarar su operando. | Una hoja del filtro no declara una columna |
Proyección y agregación
| Regla | Mensaje |
|---|---|
El operando star (*) solo es válido con COUNT. | '*' is only valid with COUNT, not <op> |
COUNT(DISTINCT *) no está permitido. | COUNT(DISTINCT *) is not allowed |
star no puede ser lado de una expresión aritmética. | '*' sólo vale como operando directo de COUNT, no como lado de una expresión aritmética |
| Las columnas de una expresión aritmética tienen que ser numéricas. | El operando "<t>.<c>" es de tipo <type>; una expresión aritmética sólo admite operandos numéricos (INT64, NUMERIC, BIGNUMERIC, FLOAT64) |
| Una expresión aritmética se anida hasta 3 niveles como máximo. | La expresión aritmética supera la profundidad máxima de 3 niveles |
Al agregar, toda columna no agregada del select debe estar en el groupBy. | Column "<t>.<c>" must appear in GROUP BY when the operation aggregates |
Toda columna del groupBy debe ser una columna no agregada del select. | Column "<t>.<c>" in GROUP BY must be a non-aggregated column of the SELECT |
having exige contexto de agregación. | HAVING requires an aggregation context (GROUP BY or an aggregate in the SELECT) |
Bloques de consulta
| Regla | Mensaje |
|---|---|
| Debe haber al menos un bloque. | A criteria must declare at least one operation (query block) |
Los order son únicos. | Two operations declare the same order=<n>: each operation order must be unique |
Debe existir el bloque order = 0. | Missing the first operation: one operation must declare order=0 |
El primer bloque no lleva setOperator. | First operation (order=0) must not have a setOperator |
Todo bloque posterior debe llevar setOperator. | Operation with order=<n> must have a setOperator |
Operadores y valores
| Regla | Mensaje |
|---|---|
| El operador debe ser compatible con el tipo de la columna/agregado. | Operator <op> is not allowed for <tipo> operands |
Operador escalar con value faltante o lista. | Operator <op> requires a single value |
IN/NOT_IN con value no-lista o vacía. | Operator <op> requires a non-empty list of values |
BETWEEN sin exactamente dos valores. | Operator <op> requires exactly two values |
Filtros de registro y de tabla relacionada
| Regla | Mensaje |
|---|---|
| Un filtro de registro exige una columna repetida. | La columna "<t>.<c>" no es una columna repetida: no admite un filtro de registro |
| Un filtro de columna exige una columna escalar. | La columna "<t>.<c>" es una columna repetida: no admite un filtro de columna |
| El sub-campo debe existir en el registro. | El sub-campo "<f>" no existe en el registro |
| El sub-campo debe existir en la tabla relacionada. | El sub-campo "<f>" no existe en la tabla relacionada "<t>" |
| Una hoja escalar no puede apuntar a un sub-campo repetido. | El sub-campo "<f>" es una columna repetida: usá un filtro de registro anidado |
| Un registro anidado exige un sub-campo repetido. | El sub-campo "<f>" no es una columna repetida |
| El sub-árbol de un filtro de registro no puede quedar vacío. | Un filtro de registro no puede quedar sin condiciones |
| El sub-árbol de un filtro de tabla relacionada no puede quedar vacío. | Un filtro sobre la tabla relacionada "<t>" no puede quedar sin condiciones |
La tabla dueña del semi-join debe participar del from. | El semi-join "<s>" se declara sobre la tabla "<t>", que no participa del FROM del bloque |
| El semi-join debe estar declarado por esa tabla en el catálogo. | El semi-join "<s>" no está declarado en la tabla "<t>" |
Ventana temporal
| Regla | Mensaje |
|---|---|
La reference debe estar en el catálogo. | timeWindow reference "<t>.<c>" is not in the catalog |
La reference debe ser una columna TIMESTAMP. | timeWindow reference "<t>.<c>" must be a TIMESTAMP column |
La reference no puede ser una columna repetida. | La referencia temporal "<t>.<c>" es una columna repetida y no puede anclar una ventana de tiempo |
value debe ser un entero positivo. | timeWindow value must be a positive integer |
unit debe ser un miembro de TimeUnitEnum. | timeWindow unit "<u>" is not a valid time unit |
En BETWEEN, from debe ser anterior o igual a to. | timeWindow BETWEEN requires "from" to be on or before "to" |
| Las fechas deben ser fechas de calendario válidas. | timeWindow date "<d>" is not a valid calendar date |
IN_THE_FIRST no está soportado. | timeWindow IN_THE_FIRST is not supported: the catalog does not declare a lifetime origin |