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

# Querying your data

> Give an agent read-only SQL access and schema introspection.

`SQLTools` connects an agent to a database. Point it at a read-only connection and the agent can introspect the schema and run queries.

```bash theme={null}
uv pip install "agno[openai,psycopg,sql]"
```

```python theme={null}
from agno.agent import Agent
from agno.db.postgres import PostgresDb
from agno.models.openai import OpenAIResponses
from agno.tools.sql import SQLTools
from sqlalchemy import create_engine

warehouse_url = "postgresql+psycopg://readonly@warehouse/analytics"
readonly_engine = create_engine(
    warehouse_url,
    connect_args={"options": "-c default_transaction_read_only=on"},
)

agent = Agent(
    model=OpenAIResponses(id="gpt-5.5"),
    db=PostgresDb(db_url="postgresql+psycopg://ai:ai@localhost:5532/ai"),
    tools=[SQLTools(db_engine=readonly_engine)],
    instructions=(
        "Introspect the schema before writing SQL. Answer with the numbers "
        "and the exact query you ran. Never guess a column name. Pass "
        "limit=None to run_sql_query when the answer needs every row."
    ),
)

agent.print_response("How many active subscriptions are on the Pro plan?")
```

The agent's `db` and the `SQLTools` connection are separate. `db` stores the agent's sessions. `SQLTools` points at the warehouse you are answering questions about. Keep them distinct.

## Introspect before generating

`SQLTools` provides `list_tables` and `describe_table` so the agent can read the current schema before generating SQL. The instruction above makes schema introspection part of each request.

| Tool             | Use                                  |
| ---------------- | ------------------------------------ |
| `list_tables`    | Discover what exists before querying |
| `describe_table` | Get exact column names and types     |
| `run_sql_query`  | Execute the generated SQL            |

`run_sql_query(query, limit=10)` returns at most 10 rows unless the agent passes a different `limit`. Pass `limit=None` for the full result set. Otherwise a `GROUP BY` over more than 10 groups comes back truncated with no sign that rows were dropped, so tell the agent when an answer needs every row.

## Scope the connection

`SQLTools` executes any SQL the engine permits. It does not classify statements as read-only. This engine starts transactions with `default_transaction_read_only=on`, which blocks ordinary write statements. Because that setting is a configurable session default, use a non-owner database role with no write grants as the hard boundary. See [Safe data access](/use-cases/data-agents/safe-data-access) for the full read and write split.

## Next steps

| Task                         | Guide                                                                   |
| ---------------------------- | ----------------------------------------------------------------------- |
| Ground SQL in business rules | [Grounding in context](/use-cases/data-agents/grounding-in-context)     |
| Stop repeating query errors  | [Self-correcting agents](/use-cases/data-agents/self-correcting-agents) |
| Allow controlled writes      | [Safe data access](/use-cases/data-agents/safe-data-access)             |

## Developer Resources

* [SQL tools cookbook](https://github.com/agno-agi/agno/blob/main/cookbook/91_tools/sql_tools.py)
* [Dash: the multi-agent data team](/deploy/templates/dash/overview)
