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

# SQL Queries

> Execute SELECT queries via API or CLI — workspace-scoped and security-validated.

When built-in metrics don't cover your question, you can run SQL directly against ClickHouse. Tell scopes every query to your workspace and only allows SELECT — so you get full query power without risk of modifying data.

## Quick start

From the CLI:

```bash theme={null}
tell query "SELECT event_name, COUNT(*) as total FROM events_v1 GROUP BY event_name ORDER BY total DESC"
```

Or via the API:

```bash theme={null}
curl -X POST "https://your-tell-server/api/v1/data/query" \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Workspace-ID: 1" \
  -d '{"query": "SELECT event_name, COUNT(*) as total FROM events_v1 GROUP BY event_name ORDER BY total DESC"}'
```

You don't need to worry about workspace isolation — Tell rewrites your query to scope it automatically.

## Available tables

| Table            | Contents                                                                                           |
| ---------------- | -------------------------------------------------------------------------------------------------- |
| `events_v1`      | Product events — `timestamp`, `event_name`, `device_id`, `session_id`, `properties` (JSON)         |
| `context_v1`     | Device and session context — `device_type`, `operating_system`, `country`, `app_version`, and more |
| `logs_v1`        | Application logs — `level`, `source`, `service`, `message` (JSON)                                  |
| `users_v1`       | User profiles — `user_id`, `email`, `name`                                                         |
| `user_traits_v1` | User properties — `user_id`, `trait_key`, `trait_value`                                            |
| `user_devices`   | Device-to-user mappings — `user_id`, `device_id`, `linked_at`                                      |
| `snapshots_v1`   | Integration snapshots — `source`, `entity`, `metrics` (JSON)                                       |

To discover tables and columns programmatically:

```bash theme={null}
# List available tables
curl "https://your-tell-server/api/v1/data/sources" \
  -H "Authorization: Bearer $TOKEN" -H "X-Workspace-ID: 1"

# List columns for a table
curl "https://your-tell-server/api/v1/data/sources/events/fields" \
  -H "Authorization: Bearer $TOKEN" -H "X-Workspace-ID: 1"

# Get distinct values for a field
curl "https://your-tell-server/api/v1/data/sources/events/values/event_name" \
  -H "Authorization: Bearer $TOKEN" -H "X-Workspace-ID: 1"
```

## Example queries

**Events by type in the last 7 days:**

```sql theme={null}
SELECT event_name, COUNT(*) as count, COUNT(DISTINCT device_id) as unique_devices
FROM events_v1
WHERE timestamp > now() - INTERVAL 7 DAY
GROUP BY event_name
ORDER BY count DESC
```

**Error logs by service in the last 24 hours:**

```sql theme={null}
SELECT service, COUNT(*) as errors
FROM logs_v1
WHERE level IN ('ERROR', 'CRITICAL')
  AND timestamp > now() - INTERVAL 24 HOUR
GROUP BY service
ORDER BY errors DESC
```

**Users with multiple devices:**

```sql theme={null}
SELECT u.user_id, u.email, COUNT(DISTINCT ud.device_id) as devices
FROM users_v1 u
JOIN user_devices ud ON u.user_id = ud.user_id
GROUP BY u.user_id, u.email
HAVING devices > 1
ORDER BY devices DESC
```

## CLI output formats

The `tell query` command supports three output formats:

```bash theme={null}
# Table (default) — columns aligned in your terminal
tell query "SELECT event_name, COUNT(*) FROM events_v1 GROUP BY event_name"

# JSON — one object per row
tell query --format json "SELECT event_name, COUNT(*) FROM events_v1 GROUP BY event_name"

# CSV — for piping into other tools
tell query --format csv "SELECT event_name, COUNT(*) FROM events_v1 GROUP BY event_name"
```

## Security

Tell validates every query before execution:

* **SELECT only** — `INSERT`, `UPDATE`, `DELETE`, `DROP`, `ALTER`, and `TRUNCATE` are blocked
* **Workspace scoping** — table references are rewritten to `{workspace_id}.table_name` automatically
* **No multi-statement** — semicolons cannot separate multiple queries
* **Rate limited** — 30 queries per minute via the API

CTEs (`WITH ... AS`), subqueries, and JOINs all work normally.

## Limits

| Parameter      | Default | Maximum    |
| -------------- | ------- | ---------- |
| Result rows    | 1,000   | 10,000     |
| Execution time | 60s     | 60s        |
| API rate       | —       | 30 req/min |

Set a custom limit with `LIMIT` in your query or `"limit"` in the API request body.
