Generar SQL
Compila un filtro de eventos a la cadena de CTEs que lo representa, desde el cuerpo o desde un filtro guardado.
Generar SQL
Compila una definición de filtro de eventos al SQL que la representa. Son dos rutas con la misma respuesta: una compila una definición enviada en el cuerpo y la otra un filtro ya guardado.
| Ruta | Origen de la definición | Código de éxito |
|---|---|---|
POST /api/event-criterias/generate-sql | El cuerpo | 201 |
GET /api/event-criterias/:id/sql | El filtro guardado | 200 |
Auth: Requerida — permiso event-criteria:view-sql en ambas rutas
El SQL no se ejecuta: estas rutas compilan y devuelven. Para validarlo contra BigQuery usa Validar; para ver filas reales, Previsualizar.
POST /api/event-criterias/generate-sql
Cuerpo de la solicitud
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
queryBlocks | QueryBlockDto[] | Sí | Mínimo 1 elemento. La definición a compilar. |
format | string | No | parameterized | literal. Default parameterized. |
La estructura de cada bloque está en Definición.
Este endpoint recibe solo la definición: name y description no forman parte del
cuerpo y enviarlos produce un 400 por propiedad desconocida.
Ejemplo
curl -X POST https://api.reten.ai/api/event-criterias/generate-sql \
-H "Authorization: Bearer <token>" \
-H "x-tenant-id: <tenant-id>" \
-H "x-project-id: <project-id>" \
-H "Content-Type: application/json" \
-d '{
"queryBlocks": [
{
"order": 0,
"from": { "table": "event_transactions", "joins": [] },
"select": {
"distinct": true,
"items": [
{ "kind": "column", "column": { "table": "event_transactions", "column": "event_id" } }
]
},
"where": {
"kind": "group",
"logicalOperator": "AND",
"children": [
{
"kind": "predicate",
"column": { "table": "event_transactions", "column": "status" },
"comparisonOperator": "EQUALS",
"value": "Facturada"
},
{
"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"]
}
]
}
}
]
}
}
]
}'import axios from 'axios';
const body = {
queryBlocks: [
{
order: 0,
from: { table: 'event_transactions', joins: [] },
select: {
distinct: true,
items: [
{ kind: 'column', column: { table: 'event_transactions', column: 'event_id' } },
],
},
where: {
kind: 'group',
logicalOperator: 'AND',
children: [
{
kind: 'predicate',
column: { table: 'event_transactions', column: 'status' },
comparisonOperator: 'EQUALS',
value: 'Facturada',
},
{
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'],
},
],
},
},
],
},
},
],
};
const response = await axios.post(
'https://api.reten.ai/api/event-criterias/generate-sql',
body,
{ headers: { Authorization: 'Bearer <token>', 'x-tenant-id': '<tenant-id>', 'x-project-id': '<project-id>' } },
);
const sql = response.data;GET /api/event-criterias/:id/sql
Compila un filtro ya guardado. La definición se lee del filtro y se valida antes de compilarla.
Parámetros de ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
id | UUID | ID del filtro de eventos. |
Parámetros de consulta
| Parámetro | Tipo | Por defecto | Descripción |
|---|---|---|---|
format | string | parameterized | parameterized | literal. |
Ejemplo
curl "https://api.reten.ai/api/event-criterias/6d2a9c41-7e35-48b0-b1c9-0f8e7d6c5b4a/sql?format=literal" \
-H "Authorization: Bearer <token>" \
-H "x-tenant-id: <tenant-id>" \
-H "x-project-id: <project-id>"import axios from 'axios';
const id = '6d2a9c41-7e35-48b0-b1c9-0f8e7d6c5b4a';
const response = await axios.get(
`https://api.reten.ai/api/event-criterias/${id}/sql`,
{
headers: { Authorization: 'Bearer <token>', 'x-tenant-id': '<tenant-id>', 'x-project-id': '<project-id>' },
params: { format: 'literal' },
},
);
const sql = response.data;Formato de salida — format
El campo format decide cómo se emiten los valores del SQL y qué campos trae la
respuesta. Es opcional; si se omite, el valor efectivo es parameterized.
format | Cómo emite los valores | Campos extra en la respuesta |
|---|---|---|
parameterized | Placeholders con nombre @pN en el query; el binding va aparte. | params, types |
literal | Los valores se inlinean como literales seguros dentro del query. | (ninguno) |
- En
generate-sql(POST) elformatva en el cuerpo. - En
:id/sql(GET) elformatva como query param.
Solo se aceptan los strings "parameterized" y "literal"; cualquier otro valor produce
un 400.
Respuesta — IEventCriteriaSql
Es una unión discriminada por format. Ambas variantes comparten una base y difieren
en los campos de binding.
Base (común a los dos formatos):
| Campo | Tipo | Descripción |
|---|---|---|
query | string | Una cadena de CTEs (las cláusulas van separadas por saltos \n): sin el WITH inicial y sin el SELECT final, para insertarla dentro de un WITH … SELECT. |
finalCteName | string | Nombre del CTE final de la cadena; siempre CTE_VALID_EVENTS. Es el "handle" del resultado: un consumidor hace SELECT … FROM CTE_VALID_EVENTS. |
subjectColumn | string | null | Columna de identidad que expone el CTE final. En un filtro de eventos es siempre null. |
-
format: "parameterized":Campo Tipo Descripción format"parameterized"Discriminador. paramsRecord<string, ScalarValue>Binding de cada @pN→ su valor.ScalarValue=string|number|boolean.typesRecord<string, string>Tipo de parámetro de BigQuery de cada @pN:STRING,TIMESTAMP,BOOL,FLOAT64,INT64,NUMERIC,BIGNUMERIC. -
format: "literal"— solo la base; sinparamsnitypes.
En parameterized, params y types son parte del contrato: el query trae @pN y
es inejecutable sin su binding y sus tipos. En literal no hay binding —los valores
viven dentro del query— por eso no trae params ni types. Discrimina siempre por
format antes de leer params/types.
subjectColumn es null en todo filtro de eventos: el CTE final proyecta eventos, no
sujetos, y el catálogo no declara una columna de identidad. Las columnas que expone el CTE
son las del select de la definición.
Ejemplo — parameterized (default)
Respuesta 201 al POST de ejemplo de arriba:
{
"format": "parameterized",
"query": "CTE_VALID_EVENTS AS (\nSELECT DISTINCT `event_transactions`.`event_id`\nFROM `reten-prod.tenant_a1b2c3d4.event_transactions` AS event_transactions\nWHERE `event_transactions`.`status` = @p0 AND EXISTS (SELECT 1 FROM UNNEST(`event_transactions`.`items`) AS `items_item`, `reten-prod.tenant_a1b2c3d4.product_properties` AS `product_properties` WHERE `product_properties`.`sku` = `items_item`.`item_id` AND (`product_properties`.`categorization`.`category_name` IN (@p1, @p2)))\n)",
"finalCteName": "CTE_VALID_EVENTS",
"subjectColumn": null,
"params": { "p0": "Facturada", "p1": "Food Service", "p2": "HORECA" },
"types": { "p0": "STRING", "p1": "STRING", "p2": "STRING" }
}El query es una sola cadena con saltos \n. Formateado para lectura:
CTE_VALID_EVENTS AS (
SELECT DISTINCT `event_transactions`.`event_id`
FROM `reten-prod.tenant_a1b2c3d4.event_transactions` AS event_transactions
WHERE `event_transactions`.`status` = @p0
AND EXISTS (
SELECT 1
FROM UNNEST(`event_transactions`.`items`) AS `items_item`,
`reten-prod.tenant_a1b2c3d4.product_properties` AS `product_properties`
WHERE `product_properties`.`sku` = `items_item`.`item_id`
AND (`product_properties`.`categorization`.`category_name` IN (@p1, @p2))
)
)Cada @pN se liga con params[pN] (su valor) y types[pN] (su tipo BigQuery); sin ese
binding el query no es ejecutable.
Cómo se consume la cadena
La respuesta no es un query ejecutable: es la cadena de CTEs que hay que envolver. En los
snippets de abajo, {query} se sustituye por el valor del campo query tal cual viene: ya
trae su propio CTE_VALID_EVENTS AS ( … ), así que el encabezado del CTE no se vuelve a
escribir.
Para ejecutarla stand-alone:
WITH {query}
SELECT * FROM CTE_VALID_EVENTSPara cruzarla contra otra consulta, se inserta {query} en el propio WITH del caller
—seguido de una coma— y se hace JOIN CTE_VALID_EVENTS por la columna de evento que el
select de la definición proyecta (por ejemplo event_id):
WITH
{query},
mis_metricas AS (
SELECT t.event_id, t.pricing.final_amount
FROM `reten-prod.tenant_a1b2c3d4.event_transactions` AS t
)
SELECT SUM(m.final_amount) AS total
FROM mis_metricas AS m
JOIN CTE_VALID_EVENTS AS v ON v.event_id = m.event_idEl nombre CTE_VALID_EVENTS es estable y no depende del filtro: es el finalCteName de
todo filtro de eventos. Las columnas que expone, en cambio, son las del select de
cada definición.
Ejemplo — literal
La misma definición con format: "literal":
{
"format": "literal",
"query": "CTE_VALID_EVENTS AS (\nSELECT DISTINCT `event_transactions`.`event_id`\nFROM `reten-prod.tenant_a1b2c3d4.event_transactions` AS event_transactions\nWHERE `event_transactions`.`status` = 'Facturada' AND EXISTS (SELECT 1 FROM UNNEST(`event_transactions`.`items`) AS `items_item`, `reten-prod.tenant_a1b2c3d4.product_properties` AS `product_properties` WHERE `product_properties`.`sku` = `items_item`.`item_id` AND (`product_properties`.`categorization`.`category_name` IN ('Food Service', 'HORECA')))\n)",
"finalCteName": "CTE_VALID_EVENTS",
"subjectColumn": null
}Los valores quedan inlineados como literales seguros (strings escapados y entre comillas
simples, TIMESTAMP('…') para instantes, NUMERIC '…' para decimales exactos); no hay
params ni types.
Reglas de validación
Antes de compilar, la definición pasa por las dos capas de validación, y ambas devuelven
400 Bad Request. La validación de negocio corre en ambos endpoints:
generate-sql valida la definición del cuerpo, y :id/sql valida el filtro guardado antes
de compilarlo.
El catálogo completo de reglas y sus mensajes exactos está en Definición → Reglas de validación.
Respuestas de error
| Estado | Descripción |
|---|---|
400 | DTO inválido (campo faltante, tipo incorrecto, enum fuera de rango, format no válido, propiedad desconocida como name/description) o una regla de negocio incumplida. En :id/sql, también si el id no es un UUID válido. |
401 | Falta o es inválido el Authorization: Bearer. |
403 | El usuario no tiene el permiso event-criteria:view-sql, o el x-tenant-id no está autorizado. |
404 | En :id/sql, no existe un filtro de eventos con ese id en el tenant (Event criteria not found), o el tenant no tiene dataset configurado. |