# SQL Editor (/docs/product-analytics/sql)

When a question doesn't fit a trend, funnel or retention chart, write SQL. **SQL Editor** in the sidebar has everything you can query on the left, the editor on top and the result below.

```sql
SELECT person.properties.plan AS plan,
  uniq(person_id) AS weekly_users
FROM events
WHERE timestamp > now() - INTERVAL 7 DAY
GROUP BY plan
ORDER BY weekly_users DESC
```

Press <kbd>⌘</kbd> <kbd>↵</kbd> (<kbd>Ctrl</kbd> <kbd>↵</kbd> on Windows and Linux) to run it.

## The explorer [#the-explorer]

The panel on the left lists everything you can query:

- **Tables**, with row counts. Open one to see its columns and their types. `events` also lists every property as `properties.<key>` (with how many events carry it) and its event names; `persons` lists person properties, and `groups` lists each group type's properties.
- **Views**: your saved queries, which work as tables.
- **Computed properties**: person properties that saved queries write.
- **Cohorts**, with their size.

Double-click a name, press <kbd>↵</kbd>, or drag it into the editor to insert it. Hover a row for its description, and use **▶ Preview** to see its first 100 rows or most common values. Preview runs in the editor when it's empty, and in a new tab otherwise, so you don't lose your work. Right-click a row for everything else: copy its name, insert a cohort as `inCohort(person_id, 3)`, or open, rename or delete a view.

Search finds tables, columns, properties, views and cohorts. The filter button hides built-in `$` properties, system tables or empty sections. Use the arrow keys to move through the list. Drag the panel's edge to resize it, or collapse it with <kbd>‹</kbd>.

## Tables [#tables]

| Table | One row per |
| --- | --- |
| `events` | Event your apps sent |
| `persons` | Person |
| `sessions` | Visit: start and end, duration, pageviews, entry and exit page, source, bounce |
| `groups` | Company, team or other [group](/docs/people/groups) |
| `person_overrides` | Merged identity: an old person id and the person it now belongs to |
| `cohort_static_members` | Member of a static or SQL cohort (use `inCohort()` for any cohort) |

Times are in your project's timezone. `person_id` already joins up the ids of someone who used several devices before signing in, so `uniq(person_id)` counts real people.

## Shorthands [#shorthands]

| Write | To get |
| --- | --- |
| `properties.plan` | A property, as text. Use `properties['my key']` for keys with spaces. |
| `toFloat64OrNull(properties.amount)` | A property as a number |
| `person.properties.email` | On `events`: the property of the person who sent the event |
| `inCohort(person_id, 3)` | Whether the person is in [cohort](/docs/people/cohorts) 3 |

The editor completes tables, views, columns, property keys and cohorts as you type. After `properties.` it suggests the keys of the table you select from.

## Saved queries are tables [#saved-queries-are-tables]

Save a query and give it a name like `paying_users`. From then on it works as a table in any other query:

```sql
SELECT count() FROM paying_users
WHERE upgraded_at > now() - INTERVAL 14 DAY
```

That's how you build your own definitions, like "active account", "first order" or "power user", once, without setting anything up elsewhere. Saved queries can build on each other. Datahop checks a query runs before saving it, and won't delete one that another saved query or a cohort still reads from.

## Charts and dashboards [#charts-and-dashboards]

Results show as a table, or switch to **Line**, **Bar** or **Number** and pick the columns to plot. Saving keeps the chart. **⋯ → Create alert** watches one of its numbers (see [Alerts](/docs/alerts)). **⋯ → Add to dashboard** puts it on any [dashboard](/docs/product-analytics/dashboards), including public ones: people with the link see the result, never the SQL. A dashboard's date range doesn't apply to SQL cards, because the query sets its own.

Copy or download any result as CSV from the result bar.

## Cohorts and person properties from SQL [#cohorts-and-person-properties-from-sql]

A saved query with a `person_id` column can feed the rest of Datahop. Open it and choose **⋯ → Use with people**:

- **Create cohort** makes a [cohort](/docs/people/cohorts#build-a-cohort-from-sql) of everyone the query returns.
- **Set person properties** writes every other column onto those people. With the query below, each customer gets `orders` and `lifetime_value`, which you can then use to target a [feature flag](/docs/feature-flags), segment an experiment or break down a chart.

```sql
SELECT person_id,
  count() AS orders,
  round(sum(toFloat64OrNull(properties.amount)), 2) AS lifetime_value
FROM events
WHERE event = 'order_completed'
GROUP BY person_id
```

Both recalculate every hour and whenever you change the query. People who drop out of the result lose the cohort membership or the properties. Datahop manages these property names, so don't also set them from your app. Turning **Set person properties** off removes them.

## Ask AI [#ask-ai]

**Ask AI** turns a question into SQL that runs against your real events and properties, and explains what it returns. When a query fails, **Fix with AI** rewrites it using the error. Both need the AI assistant switched on for your workspace.

## Agents [#agents]

Agents use the same SQL. Over MCP, `sql_schema` lists the tables, saved queries, events, properties and cohorts; `sql` runs a query; and `save_query` saves one as a table, so an agent can define `active_accounts` once and every person and agent can use it. See the [tool reference](/docs/agents/tools#querying).

<CopyPrompt preset="connect">
  Hand this to your agent to connect it, then ask it questions in plain language. It writes and saves the SQL.
</CopyPrompt>

## Limits [#limits]

One read-only `SELECT` per run, up to 20 seconds and 10,000 rows. Aggregate in SQL or add a `LIMIT` for bigger results.
