> ## Documentation Index
> Fetch the complete documentation index at: https://help.treble.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Otimização de Consultas

> Como escrever consultas eficientes no Data Warehouse.

# Otimização de Consultas

O Data Warehouse é construído sobre o ClickHouse, um banco de dados colunar. Isso significa que as consultas se comportam de forma diferente de um banco de dados relacional tradicional. Aqui explicamos como aproveitar a estrutura para obter resultados rápidos.

## Princípios Fundamentais

### 1. Sempre filtre por data

Todas as tabelas de fatos são **particionadas por mês** (`toYYYYMM(created_at)`). Filtrar por data permite ao ClickHouse pular partições inteiras sem lê-las.

```sql theme={null}
-- Bom: ClickHouse lê apenas 1 partição
SELECT * FROM fact_conversations
WHERE created_at >= '2026-04-01' AND created_at < '2026-05-01'

-- Ruim: ClickHouse lê todas as partições
SELECT * FROM fact_conversations
WHERE agent_name = 'Juan'
```

### 2. Você não precisa filtrar por company\_id

Seu usuário tem uma **política de linha** que filtra automaticamente pela sua empresa. Você não precisa adicionar `WHERE company_id = ...` — é aplicado de forma transparente.

### 3. Selecione apenas as colunas que precisa

ClickHouse é colunar: ele só lê as colunas que você menciona no seu `SELECT`. Menos colunas = menos dados lidos = mais rápido.

```sql theme={null}
-- Bom: lê apenas 3 colunas
SELECT conversation_id, created_at, status
FROM fact_conversations
WHERE created_at >= now() - INTERVAL 7 DAY

-- Evite: lê todas as colunas
SELECT *
FROM fact_conversations
WHERE created_at >= now() - INTERVAL 7 DAY
```

### 4. Use LIMIT para explorar

Ao explorar dados, use `LIMIT` para evitar trazer milhões de linhas:

```sql theme={null}
SELECT * FROM fact_agent_messages
WHERE created_at >= now() - INTERVAL 1 DAY
ORDER BY created_at DESC
LIMIT 100
```

## Estrutura Interna das Tabelas

Cada tabela tem uma **ordem de classificação** (sort key) que determina como os dados são organizados em disco. Consultas que filtram pelas primeiras colunas do sort key são muito mais eficientes.

### Sort Keys por Tabela

| Tabela                      | Sort Key                                             | Filtra eficientemente por |
| --------------------------- | ---------------------------------------------------- | ------------------------- |
| `fact_conversations`        | `(company_id, created_at, conversation_id)`          | data, conversation\_id    |
| `fact_agent_messages`       | `(company_id, created_at, message_id)`               | data, message\_id         |
| `fact_redirections`         | `(company_id, created_at, redirection_id)`           | data                      |
| `fact_agent_status_changes` | `(company_id, agent_id, created_at)`                 | agent\_id + data          |
| `fact_agent_daily`          | `(company_id, day, agent_id)`                        | dia, agent\_id            |
| `fact_sessions`             | `(company_id, created_at, session_id)`               | data, session\_id         |
| `fact_deployment_status`    | `(company_id, timestamps_eta, deployment_id)`        | data                      |
| `fact_deployment_daily`     | `(company_id, day, poll_id)`                         | dia, poll\_id             |
| `fact_inbound_messages`     | `(company_id, created_at, session_id)`               | data                      |
| `fact_whatsapp_links`       | `(company_id, created_at, event_id)`                 | data                      |
| `fact_hsm_responses`        | `(company_id, response_date, interaction_answer_id)` | data                      |

<Note>
  `company_id` é sempre a primeira coluna do sort key. Como seu usuário tem um filtro automático por empresa, todas as suas consultas aproveitam essa otimização sem que você precise fazer nada.
</Note>

### Índices Secundários

Algumas tabelas têm índices adicionais que ajudam a filtrar por colunas que não estão no sort key:

