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

# Saved queries

> Create reusable SQL questions in the Configview dashboard, then run them from the dashboard, Slack, or your connected AI assistant.

<Note>
  **Already connected an AI assistant?** This page covers what your assistant can call and how to author new queries. If you haven't connected one yet, start with [Connect AI assistants](/llm).
</Note>

## What it is

A **saved query** is a piece of SQL — with a name, description, and optional parameters — stored in your Configview instance so you (or your team, or an AI assistant) can re-run it without rewriting it. Think of it as a bookmark for a question you'll ask more than once: *"Which Okta groups are missing MFA?"*, *"AWS users created in the last 30 days"*, *"GCP projects with public buckets"*.

Once saved, a query is available from three places:

| Surface                 | What it does                                                                    | Who creates queries here                                                                                     |
| ----------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| **Dashboard**           | Tile on the main dashboard. Run anytime, share results.                         | Anyone with admin access                                                                                     |
| **Slack bot**           | Ask the bot in natural language; it picks the matching query and posts results. | Curated catalog + queries you mark "Enable for Slack bot"                                                    |
| **MCP / LLM connector** | Claude, ChatGPT, Cursor, etc. call your saved queries via [MCP](/llm).          | **Curated catalog only today** — see [Make a query available to the LLM](#make-a-query-available-to-the-llm) |

This page walks through creating a saved query in the dashboard end-to-end.

***

## Before you start

You need:

* **Admin access** to your Configview dashboard.
* **A question you can answer with SQL** against your ingested data. If you're not sure what's available, browse **Admin > Query > Select a Table** — every ingestion source shows up as a table (e.g. `aws_iam_users`, `okta_users`, `google_users`, `kandji_devices`).
* **No SQL experience required** for simple cases — the page includes a filter builder that writes the SQL for you.

***

## Step 1: Open the Query editor

In the dashboard sidebar, click **Admin > Query**.

You'll see:

* A **Select a Table** dropdown (lists every ingestion table).
* A **Raw SQL Query** text field (free-form).
* A **filter builder** that appears once you pick a table (for users who'd rather click than type SQL).
* **RUN Query** and **SAVE** buttons in the top-right of the card.

***

## Step 2: Build the query

Pick whichever approach matches your comfort level:

### Option A — Use the filter builder (no SQL needed)

1. Pick a table from **Select a Table**. The text field auto-fills with `SELECT * FROM <table>`.
2. Scroll to the **filter builder** that appears below.
3. Add filter rows — column, operator (`=`, `LIKE`, `IN`, etc.), value — and click **Apply**.
4. The builder rewrites the **Raw SQL Query** field for you. You can keep clicking to add more filters, or jump into the text field to fine-tune.

### Option B — Write SQL directly

Just type or paste SQL into the **Raw SQL Query** field. Any valid `SELECT` against your ingestion tables works. Examples:

```sql theme={null}
SELECT email, status, last_login_at
FROM okta_users
WHERE mfa_enabled = 0
ORDER BY last_login_at DESC;
```

```sql theme={null}
SELECT u.email, COUNT(*) AS group_count
FROM google_users u
JOIN google_group_members gm ON gm.user_email = u.email
WHERE u.suspended = 1
  AND u.run_at = (SELECT MAX(run_at) FROM google_users)
GROUP BY u.email
HAVING group_count > 0;
```

<Note>
  **Snapshot tables — important caveat.** Some ingestion tables (e.g. `google_users`, `google_groups`, `kandji_devices`) append a full snapshot per run instead of replacing. Without a `run_at` filter, you'll get duplicate rows — one per ingestion run. Always include:

  ```sql theme={null}
  WHERE <table>.run_at = (SELECT MAX(run_at) FROM <table>)
  ```

  If you see N× the rows you expected, this is almost always the cause.
</Note>

***

## Step 3: Run it and check the result

Click **RUN Query**.

Results appear in a DataGrid below:

* **Search** filters the result client-side.
* **Column picker** lets you hide/show columns.
* **Download CSV** exports the rows.

Iterate until the result is what you want. Saving a broken query is a waste of a name — get the SQL right first.

***

## Step 4: Save it

Click **SAVE**. The **Save Custom Query** modal opens:

| Field                                  | What it's for                                                                                                                                                 |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Title** *(required)*                 | What shows up on the dashboard tile and in the LLM/Slack picker. Be specific — *"Suspended Okta users still in groups"* beats *"Stale users"*.                |
| **Description**                        | One or two sentences. This is what an AI assistant reads to decide whether to call your query. Lead with a verb: *"Lists..."*, *"Returns...", "Looks up..."*. |
| **Category**                           | Groups queries on the dashboard. Defaults to `custom`. Pick an existing category if one fits.                                                                 |
| **Labels**                             | Short tags for filtering later. Either pick from existing labels or type new ones.                                                                            |
| **Enable for Slack bot**               | Toggle on to surface this query in the Slack bot's natural-language picker.                                                                                   |
| **Slack summary** *(when Slack is on)* | Plain-English description the bot uses to match the query to a question. *"Returns suspended Okta users that still belong to one or more groups."*            |
| **Cache TTL (seconds)**                | How long results are cached before re-running. Default `3600` (1 hour). Use `300` for ops/health queries; `21600` (6h) for slow cloud-IAM queries.            |
| **Parameters (JSON, optional)**        | Declare typed inputs the query accepts. See [Parameters](#parameters) below.                                                                                  |

Click **Save**. The query now appears as a tile on **Admin > Dashboard**.

***

## Parameters

Parameters let one saved query answer a family of questions instead of a single hardcoded one — *"AWS users created in the last N days"* instead of *"...last 30 days"*.

### Declare parameters in the modal

Paste a JSON array into the **Parameters** field:

```json theme={null}
[
  {"name": "days", "type": "int", "required": false, "default": 30, "description": "Lookback window in days"},
  {"name": "email", "type": "string", "required": true, "description": "User email to look up"}
]
```

| Field         | Required | Notes                                                                  |
| ------------- | -------- | ---------------------------------------------------------------------- |
| `name`        | Yes      | The placeholder name. Reference it as `:name` in your SQL.             |
| `type`        | Yes      | `string`, `int`, or `bool`.                                            |
| `required`    | Yes      | If `true`, the caller must supply a value.                             |
| `default`     | No       | Used when `required` is `false` and the caller doesn't supply a value. |
| `description` | No       | Shown to LLMs and in the dashboard run form.                           |

### Reference parameters in the SQL

Use `:name` placeholders. **Never** use string formatting (`f"... {var} ..."` or `%s`) — placeholders are bound safely by the engine; string interpolation opens up SQL injection.

```sql theme={null}
SELECT email, created_at
FROM aws_iam_users
WHERE created_at >= DATE_SUB(NOW(), INTERVAL :days DAY)
ORDER BY created_at DESC;
```

When the query is run (from the dashboard, Slack, or an LLM), the caller supplies `{"days": 7}` and the engine binds it.

***

## Run a saved query

### From the dashboard

Go to **Admin > Dashboard**. Each saved query is a tile. Click it to see the latest cached result, or click **Run Now** (the play icon) to bypass the cache and re-execute against live data. If the query takes parameters, you'll see a form for them.

### From Slack

If you toggled **Enable for Slack bot** when saving, the bot will pick up the query. Ask in plain language: *"Show me suspended Okta users in groups"*. The bot matches against your **Slack summary**, runs the query, and posts results.

### From an LLM (MCP)

See the next section.

***

## Make a query available to the LLM

This is the part that needs the honest disclaimer.

**Today, the MCP connector exposes only the curated query catalog that Configview ships with every instance** — not user-saved queries. The MCP server filters its `list_queries` response to queries that have an internal `seed_key`, which only curated queries do.

What this means in practice:

* **Queries you save in the dashboard** → available immediately on the **dashboard** and **Slack bot** (if you enabled Slack).
* **For an LLM to call your query via MCP** → ask Configview to add it to the curated catalog. Email `support@configview.com` with the SQL, title, description, parameters, and which category it fits. We'll add it in the next release, after which it's callable from Claude/ChatGPT/Cursor via the standard MCP flow ([documented here](/llm)).

We're working on opening MCP to user-saved queries directly — this page will be updated when that ships.

***

## Edit, deprecate, or delete a query

From **Admin > Dashboard**:

* **Edit** — click the pencil icon on a tile. The Query editor reopens populated with the saved SQL.
* **Deprecate** — hides the query from the default dashboard view, Slack, and (for curated) MCP. Tiles and existing bookmarks still resolve. Toggle **Include deprecated** in the dashboard filters to see them again.
* **Delete** — only for queries you no longer need at all. Bookmarks and old Slack messages referencing this query will break.

***

## Best practices

* **Title precisely.** The title is what humans and LLMs use to recognize the query. *"Workspace 200 missing CrowdStrike"* beats *"Device check"*.
* **Write a description.** One sentence. It's the difference between an LLM picking your query for the right question and ignoring it.
* **Filter snapshot tables.** If your data source appends per-run snapshots, always include `run_at = (SELECT MAX(run_at) FROM <table>)`. See the callout in [Step 2](#step-2-build-the-query).
* **Cache deliberately.** Default 1h is fine for most things. Drop to 5 min for ops/health; raise to 6h for slow cloud-IAM scans.
* **Use parameters instead of duplicating queries.** One parameterized "AWS users created in last N days" is better than five hardcoded variants.
* **Test before saving.** Run the query first. Verify the row count, scan the columns, then save.

***

## Troubleshooting

**"Permission denied"** — You need admin role on the dashboard. Ask your Configview admin.

**Query returns 5× the rows you expected** — Snapshot-table issue. Add `WHERE <table>.run_at = (SELECT MAX(run_at) FROM <table>)`.

**"Parameters JSON is invalid"** — The modal validates the JSON before letting you save. Common mistakes: missing comma between objects, single quotes (must be double), trailing comma.

**Saved query doesn't appear on the dashboard tile list** — Refresh the page. If still missing, check **Include deprecated** in the dashboard filters; the save may have hit an existing deprecated entry.

**Slack bot doesn't pick up the query after toggling on** — Bot fetches the query list on a short cache (about a minute). Wait, then try again. If still missing, verify **Slack summary** is filled in — the bot can't match a query with no summary.

**LLM via MCP says the query isn't available** — Expected. User-saved queries are not yet exposed via MCP. See [Make a query available to the LLM](#make-a-query-available-to-the-llm).

**Need to recover an old query** — Saved queries are not auto-deleted on edit; the old version is overwritten. If you need version history, copy the SQL out before each edit.

**Still stuck?** — Every save and run is recorded in the **Activity Log** (sidebar: Admin > Activity Log). Click the eye icon next to any entry to see the full message, including any SQL error.
