Gerar SQL
Compila um filtro de eventos para a cadeia de CTEs que o representa, a partir do corpo ou de um filtro salvo.
Gerar SQL
Compila uma definição de filtro de eventos para o SQL que a representa. São duas rotas com a mesma resposta: uma compila uma definição enviada no corpo e a outra um filtro de eventos já salvo.
| Rota | Origem da definição | Código de sucesso |
|---|---|---|
POST /api/event-criterias/generate-sql | O corpo | 201 |
GET /api/event-criterias/:id/sql | O filtro salvo | 200 |
Auth: Obrigatória — permissão event-criteria:view-sql em ambas as rotas
O SQL não é executado: estas rotas compilam e retornam. Para validá-lo contra o BigQuery use Validar; para ver linhas reais, Pré-visualizar.
POST /api/event-criterias/generate-sql
Corpo da requisição
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
queryBlocks | QueryBlockDto[] | Sim | Mínimo 1 elemento. A definição a compilar. |
format | string | Não | parameterized | literal. Padrão parameterized. |
A estrutura de cada bloco está em Definição.
Este endpoint recebe apenas a definição: name e description não fazem parte do
corpo e enviá-los produz um 400 por propriedade desconhecida.
Exemplo
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 um filtro já salvo. A definição é lida do filtro e validada antes de compilá-la.
Parâmetros de rota
| Parâmetro | Tipo | Descrição |
|---|---|---|
id | UUID | ID do filtro de eventos. |
Parâmetros de consulta
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
format | string | parameterized | parameterized | literal. |
Exemplo
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 saída — format
O campo format decide como os valores do SQL são emitidos e quais campos a resposta
traz. É opcional; se omitido, o valor efetivo é parameterized.
format | Como emite os valores | Campos extras na resposta |
|---|---|---|
parameterized | Placeholders nomeados @pN no query; o binding vai separado. | params, types |
literal | Os valores são inlineados como literais seguros dentro do query. | (nenhum) |
- Em
generate-sql(POST) oformatvai no corpo. - Em
:id/sql(GET) oformatvai como query param.
Só são aceitas as strings "parameterized" e "literal"; qualquer outro valor produz
um 400.
Resposta — IEventCriteriaSql
É uma união discriminada por format. Ambas as variantes compartilham uma base e
diferem nos campos de binding.
Base (comum aos dois formatos):
| Campo | Tipo | Descrição |
|---|---|---|
query | string | Uma cadeia de CTEs (as cláusulas são separadas por quebras \n): sem o WITH inicial e sem o SELECT final, para inseri-la dentro de um WITH … SELECT. |
finalCteName | string | Nome do CTE final da cadeia; sempre CTE_VALID_EVENTS. É o "handle" do resultado: um consumidor faz SELECT … FROM CTE_VALID_EVENTS. |
subjectColumn | string | null | Coluna de identidade exposta pelo CTE final. Em um filtro de eventos é sempre null. |
-
format: "parameterized":Campo Tipo Descrição format"parameterized"Discriminador. paramsRecord<string, ScalarValue>Binding de cada @pN→ seu valor.ScalarValue=string|number|boolean.typesRecord<string, string>Tipo de parâmetro do BigQuery de cada @pN:STRING,TIMESTAMP,BOOL,FLOAT64,INT64,NUMERIC,BIGNUMERIC. -
format: "literal"— apenas a base; semparamsnemtypes.
Em parameterized, params e types fazem parte do contrato: o query traz @pN
e é não executável sem seu binding e seus tipos. Em literal não há binding —os
valores vivem dentro do query— por isso não traz params nem types. Sempre
discrimine por format antes de ler params/types.
subjectColumn é null em todo filtro de eventos: o CTE final projeta eventos, não
sujeitos, e o catálogo não declara uma coluna de identidade. As colunas que o CTE expõe
são as do select da definição.
Exemplo — parameterized (padrão)
Resposta 201 ao POST de exemplo acima:
{
"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" }
}O query é uma única string com quebras \n. Formatado para leitura:
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 é vinculado com params[pN] (seu valor) e types[pN] (seu tipo BigQuery); sem
esse binding o query não é executável.
Como a cadeia é consumida
A resposta não é um query executável: é a cadeia de CTEs que precisa ser envolvida. Nos
snippets abaixo, {query} é substituído pelo valor do campo query tal como vem: ele já
traz o seu próprio CTE_VALID_EVENTS AS ( … ), então o cabeçalho do CTE não é escrito de
novo.
Para executá-la stand-alone:
WITH {query}
SELECT * FROM CTE_VALID_EVENTSPara cruzá-la contra outra consulta, insere-se {query} no próprio WITH do caller
—seguido de uma vírgula— e faz-se JOIN CTE_VALID_EVENTS pela coluna de evento que o
select da definição projeta (por exemplo 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_idO nome CTE_VALID_EVENTS é estável e não depende do filtro: é o finalCteName de todo
filtro de eventos. As colunas que ele expõe, por outro lado, são as do select de
cada definição.
Exemplo — literal
A mesma definição com 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
}Os valores ficam inlineados como literais seguros (strings escapadas entre aspas
simples, TIMESTAMP('…') para instantes, NUMERIC '…' para decimais exatos); não há
params nem types.
Regras de validação
Antes de compilar, a definição passa pelas duas camadas de validação, e ambas retornam
400 Bad Request. A validação de negócio roda em ambos os endpoints: generate-sql
valida a definição do corpo, e :id/sql valida o filtro salvo antes de compilá-lo.
O catálogo completo de regras e suas mensagens exatas está em Definição → Regras de validação.
Respostas de erro
| Status | Descrição |
|---|---|
400 | DTO inválido (campo faltando, tipo incorreto, enum fora do intervalo, format inválido, propriedade desconhecida como name/description) ou uma regra de negócio violada. Em :id/sql, também se o id não for um UUID válido. |
401 | Falta ou é inválido o Authorization: Bearer. |
403 | O usuário não tem a permissão event-criteria:view-sql, ou o x-tenant-id não está autorizado. |
404 | Em :id/sql, não existe um filtro de eventos com esse id no tenant (Event criteria not found), ou o tenant não tem um dataset configurado. |