Gerar SQL
Compila uma definição de audience criteria para SQL do BigQuery, a partir do corpo da requisição (ainda não persistida) ou por id de um critério salvo, em formato parametrizado ou literal.
Gerar SQL
Compila uma definição de audience criteria para SQL do BigQuery (GoogleSQL). Um mesmo motor atende duas entradas:
POST /api/audience-criterias/generate-sql— compila uma definição enviada no corpo (ainda não persistida).GET /api/audience-criterias/:id/sql— compila um critério já salvo, referenciado por seuid.
Ambos retornam a mesma forma de resposta: IAudienceCriteriaSql,
uma união discriminada pelo campo format. O format (parametrizado ou literal)
é escolhido por requisição e governa tanto a forma do query quanto os campos da
resposta.
Dentro de um projeto, o SQL de toda audiência que não seja a audiência raiz sai intersectado com a audiência raiz do projeto — veja Interseção com a audiência raiz.
Auth: Obrigatória — permissão audience-criteria:view-sql (ambos endpoints).
O SQL gerado é pensado para o BigQuery: referencia as tabelas como
`<gcp-project>.<dataset>.<table>`. O query é uma cadeia de CTEs intersectável —
sem o WITH inicial e sem o SELECT final — para que um consumidor a insira
direto dentro de seu próprio WITH … SELECT. O último CTE se chama finalCteName
(CTE_AUDIENCE_USERS) e expõe a coluna de identidade subjectColumn (ex.: user_id).
No formato parametrizado usa parâmetros nomeados @pN. O <gcp-project> (o projeto
do Google Cloud, não um projeto da Reten) vem da configuração de ambiente da API e o <dataset> é resolvido a partir do tenant;
nenhum dos dois faz parte do contrato de entrada.
POST /api/audience-criterias/generate-sql
Compila uma definição enviada no corpo, sem persisti-la.
Auth: Obrigatória — permissão audience-criteria:view-sql
O endpoint responde 201 Created —não 200—, mesmo sem criar nenhum recurso.
Corpo da requisição
Nível raiz (GenerateSqlDto): a definição do critério (CriteriaDefinitionDto)
mais o campo format.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
queryBlocks | QueryBlockDto[] | Sim | Mínimo 1 elemento. Cada bloco ≈ um statement SQL completo. |
composedWith | CreateCompositionDto[] | Não | Compõe esta definição com outras audiências via operações de conjunto. |
format | "parameterized" | "literal" | Não | Formato de saída. Padrão parameterized (ver Formato de saída). |
criteriaId | string (UUID) | null | Não | A audiência salva que esta definição edita, para que o rascunho gere o mesmo SQL que essa audiência gerará ao ser salva (veja Interseção com a audiência raiz). Ausente ou null: uma audiência nova. Deve ser um UUID válido (caso contrário, 400) de uma audiência do projeto ativo (caso contrário, 404). |
Diferentemente de Criar, a definição
não leva name nem description: é apenas a árvore de consulta. A validação é
estrita, então enviar name/description (ou qualquer outra propriedade
desconhecida) produz um 400.
Definição do critério
É a mesma árvore que Criar recebe, sem
os campos name/description. Está documentada aqui de forma autocontida.
queryBlocks[] — bloco de consulta
Cada elemento de queryBlocks (QueryBlockDto) é um statement SQL completo:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
order | number (inteiro) | Sim | Posição do bloco; inteiro ≥ 0. |
setOperator | SetOperatorEnum | Não | Combina este bloco com o anterior. Ausente no primeiro bloco; obrigatório nos demais (ver regras). |
from | FromDto | Sim | Fonte de dados: tabela principal + joins. |
select | SelectDto | Sim | Projeção (colunas e/ou agregações). |
timeWindow | TimeWindowDto | Não | Janela temporal do bloco (união discriminada por timeOperator). |
where | WhereDto | Sim | Árvore de predicados. Obrigatório mesmo que seja um grupo vazio. |
groupBy | ColumnRefDto[] | Não | Colunas de agrupamento. |
having | HavingDto | Não | Filtro sobre agregados. |
O setOperator de um query block combina blocos dentro da mesma definição; não
deve ser confundido com o setOperator de uma composição,
que combina critérios inteiros. São dois níveis distintos de operação de conjunto.
ColumnRefDto — referência a coluna
Usado em toda a árvore (select, filtros, group by, joins, agregados, janela temporal):
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
table | string | Sim | Tabela do catálogo. Não vazio. |
column | string | Sim | Coluna. Não vazio. Pode ser um path de STRUCT (ex.: "pricing.final_amount"). |
Toda coluna é sempre qualificada com sua table para evitar ambiguidade.
from — fonte de dados (FromDto)
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
table | string | Sim | Tabela principal do catálogo. Não vazio. |
joins | JoinDto[] | Não | Joins com outras tabelas. Padrão []. |
unnests | ColumnRefDto[] | Não | Colunas repetidas declaradas como fonte de linhas (uma linha por elemento). Uma audiência não as aceita: omita o campo ou envie [] (o padrão); uma lista não vazia retorna 400. |
Cada JoinDto:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
type | JoinTypeEnum | Sim | INNER | LEFT | RIGHT | FULL. |
table | string | Sim | Tabela a unir (do catálogo). Não vazio. |
on | JoinPredicateDto[] | Sim | Condições de junção. |
Cada JoinPredicateDto é um par de colunas: { left: ColumnRefDto, right: ColumnRefDto }.
select — projeção (SelectDto)
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
distinct | boolean | Sim | Aplica SELECT DISTINCT. |
items | (SelectColumnItem | SelectAggregateItem)[] | Sim | Não vazio. União discriminada pelo campo kind. |
Os items são discriminados por kind:
-
Coluna —
kind: "column":Campo Tipo Obrigatório Descrição kind"column"Sim Discriminador. columnColumnRefDtoSim Coluna projetada. aliasstring Não Deve casar com ^[A-Za-z_][A-Za-z0-9_]*$. -
Agregado —
kind: "aggregate":Campo Tipo Obrigatório Descrição kind"aggregate"Sim Discriminador. aggregateAggregateDtoSim Expressão de agregação. aliasstring Sim Aqui o alias é obrigatório. Mesmo regex de identificador.
AggregateDto:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
aggregationOperator | AggregationOperatorEnum | Sim | COUNT | SUM | AVG | MIN | MAX. |
operand | object | Sim | O que é agregado. União discriminada por kind: column, star ou arithmetic (veja abaixo). |
distinct | boolean | Não | Aplica DISTINCT dentro do agregado. |
operand —o que é agregado— é discriminado por kind:
kind | Campos adicionais | Significado |
|---|---|---|
column | column: ColumnRefDto | Uma coluna do catálogo. |
star | (nenhum) | *: conta linhas. Só é válido com COUNT e sem distinct (COUNT(DISTINCT *) não é permitido). |
arithmetic | arithmeticOperator: ArithmeticOperatorEnum, left, right | Expressão binária. Cada lado é outro operando: uma column ou um arithmetic aninhado (nunca star). |
ArithmeticOperatorEnum: ADD | SUBTRACT | MULTIPLY | SAFE_DIVIDE (uma divisão que retorna
NULL quando o denominador é zero, em vez de fazer a consulta falhar). As colunas de uma expressão
aritmética precisam ser numéricas (INT64, NUMERIC, BIGNUMERIC, FLOAT64), e as expressões se
aninham até 3 níveis de arithmetic, contando o externo.
where — árvore de predicados (WhereDto)
O where é uma árvore recursiva. O nó raiz é sempre um grupo, e suas folhas são de
dois tipos:
kind | O que filtra |
|---|---|
group | Não é uma folha: agrupa filhos com AND / OR. Os grupos podem ser aninhados. |
predicate | Uma coluna escalar de uma tabela do from. |
recordPredicate | Uma coluna RECORD REPEATED: pelo menos 1 elemento satisfaz o sub-filtro. |
Grupo — kind: "group"
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
kind | "group" | Sim | Discriminador. |
logicalOperator | LogicalOperatorEnum (AND | OR) | Sim | Conectivo entre os filhos. |
children | (Group | Leaf)[] | Sim | Filhos do grupo; podem ser grupos aninhados. |
logicalOperator é sempre obrigatório em um grupo, mas só tem efeito com 2 ou
mais filhos. Com menos de 2 filhos seu valor é irrelevante (gera o mesmo SQL), mas
ainda assim deve ser enviado. Um where sem filtros é expresso como um grupo vazio:
{ "kind": "group", "logicalOperator": "AND", "children": [] }.
Folha de coluna — kind: "predicate"
Compara uma coluna escalar qualificada por sua tabela.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
kind | "predicate" | Sim | Discriminador. |
column | ColumnRefDto | Sim | Coluna do predicado. |
comparisonOperator | ComparisonOperatorEnum | Sim | Operador de comparação. |
value | PredicateValue | Depende | Seu formato depende do operador. |
{
"kind": "predicate",
"column": { "table": "user_properties", "column": "country" },
"comparisonOperator": "IN",
"value": ["CL", "MX"]
}Folha de registro — kind: "recordPredicate"
Filtra por uma coluna RECORD REPEATED (um array de structs, como attributes).
Significa que pelo menos um elemento do array satisfaz o sub-filtro.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
kind | "recordPredicate" | Sim | Discriminador. |
column | ColumnRefDto | Sim | Coluna RECORD REPEATED de uma tabela do from. |
subWhere | Sub-árvore relativa | Sim | Condições sobre os sub-campos do elemento. Não pode ficar vazio. |
{
"kind": "recordPredicate",
"column": { "table": "event_transactions", "column": "attributes" },
"subWhere": {
"kind": "group",
"logicalOperator": "AND",
"children": [
{ "kind": "predicate", "field": "key", "comparisonOperator": "EQUALS", "value": "canal" },
{ "kind": "predicate", "field": "value", "comparisonOperator": "EQUALS", "value": "web" }
]
}
}As condições do subWhere são avaliadas sobre um mesmo elemento do array, e não
sobre o array inteiro. O exemplo acima seleciona linhas que têm um atributo cujo key
é canal e cujo value é web — não linhas que têm um atributo com esse key e
outro, diferente, com esse value.
Quais colunas aceitam um filtro de registro é informado pelo
catálogo: as que vêm com
kind: "record".
Sub-árvore relativa
O subWhere é um grupo AND/OR cujas folhas são resolvidas contra os sub-campos que o
registro declara.
Há uma única diferença em relação à árvore de nível superior, e ela é a origem mais comum de erros:
Dentro de uma sub-árvore relativa os operandos não levam tabela: você escreve
"field": "key", e não "column": { "table": "attributes", "column": "key" }. A folha
usa a chave field (uma string), não column.
Folhas permitidas dentro de uma sub-árvore relativa:
- Folha escalar —
{ "kind": "predicate", "field": string, "comparisonOperator": …, "value"?: … }. Ofielddeve ser um sub-campo escalar do registro. - Registro aninhado —
{ "kind": "recordPredicate", "field": string, "subWhere": … }. Um registro dentro de outro registro; aninha sem limite declarado, desde que o catálogo declare cada nível. - Grupo — o mesmo formato de grupo, para combinar as folhas anteriores com
AND/OR.
Forma de value por comparisonOperator
| Operadores | Forma de value | Exemplo |
|---|---|---|
IS_NULL, IS_NOT_NULL | Sem value (omitido) | (omitido) |
IN, NOT_IN | Lista não vazia ScalarValue[] | ["CL", "MX"] |
BETWEEN | Par [min, max] | [10, 20] |
O 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 | "active" · 42 |
ScalarValue = string | number | boolean. Além disso, cada operador deve ser
compatível com o tipo da coluna (ver regras de validação).
groupBy — colunas de agrupamento
Lista opcional de ColumnRefDto[]. Quando o bloco agrega (declara groupBy ou tem um
agregado no select), o groupBy deve coincidir exatamente com as colunas não
agregadas do select (ver regras de validação).
having — filtro sobre agregados (HavingDto)
Mesma estrutura recursiva que where (grupos AND/OR aninhados e mesma regra de value
por operador), mas a folha opera sobre um agregado, não sobre uma coluna:
- Predicado (folha) —
{ kind: "predicate", aggregate: AggregateDto, comparisonOperator: ComparisonOperatorEnum, value?: PredicateValue }. - Grupo — idêntico ao de
where.
timeWindow — janela temporal (TimeWindowDto)
União discriminada por timeOperator. Restringe o bloco a um período de tempo sobre
uma coluna TIMESTAMP do catálogo (reference). É um campo irmão de where, não um
nó da árvore de filtros.
timeOperator | Campos adicionais |
|---|---|
ALL_TIME | (nenhum) — sem restrição de tempo. |
IN_THE_LAST | reference: ColumnRefDto, unit: TimeUnitEnum, value: inteiro > 0 |
IN_THE_FIRST | reference: ColumnRefDto, unit: TimeUnitEnum, value: inteiro > 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 negócio de cada operador:
ALL_TIME— sem janela; considera todo o histórico.IN_THE_LAST— janela móvel relativa a "agora": os últimosvalueunit(ex.: últimos30 DAYS). Recalculada a cada execução.IN_THE_FIRST— janela relativa à origem do ciclo de vida do usuário (sua data de cadastro): os primeirosvalueunitde vida de cada usuário. Ancorada por usuário, não a uma data fixa.SINCE— a partir da data civilfrom(inclusive).BETWEEN— entrefrometo, ambas datas civis inclusivas do dia completo.
As datas civis YYYY-MM-DD (SINCE, BETWEEN) são resolvidas para o início do
dia no fuso horário do tenant. O detalhe do fuso horário é interno; basta enviar a
data civil.
composedWith[] — composição com outras audiências
Cada elemento (CreateCompositionDto) combina esta definição com outra audiência
via uma operação de conjunto:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
operandCriteriaId | string (UUID) | Sim | Id da outra audiência (operando). Deve existir. |
setOperator | SetOperatorEnum | Sim | UNION_DISTINCT | UNION_ALL | INTERSECT_DISTINCT. |
Exemplo
Definição de uma audiência de usuários com uma transação dentro dos seus primeiros 30 dias
desde a data de cadastro (IN_THE_FIRST). Ao omitir format, a resposta é parametrizada (padrão).
curl -X POST https://api.reten.ai/api/audience-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" },
"select": {
"distinct": true,
"items": [
{
"kind": "column",
"column": { "table": "event_transactions", "column": "user_id" }
}
]
},
"timeWindow": {
"timeOperator": "IN_THE_FIRST",
"reference": { "table": "event_transactions", "column": "event_date" },
"unit": "DAYS",
"value": 30
},
"where": { "kind": "group", "logicalOperator": "AND", "children": [] }
}
]
}'import axios from 'axios';
const body = {
queryBlocks: [
{
order: 0,
from: { table: 'event_transactions' },
select: {
distinct: true,
items: [
{
kind: 'column',
column: { table: 'event_transactions', column: 'user_id' },
},
],
},
timeWindow: {
timeOperator: 'IN_THE_FIRST',
reference: { table: 'event_transactions', column: 'event_date' },
unit: 'DAYS',
value: 30,
},
where: { kind: 'group', logicalOperator: 'AND', children: [] },
},
],
// format: 'literal', // opcional; padrão é 'parameterized'
};
const response = await axios.post(
'https://api.reten.ai/api/audience-criterias/generate-sql',
body,
{ headers: { Authorization: 'Bearer <token>', 'x-tenant-id': '<tenant-id>', 'x-project-id': '<project-id>' } },
);
const sql = response.data; // IAudienceCriteriaSqlA resposta é documentada em IAudienceCriteriaSql, com
um exemplo de cada formato.
GET /api/audience-criterias/:id/sql
Compila um critério já salvo, referenciado por seu id. A definição não viaja no
corpo: é obtida do critério persistido no projeto ativo.
Auth: Obrigatória — permissão audience-criteria:view-sql
Status 200 OK (é um GET).
Parâmetros de rota
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | UUID | Sim | Id do audience criteria. Deve ser um UUID válido (caso contrário, 400). |
Parâmetros de consulta
GetSqlDto:
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
format | "parameterized" | "literal" | Não | Formato de saída. Padrão parameterized (ver Formato de saída). |
Exemplo
curl "https://api.reten.ai/api/audience-criterias/f47ac10b-58cc-4372-a567-0e02b2c3d479/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 = 'f47ac10b-58cc-4372-a567-0e02b2c3d479';
const response = await axios.get(
`https://api.reten.ai/api/audience-criterias/${id}/sql`,
{
params: { format: 'literal' }, // opcional; padrão é 'parameterized'
headers: { Authorization: 'Bearer <token>', 'x-tenant-id': '<tenant-id>', 'x-project-id': '<project-id>' },
},
);
const sql = response.data; // IAudienceCriteriaSqlFormato 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 — IAudienceCriteriaSql
É 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 intersectável (uma única string; 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 (estável: CTE_AUDIENCE_USERS); é o "handle" do resultado da audiência — um consumidor faz JOIN {finalCteName} / SELECT … FROM {finalCteName}. |
subjectColumn | string | Coluna de identidade exposta pelo CTE final (ex.: user_id). |
granularity | "table_row" | "record_element" | O que é uma linha do CTE final. Em uma audiência é sempre table_row: uma linha por linha da tabela, uma por usuário. O outro valor do contrato, record_element (uma linha por elemento de um registro repetido declarado como fonte de linhas), não ocorre em audiências, que não aceitam fontes de linhas. |
-
format: "parameterized"—IParameterizedAudienceCriteriaSql:Campo Tipo Descrição format"parameterized"Discriminador. querystring Cadeia de CTEs com placeholders @pN.finalCteNamestring Nome do CTE final. subjectColumnstring Coluna de identidade exposta pelo CTE final. granularity"table_row"|"record_element"O que é uma linha do CTE final (em uma audiência, sempre table_row).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(ex.:STRING,INT64,TIMESTAMP,BOOL,FLOAT64,NUMERIC,BIGNUMERIC). -
format: "literal"—ILiteralAudienceCriteriaSql:Campo Tipo Descrição format"literal"Discriminador. querystring Cadeia de CTEs com os valores já inlineados como literais. finalCteNamestring Nome do CTE final. subjectColumnstring Coluna de identidade exposta pelo CTE final. granularity"table_row"|"record_element"O que é uma linha do CTE final (em uma audiência, sempre table_row).
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.
Exemplo — parameterized (padrão)
Resposta 201 ao POST de exemplo acima (sem format):
{
"format": "parameterized",
"query": "op_5e2a91c07b3d_0 AS (\nSELECT DISTINCT `event_transactions`.`user_id`\nFROM `reten-prod.tenant_a1b2c3d4.event_transactions` AS event_transactions\nINNER JOIN `reten-prod.tenant_a1b2c3d4.user_properties` AS user_properties ON `event_transactions`.`user_id` = `user_properties`.`user_id`\nWHERE `event_transactions`.`event_date` BETWEEN `user_properties`.`sign_up_date` AND TIMESTAMP(DATETIME_ADD(DATETIME(`user_properties`.`sign_up_date`), INTERVAL @p0 DAY))\n),\nop_9a8b7c6d5e4f_0 AS (\nSELECT DISTINCT `user_properties`.`user_id`\nFROM `reten-prod.tenant_a1b2c3d4.user_properties` AS user_properties\nWHERE `user_properties`.`is_active` = @p1\n),\nCTE_AUDIENCE_USERS AS (\nSELECT * FROM op_5e2a91c07b3d_0\nINTERSECT DISTINCT\nSELECT * FROM op_9a8b7c6d5e4f_0\n)",
"finalCteName": "CTE_AUDIENCE_USERS",
"subjectColumn": "user_id",
"granularity": "table_row",
"params": { "p0": 30, "p1": true },
"types": { "p0": "INT64", "p1": "BOOL" }
}O query é uma única string com quebras \n — uma cadeia de CTEs sem o WITH
inicial e sem o SELECT final. Formatado para leitura:
op_5e2a91c07b3d_0 AS (
SELECT DISTINCT `event_transactions`.`user_id`
FROM `reten-prod.tenant_a1b2c3d4.event_transactions` AS event_transactions
INNER JOIN `reten-prod.tenant_a1b2c3d4.user_properties` AS user_properties ON `event_transactions`.`user_id` = `user_properties`.`user_id`
WHERE `event_transactions`.`event_date` BETWEEN `user_properties`.`sign_up_date` AND TIMESTAMP(DATETIME_ADD(DATETIME(`user_properties`.`sign_up_date`), INTERVAL @p0 DAY))
),
op_9a8b7c6d5e4f_0 AS (
SELECT DISTINCT `user_properties`.`user_id`
FROM `reten-prod.tenant_a1b2c3d4.user_properties` AS user_properties
WHERE `user_properties`.`is_active` = @p1
),
CTE_AUDIENCE_USERS AS (
SELECT * FROM op_5e2a91c07b3d_0
INTERSECT DISTINCT
SELECT * FROM op_9a8b7c6d5e4f_0
)Cada @pN é vinculado com params[pN] (seu valor) e types[pN] (seu tipo BigQuery);
sem esse binding o query não é executável. p0 é da audiência e p1 da audiência raiz
do projeto, que aqui mantém sua definição padrão (veja
Interseção com a audiência raiz). O INNER JOIN com
user_properties não está na requisição: IN_THE_FIRST mede os primeiros dias de cada usuário a
partir da data de cadastro, então a API o adiciona.
Para executá-la stand-alone, envolva a cadeia com o nome do CTE da resposta:
WITH {query} SELECT * FROM {finalCteName}. Para intersectá-la contra outra query,
insira {query} no seu próprio WITH e faça JOIN {finalCteName} por {subjectColumn}.
Exemplo — literal
A mesma definição com format: "literal" (na variante by-id, ?format=literal):
{
"format": "literal",
"query": "op_5e2a91c07b3d_0 AS (\nSELECT DISTINCT `event_transactions`.`user_id`\nFROM `reten-prod.tenant_a1b2c3d4.event_transactions` AS event_transactions\nINNER JOIN `reten-prod.tenant_a1b2c3d4.user_properties` AS user_properties ON `event_transactions`.`user_id` = `user_properties`.`user_id`\nWHERE `event_transactions`.`event_date` BETWEEN `user_properties`.`sign_up_date` AND TIMESTAMP(DATETIME_ADD(DATETIME(`user_properties`.`sign_up_date`), INTERVAL 30 DAY))\n),\nop_9a8b7c6d5e4f_0 AS (\nSELECT DISTINCT `user_properties`.`user_id`\nFROM `reten-prod.tenant_a1b2c3d4.user_properties` AS user_properties\nWHERE `user_properties`.`is_active` = TRUE\n),\nCTE_AUDIENCE_USERS AS (\nSELECT * FROM op_5e2a91c07b3d_0\nINTERSECT DISTINCT\nSELECT * FROM op_9a8b7c6d5e4f_0\n)",
"finalCteName": "CTE_AUDIENCE_USERS",
"subjectColumn": "user_id",
"granularity": "table_row"
}Formatado para leitura:
op_5e2a91c07b3d_0 AS (
SELECT DISTINCT `event_transactions`.`user_id`
FROM `reten-prod.tenant_a1b2c3d4.event_transactions` AS event_transactions
INNER JOIN `reten-prod.tenant_a1b2c3d4.user_properties` AS user_properties ON `event_transactions`.`user_id` = `user_properties`.`user_id`
WHERE `event_transactions`.`event_date` BETWEEN `user_properties`.`sign_up_date` AND TIMESTAMP(DATETIME_ADD(DATETIME(`user_properties`.`sign_up_date`), INTERVAL 30 DAY))
),
op_9a8b7c6d5e4f_0 AS (
SELECT DISTINCT `user_properties`.`user_id`
FROM `reten-prod.tenant_a1b2c3d4.user_properties` AS user_properties
WHERE `user_properties`.`is_active` = TRUE
),
CTE_AUDIENCE_USERS AS (
SELECT * FROM op_5e2a91c07b3d_0
INTERSECT DISTINCT
SELECT * FROM op_9a8b7c6d5e4f_0
)Os valores ficam inlineados como literais seguros (aqui 30 e TRUE; strings vão
escapadas entre aspas simples); não há params nem types.
Interseção com a audiência raiz
Todo projeto tem uma audiência raiz, e toda outra audiência do projeto é avaliada dentro dela. Em ambos os endpoints, o SQL de qualquer audiência que não seja a audiência raiz sai intersectado com a audiência raiz do projeto:
- A audiência e a audiência raiz mantêm seus próprios CTEs, e o CTE final intersecta o último CTE de cada uma:
CTE_AUDIENCE_USERS AS (SELECT * FROM <último CTE da audiência> INTERSECT DISTINCT SELECT * FROM <último CTE da audiência raiz>). - No formato
parameterized, os parâmetros@pNda audiência vêm primeiro e os da audiência raiz em seguida. - Não há interseção quando a audiência compilada é a própria audiência raiz —
GET /:id/sqlcom oidda audiência raiz, ou um rascunho cujocriteriaIdé a audiência raiz. - A interseção é posicional (
SELECT *nos dois lados): a audiência deve projetar as mesmas colunas que a audiência raiz, na mesma ordem e com tipos compatíveis — com a definição padrão da audiência raiz, apenasuser_id. Caso contrário, o SQL falha ao ser executado.
Os CTEs intermediários levam no nome um prefixo do id de cada audiência (em um rascunho
enviado sem criteriaId, esse prefixo muda a cada requisição), então referencie sempre o
resultado por meio de finalCteName.
Por exemplo, uma audiência de usuários com uma transação COMPLETED, em um projeto cuja
audiência raiz foi editada para country = 'CL' (formato literal, formatado para leitura):
op_f47ac10b58cc_0 AS (
SELECT DISTINCT `event_transactions`.`user_id`
FROM `<gcp-project>.<dataset>.event_transactions` AS event_transactions
WHERE `event_transactions`.`status` = 'COMPLETED'
),
op_9a8b7c6d5e4f_0 AS (
SELECT DISTINCT `user_properties`.`user_id`
FROM `<gcp-project>.<dataset>.user_properties` AS user_properties
WHERE `user_properties`.`country` = 'CL'
),
CTE_AUDIENCE_USERS AS (
SELECT * FROM op_f47ac10b58cc_0
INTERSECT DISTINCT
SELECT * FROM op_9a8b7c6d5e4f_0
)Regras de validação
Antes de compilar, a definição passa pelas mesmas duas camadas de validação que
Criar, e ambas
retornam 400 Bad Request:
- Validação do DTO (estrutura, tipos, enums, campos obrigatórios,
formatválido).messageé um array de strings e propriedades desconhecidas são rejeitadas (incluindoname/description). - Validação de negócio (a definição deve gerar SQL válido e coerente com o
catálogo).
messageé um string único com a regra violada.
A validação de negócio roda em ambos os endpoints: generate-sql valida a
definição do corpo, e :id/sql valida o critério salvo antes de compilá-lo. O
catálogo completo de regras e suas mensagens exatas está em
Criar.
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; em generate-sql, se o criteriaId não for. |
401 | Falta ou é inválido o Authorization: Bearer. |
403 | O usuário não tem a permissão audience-criteria:view-sql, ou o x-tenant-id não está autorizado. |
404 | Em :id/sql, não existe um audience criteria com esse id no projeto ativo. Em generate-sql, um operandCriteriaId de composedWith ou o criteriaId não existe no projeto ativo. |
Excluir Audiência
Arquiva (soft delete) um audience criteria; responde 204 sem corpo. Falha com 409 se for a audiência raiz do projeto, e com 400 se outro critério o referencia como operando.
Validar
Valida uma definição de audience criteria e executa um dry-run contra o BigQuery, a partir do corpo (ainda não persistida) ou por id de um critério salvo; retorna se é válida e o custo estimado, sem dados nem SQL.