Reten Docs
Filtros de eventos

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.

RotaOrigem da definiçãoCódigo de sucesso
POST /api/event-criterias/generate-sqlO corpo201
GET /api/event-criterias/:id/sqlO filtro salvo200

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

CampoTipoObrigatórioDescrição
queryBlocksQueryBlockDto[]SimMínimo 1 elemento. A definição a compilar.
formatstringNãoparameterized | 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âmetroTipoDescrição
idUUIDID do filtro de eventos.

Parâmetros de consulta

ParâmetroTipoPadrãoDescrição
formatstringparameterizedparameterized | 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.

formatComo emite os valoresCampos extras na resposta
parameterizedPlaceholders nomeados @pN no query; o binding vai separado.params, types
literalOs valores são inlineados como literais seguros dentro do query.(nenhum)
  • Em generate-sql (POST) o format vai no corpo.
  • Em :id/sql (GET) o format vai 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):

CampoTipoDescrição
querystringUma 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.
finalCteNamestringNome do CTE final da cadeia; sempre CTE_VALID_EVENTS. É o "handle" do resultado: um consumidor faz SELECT … FROM CTE_VALID_EVENTS.
subjectColumnstring | nullColuna de identidade exposta pelo CTE final. Em um filtro de eventos é sempre null.
  • format: "parameterized":

    CampoTipoDescriçã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; sem params nem types.

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_EVENTS

Para 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_id

O 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

StatusDescrição
400DTO 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.
401Falta ou é inválido o Authorization: Bearer.
403O usuário não tem a permissão event-criteria:view-sql, ou o x-tenant-id não está autorizado.
404Em :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.