Reten Docs
Filtros de eventos

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á:

EndpointDónde aparece la definición
POST /event-criteriasEn el cuerpo, bajo queryBlocks
PATCH /event-criterias/:idEn el cuerpo, bajo queryBlocks (opcional)
GET /event-criterias/:idEn la respuesta, bajo queryBlocks
POST /generate-sqlEn el cuerpo, bajo queryBlocks
POST /validate-from-bodyEn el cuerpo, bajo queryBlocks
POST /preview-from-bodyEn 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.

CampoTipoRequeridoDescripción
ordernumber (entero)SíPosición del bloque; entero ≥ 0. Único dentro del filtro.
setOperatorSetOperatorEnumNoCombina este bloque con el anterior. Ausente en el bloque order = 0; obligatorio en el resto.
fromFromDtoSíFuente de datos: tabla principal + joins.
selectSelectDtoSíProyección (columnas y/o agregaciones).
timeWindowTimeWindowDtoNoVentana temporal del bloque (unión discriminada por timeOperator).
whereWhereDtoSíÁrbol de filtros. Obligatorio aunque sea un grupo vacío.
groupByColumnRefDto[]NoColumnas de agrupación.
havingHavingDtoNoFiltro 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):

CampoTipoRequeridoDescripción
tablestringSíTabla del catálogo. No vacío.
columnstringSí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)

CampoTipoRequeridoDescripción
tablestringSíTabla principal del catálogo. No vacío.
joinsJoinDto[]NoCruces con otras tablas. Default [].

Cada JoinDto:

CampoTipoRequeridoDescripción
typeJoinTypeEnumSíINNER | LEFT | RIGHT | FULL.
tablestringSíTabla a unir (del catálogo). No vacío.
onJoinPredicateDto[]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)

CampoTipoRequeridoDescripción
distinctbooleanSí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":

    CampoTipoRequeridoDescripción
    kind"column"SíDiscriminador.
    columnColumnRefDtoSíColumna proyectada. Debe ser escalar.
    aliasstringNoDebe matchear ^[A-Za-z_][A-Za-z0-9_]*$.
  • Agregado — kind: "aggregate":

    CampoTipoRequeridoDescripción
    kind"aggregate"SíDiscriminador.
    aggregateAggregateDtoSíExpresión de agregación.
    aliasstringSíAquí el alias es obligatorio. Mismo regex de identificador.

AggregateDto:

CampoTipoRequeridoDescripción
aggregationOperatorAggregationOperatorEnumSíCOUNT | SUM | AVG | MIN | MAX.
operandobjectSíLo que se agrega. Unión discriminada por kind: column, star o arithmetic (ver abajo).
distinctbooleanNoAplica DISTINCT dentro del agregado. COUNT(DISTINCT *) no está permitido.

operand —lo que se agrega— se discrimina por kind:

kindCampos adicionalesSignificado
columncolumn: ColumnRefDtoUna columna del catálogo.
star(ninguno)*: cuenta filas. Solo es válido con COUNT.
arithmeticarithmeticOperator: ArithmeticOperatorEnum, left, rightExpresió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:

kindQué filtra
groupNo es una hoja: agrupa hijos con AND / OR. Se pueden anidar grupos.
predicateUna columna escalar de una tabla del from.
recordPredicateUna columna RECORD REPEATED: existe ≥1 elemento que cumple el sub-filtro.
semiJoinPredicateUn semi-join del catálogo: existe ≥1 match en la tabla relacionada.

Grupo — kind: "group"

CampoTipoRequeridoDescripción
kind"group"SíDiscriminador.
logicalOperatorLogicalOperatorEnum (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.

CampoTipoRequeridoDescripción
kind"predicate"SíDiscriminador.
columnColumnRefDtoSíColumna del predicado.
comparisonOperatorComparisonOperatorEnumSíOperador de comparación.
valuePredicateValueDependeSu 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.

CampoTipoRequeridoDescripción
kind"recordPredicate"SíDiscriminador.
columnColumnRefDtoSíColumna RECORD REPEATED de una tabla del from.
subWhereSub-árbol relativoSí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.

CampoTipoRequeridoDescripción
kind"semiJoinPredicate"SíDiscriminador.
tablestringSíTabla dueña del semi-join. Debe participar del from del bloque.
semiJoinstringSíNombre del semi-join que esa tabla declara en el catálogo.
subWhereSub-árbol relativoSí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"?: … }. El field debe ser un sub-campo escalar del scope.
  • Registro anidado — { "kind": "recordPredicate", "field": string, "subWhere": … }. Un record dentro de otro record (por ejemplo items[].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

OperadoresForma de valueEjemplo
IS_NULL, IS_NOT_NULLSin value (se omite)(se omite)
IN, NOT_INLista no vacía ScalarValue[]["Food Service", "HORECA"]
BETWEENPar [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 columnaOperadores admitidos
STRINGEQUALS · NOT_EQUALS · CONTAINS · NOT_CONTAINS · STARTS_WITH · ENDS_WITH · IN · NOT_IN · IS_NULL · IS_NOT_NULL
BOOLEANEQUALS · NOT_EQUALS · IS_NULL · IS_NOT_NULL
FLOAT64, INT64, NUMERIC, BIGNUMERICEQUALS · NOT_EQUALS · GREATER_THAN · LESS_THAN · GREATER_THAN_OR_EQUAL · LESS_THAN_OR_EQUAL · BETWEEN · IN · NOT_IN · IS_NULL · IS_NOT_NULL
TIMESTAMPEQUALS · 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.

timeOperatorCampos adicionales
ALL_TIME(ninguno) — sin acotar por tiempo.
IN_THE_LASTreference: ColumnRefDto, unit: TimeUnitEnum, value: entero > 0
SINCEreference: ColumnRefDto, from: string (YYYY-MM-DD)
BETWEENreference: 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 últimos value unit (p. ej. últimos 30 DAYS). Se recomputa en cada corrida.
  • SINCE — desde la fecha civil from (inclusiva) en adelante.
  • BETWEEN — entre from y to, 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:

  1. Validación del DTO (estructura, tipos, enums, campos requeridos). message es un arreglo de strings y se rechazan propiedades desconocidas.
  2. Validación de negocio (la definición debe generar SQL válido y coherente contra el catálogo). message es 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

ReglaMensaje
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

ReglaMensaje
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

ReglaMensaje
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

ReglaMensaje
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

ReglaMensaje
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

ReglaMensaje
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