| Tabela                   | Índice                 | Coluna             | Tipo          | Útil para                                           |
| ------------------------ | ---------------------- | ------------------ | ------------- | --------------------------------------------------- |
| `fact_agent_messages`    | `idx_conversation_id`  | `conversation_id`  | minmax        | Buscar mensagens de uma conversa específica         |
| `fact_agent_messages`    | `idx_sender`           | `sender`           | bloom\_filter | Filtrar por `sender = 'AGENT'` ou `sender = 'USER'` |
| `fact_sessions`          | `idx_poll_id`          | `poll_id`          | minmax        | Filtrar por campanha                                |
| `fact_sessions`          | `idx_inbound_outbound` | `inbound_outbound` | bloom\_filter | Filtrar por tipo INBOUND/OUTBOUND                   |
| `fact_deployment_status` | `idx_poll_id`          | `poll_id`          | minmax        | Filtrar por campanha                                |
| `fact_deployment_status` | `idx_status`           | `status`           | bloom\_filter | Filtrar por status de envio                         |
| `fact_inbound_messages`  | `idx_poll_id`          | `poll_id`          | minmax        | Filtrar por campanha                                |
| `fact_hsm_responses`     | `idx_hsm_id`           | `hsm_id`           | minmax        | Filtrar por template HSM                            |
| `fact_hsm_responses`     | `idx_poll_id`          | `poll_id`          | minmax        | Filtrar por campanha                                |

## Limites do Sistema

Seu usuário tem os seguintes limites para proteger a estabilidade do sistema:

| Limite                        | Valor       |
| ----------------------------- | ----------- |
| Tempo máximo de execução      | 30 segundos |
| Máximo de linhas lidas        | 50 milhões  |
| Máximo de bytes lidos         | 5 GB        |
| Máximo de linhas no resultado | 500.000     |
| Máximo de memória             | 2 GB        |

Se sua consulta exceder algum desses limites, ela será cancelada automaticamente. Para evitar:

* Adicione filtros de data mais estreitos
* Selecione menos colunas
* Use `LIMIT`
* Pré-agregue com `GROUP BY` em vez de trazer linhas individuais

## Padrões Comuns

### JOIN entre tabelas de fatos

Você pode cruzar tabelas usando `conversation_id` ou `survey_user_id`:

```sql theme={null}
-- Mensagens de uma conversa com dados da conversa
SELECT
    fc.conversation_id,
    fc.agent_name,
    fm.created_at AS data_mensagem,
    fm.sender,
    fm.content
FROM client_analytics.fact_conversations fc
INNER JOIN client_analytics.fact_agent_messages fm
    ON fm.conversation_id = fc.conversation_id
WHERE fc.created_at >= now() - INTERVAL 7 DAY
ORDER BY fm.created_at
LIMIT 1000
```

### JOIN com dimensões

```sql theme={null}
-- Produtividade por equipe (team_name vem da dimensão)
SELECT
    at.team_name,
    sum(ad.chats_handled) AS chats,
    round(avg(ad.avg_first_response_sec), 0) AS resposta_media_seg
FROM client_analytics.fact_agent_daily ad
INNER JOIN client_analytics.dim_agent_tags at ON at.agent_id = ad.agent_id
WHERE ad.day >= today() - 30
GROUP BY at.team_name
ORDER BY chats DESC
```

### Nível de serviço personalizado

```sql theme={null}
-- Defina seu próprio limite de SLA
SELECT
    toDate(created_at) AS dia,
    count() AS conversas,
    countIf(first_response_sec <= 60) AS dentro_60s,
    countIf(first_response_sec <= 120) AS dentro_120s,
    countIf(first_response_sec <= 300) AS dentro_5min,
    round(countIf(first_response_sec <= 120) * 100.0 / count(), 1) AS sla_pct
FROM client_analytics.fact_conversations
WHERE created_at >= now() - INTERVAL 30 DAY
  AND first_response_sec IS NOT NULL
GROUP BY dia
ORDER BY dia DESC
```
