Skip to content

Reference / Authoring

explore_data

Run one read-only SELECT against a connection and get a small, complete answer. The bound, why it refuses instead of shortening, parameters, errors and scope.

Runs one read-only SELECT against a connection and returns the whole small answer: the columns, the row count, and the rows themselves.

This is the call for the questions that come before anything is designed. What values does this column hold. How many customers are there. What does a row of this table look like. How far back does the data go. Is this column ever null.

Behaviour hints for clients: read-only, not open-world.

When your AI should use it, and when it should not

The order matters, and only your AI can apply it, because only your AI can see its own tool list.

  1. If a semantic layer defines the metric, that definition wins. Dashies keeps a dashboard fresh, versioned and shareable; it does not decide what a metric means, and neither does anything you learn from this call.
  2. If the session can reach your warehouse directly (your own SQL client, or a server your warehouse vendor publishes), it should explore there. It is your system, it is not bounded by the size of an MCP result, and you can see what was run.
  3. If it has neither, this is the supported call. It is a real answer rather than a workaround, which is the whole reason it exists as its own tool.

It is not the check that a statement you are about to publish will keep working. That is validate_cube_sql, and no tool outside Dashies can stand in for it.

Parameters

ParameterTypeRequiredNotes
sqlstringyesA single read-only SELECT or WITH ... SELECT. Bound it yourself: a LIMIT, a narrow window, or a GROUP BY that returns counts rather than rows.
connectionstringnoself (the default), a warehouse connection id, or the uploaded-file source id (which also needs upload).
uploadstringnoWhich uploaded file to read, as an upload id from create_file_upload. Required when connection is the workspace's uploaded-file source, and refused on any other connection - a warehouse has no uploads. There is no default and the newest upload is not assumed: a later upload would otherwise silently change what this call answers and what a published dashboard reads, so omitting it is refused with the newest ready upload named for you to pass back.

The bound, and why it refuses instead of shortening

BoundValue
Rows in one answer200
Bytes of rows in one answer12288

The two bind independently, whichever comes first, so a handful of rows carrying long text or JSON reaches the byte budget while a narrow answer reaches the row bound.

Past either bound you get nothing, not the first part

A shortened answer to "what values are in this column" reads exactly like a complete one, and a dashboard built on the short version filters on a value set that was never the whole set. So an oversized answer is refused, and the refusal says what to change.

The consequence is worth stating positively: every answer you do get is complete. There is no sample, no truncation flag, and nothing to page through.

That claim is load-bearing, so it is worth saying how it is kept. The whole result is read rather than the first page of it, and if the warehouse returns rows but will not report how many there are in total, the answer is refused rather than reported as though it were all of them. An answer that might be a fragment reads exactly like a complete one, which is the reason the refusal is better than a number.

There is no offset argument and no way to raise the bound, so this is not a way to read a table out a page at a time. If what you want is a table's contents, this is not the call.

Returns

Explored connection "self". 3 rows.
Columns:
- region: string
- orders: number

That is the whole answer, not a sample.

BEGIN_JSON
{"connection":"self","row_count":3,"columns":[{"name":"region","type":"string"},{"name":"orders","type":"number"}],"rows":[{"region":"EMEA","orders":1240},{"region":"AMER","orders":903},{"region":"APAC","orders":517}]}
END_JSON

The header lines and the JSON block are built from the same object, so they always agree: the columns listed above are the columns in the block.

row_count is the warehouse's own total for the statement, not a count of the rows in the block. On every answer you actually receive the two are equal, because an answer whose row count disagrees with its own total is refused in either direction: too few rows for the total, or more rows than the total claims. They are stated as two facts rather than one because they come from two places.

An answer with no rows is a result, not a failure, and the text says so: Empty is a result, not a failure - the filter, the window or the table is empty. It usually means the filter matches nothing or the window is outside the data, and retrying it unchanged will produce the same answer.

Errors

The shared connection rules, plus:

ConditionText
Missing SQLsql is required
Over the row boundThat returned more than 200 rows, which is more than one exploration answer holds, so nothing is being returned rather than the first part of it: ...
Over the byte boundThat answer is too wide to return, so nothing is being returned rather than part of it. Name the columns you actually want instead of selecting them all, or take fewer rows.
An answer that could not be read to the endThe answer came back in parts and could not be read to the end, so it is not being returned - a partial answer reads exactly like a small one. Retry; ...
The warehouse would not say how many rows there areThe warehouse returned rows but did not report how many there are in total, so whether these are all of them cannot be established ...
More than one statementThat is more than one statement (a ';' separates statements). ...
Not a read-only SELECTThat is not a single read-only SELECT (or WITH ... SELECT). ...
A statement that writesThat statement tries to write, and this call runs in a read-only transaction. ...
A system-catalog readThat statement reads the database's own system catalog, which this connection does not expose. ...
Too slowThat took longer than one exploration answer is allowed to take, so it was stopped. ...

For BigQuery, Snowflake, Redshift and Databricks an error about your own SQL (an unknown column, a syntax error) is surfaced as the engine wrote it.

Rate limited at 120 calls per user per minute, shared with validate_cube_sql: the two run the same read against the same executor, so they are one budget rather than two.

Scope

Connection-scoped, so there is no workspace argument. It accepts any connection belonging to a workspace you are a current member of, plus the built-in self and the shared sample. A warehouse connection requires a paid plan; self does not. It reads only the tables that connection exposes, inside a read-only transaction, exactly as validate_cube_sql does.

Check it worked

The row count is the thing to read. If an exploratory count comes back at zero, that is a real finding about the filter or the window rather than a failed call, and it is worth resolving before it becomes a dashboard tile that reads zero forever.