Prompt
## Pergunta de negócio
{{pergunta}}
## Esquema disponível
```sql
{{esquema}}
```
Dialeto: {{dialeto}}
## Etapa 1, decisões escondidas (antes de qualquer SQL)
Liste toda decisão que a pergunta deixa em aberto e que muda o resultado. Típicas:
- Qual data conta: criação, pagamento, competência?
- Fuso horário e onde vira o dia
- O que fazer com registro cancelado, estornado, de teste ou duplicado
- Se o denominador inclui quem entrou no meio do período
- Se valor é bruto ou líquido de imposto e desconto
Para cada decisão: as opções, e qual você escolheu como padrão e por quê.
## Etapa 2, a consulta
SQL comentado, com cada decisão da etapa 1 marcada no ponto onde foi aplicada.
- CTEs nomeadas por significado de negócio, não `t1`, `t2`
- Sem `SELECT *`
- Filtros de data explícitos e inclusivos/exclusivos declarados
## Etapa 3, como conferir
Duas checagens rápidas que provam que o resultado não é absurdo (ordem de grandeza,
total que bate com outra fonte, linha conhecida).
## Etapa 4, armadilhas deste esquema
O que neste esquema específico costuma produzir número errado.What this prompt does
Traduz uma pergunta ambígua de negócio em SQL, listando primeiro as decisões que o analista teria que tomar no caminho, qual data conta, o que é usuário ativo, como tratar cancelamento. A consulta vem depois, com as escolhas comentadas.
Preview
Etapa 1, decisões escondidas
1. O que é "ativo"? Teve sessão? Tem assinatura ativa? Ambos?
→ Escolhi: teve ao menos uma sessão no período. É a definição de uso, e o esquema tem sessions. Se o time quiser "ativo = pagante", a consulta é outra, e o número será bem menor.
2. Sessão de qualquer duração conta? Uma sessão de 2 segundos é provavelmente um bot ou um toque acidental.
→ Escolhi: duração ≥ 10s. É arbitrário e você deve calibrar, mas não filtrar nada infla o número.
3. Fuso horário. started_at está em UTC ou local? Junho em UTC ≠ junho em São Paulo.
→ Escolhi: converto para America/Sao_Paulo. Se a coluna já estiver em horário local, remova a conversão, aplicá-la duas vezes desloca 3 horas de dados nas bordas do mês.
4. Usuário deletado no meio do mês. Ele usou o produto em junho e saiu no dia 20.
→ Escolhi: conta. Ele foi ativo em junho; o deleted_at posterior não apaga isso. Filtrar por deleted_at IS NULL é o erro mais comum aqui e subestima meses passados de forma crescente.
5. Usuários de teste. is_test existe, então alguém já foi mordido por isso.
→ Escolhi: excluir.
6. Borda do período. 30 de junho às 23h59 conta?
→ Escolhi: intervalo semiaberto >= 01/06 AND < 01/07. Evita o buraco de um segundo que BETWEEN com data cria.
Etapa 2, a consulta
WITH sessoes_do_periodo AS (
SELECT
s.user_id,
s.id AS session_id
FROM sessions AS s
WHERE
-- decisão 3 e 6: fuso local, intervalo semiaberto
(s.started_at AT TIME ZONE 'UTC' AT TIME ZONE 'America/Sao_Paulo')
>= DATE '2026-06-01'
AND (s.started_at AT TIME ZONE 'UTC' AT TIME ZONE 'America/Sao_Paulo')
< DATE '2026-07-01'
-- decisão 2: descarta toque acidental e bot
AND s.duration_s >= 10
),
usuarios_validos AS (
SELECT u.id
FROM users AS u
-- decisão 5: fora os usuários de teste
WHERE u.is_test = FALSE
-- decisão 4: NÃO filtramos deleted_at de propósito.
-- Quem usou em junho foi ativo em junho, mesmo que tenha saído depois.
)
SELECT COUNT(DISTINCT s.user_id) AS usuarios_ativos
FROM sessoes_do_periodo AS s
INNER JOIN usuarios_validos AS u ON u.id = s.user_id;
Etapa 3, como conferir
- Ordem de grandeza: rode a mesma consulta para maio e para julho. Um salto acima de 30% entre meses consecutivos quase sempre é erro de consulta, não crescimento real.
- Teto lógico: o resultado tem que ser menor que
COUNT(*) FROM users WHERE is_test = FALSE AND created_at < '2026-07-01'. Se for maior, há duplicata emsessions.user_idapontando para usuário inexistente, sinal de FK ausente.
Etapa 4, armadilhas deste esquema
subscriptions.statusnão tem histórico. Só existe o estado atual. Você não consegue responder "quantos estavam pagantes em junho" com este esquema, só "quantos estão pagantes hoje e eram usuários em junho". São coisas diferentes, e a segunda não serve para série temporal. Precisa de uma tabela de eventos de assinatura.canceled_atsemstarted_atda assinatura. Não dá para calcular tempo de vida.- Sem índice aparente em
sessions.started_at. Em tabela grande, a conversão de fuso dentro doWHEREimpede uso de índice. Se demorar, calcule os limites em UTC do lado de fora e compare direto comstarted_at.
How to use
- 1
Copy the template
Use the copy button in the block above. It is 32 lines of instruction.
- 2
Fill in the variables
Swap the 3 highlighted variables for your own content. Leaving them blank degrades the output.
- 3
Pick one of the listed models
Run it on one of the models listed on the side. A smaller model usually degrades the result.
- 4
Paste and run
Or use the lab, with your own key, and compare it against the preview.