> Build a safer LangChain SQL agent for PostgreSQL or MySQL with schema checks, query validation, error recovery, and production safeguards.

## Introduction: LangChain SQL in plain language

A **LangChain SQL** agent turns an ordinary-language database question into SQL, runs it, and explains the result. Complications arise when a question needs several tables, the model guesses a column, or a query uses PostgreSQL syntax against MySQL. **SQLDatabaseToolkit** gives a ReAct SQL agent database tools for inspecting facts before acting.

This tutorial builds a read-only LangChain SQL agent for PostgreSQL or MySQL, turning text into SQL with schema checks, query validation, and production safeguards. You will learn how to:

- connect without putting passwords in source code;
- configure the four toolkit tools and the ReAct loop;
- recover from schema and query errors; and
- add limits, permissions, review, and tests before production.

The aim is useful answers, not magical-looking demos.

![LangChain SQL agent tutorial overview](/assets/langchain-sql-agent-guide.webp)

*LangChain’s official SQL agent tutorial shows the agent loop: inspect tables, retrieve schemas, generate and check SQL, execute it, and correct database errors.*

## What SQLDatabaseToolkit does for a LangChain SQL agent—and why it matters

**SQLDatabaseToolkit** creates tools around a SQLAlchemy connection for a compatible chat model. The database stores rows, applies joins and filters, and calculates aggregates; the model selects tools and proposes read-only SQL.

A LangChain SQL agent neither “learns” nor copies your live database. It requests table names, selected schemas, and query results as needed. The [SQLDatabase reference](https://reference.langchain.com/python/langchain-community/utilities/sql_database/SQLDatabase) also supports table allowlists, lazy reflection, and a configurable number of sample rows in schema context.

| Part | Responsibility | Example |
|---|---|---|
| User | States the business question | “Which campaigns produced the most paid orders last month?” |
| Model | Plans steps and drafts SQL | Chooses campaigns and orders, then creates a join |
| Toolkit | Exposes safe, named operations | Lists tables, reads schema, checks SQL, runs SQL |
| Database | Validates and executes the statement | PostgreSQL returns grouped order totals |
| Application | Controls access and presentation | Applies authentication, timeouts, logs, and result formatting |

Unlike a single text to SQL prompt, the feedback loop turns a nonexistent-column error into an observation, letting the agent inspect the schema and correct the statement instead of returning invented data.

## How a ReAct SQL agent uses four SQLDatabaseToolkit tools

ReAct alternates reasoning and actions. The original [ReAct paper](https://arxiv.org/abs/2210.03629) reported absolute success-rate gains of **34 percentage points** on ALFWorld and **10 points** on WebShop over imitation or reinforcement-learning baselines. Though not SQL tests, they show the ReAct pattern useful for LangChain SQL: observe, update the plan, and act.

SQLDatabaseToolkit currently returns four tools. Tutorials may shorten their names to list_tables, get_schema, query_sql, and check_sql; Python uses the names below.

| Toolkit tool | Simple meaning | When the agent should use it |
|---|---|---|
| sql_db_list_tables | list_tables | Find allowed tables instead of guessing |
| sql_db_schema | get_schema | Inspect columns, types, keys, and sample rows before writing SQL |
| sql_db_query_checker | check_sql | Review joins, NULL behavior, types, and dialect syntax before execution |
| sql_db_query | query_sql | Execute the checked statement and return rows or an error |

A healthy ReAct SQL agent follows this sequence:

1. List the exposed tables.
2. Select only relevant tables.
3. Read their schemas and row samples.
4. Draft a limited, read-only query.
5. Run the query through the checker.
6. Execute it, inspect the result, and revise only for a useful error.
7. Answer in plain language and state any assumptions.

LangChain’s [SQL-agent guide](https://docs.langchain.com/oss/python/langchain/sql-agent) describes the same eight-stage path from table discovery to a final answer and warns that model-generated SQL carries inherent risk.

## SQL agent LangChain setup: working SQLDatabaseToolkit code

Use Python 3.11+ and install the framework, community package, model adapter, SQLAlchemy, and your database driver.

~~~bash
python -m pip install -U langchain langgraph langchain-community langchain-openai sqlalchemy "psycopg[binary]" pymysql
export OPENAI_API_KEY="your-api-key"
export OPENAI_MODEL="a-tool-calling-model-name"
~~~

The shared function uses an allowlist, lazy table reflection, and two sample rows in schema output. It tests pooled connections and creates a ReAct SQL agent through the current create_agent interface. LangChain’s [toolkit reference](https://reference.langchain.com/python/langchain-community/agent_toolkits/sql/toolkit/SQLDatabaseToolkit) confirms that the model passed to SQLDatabaseToolkit powers its query-checker tool.

~~~python
import os

from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain_community.agent_toolkits import SQLDatabaseToolkit
from langchain_community.utilities import SQLDatabase

def make_sql_agent(database_url, allowed_tables, schema=None):
db = SQLDatabase.from_uri(
database_url,
schema=schema,
include_tables=allowed_tables,
sample_rows_in_table_info=2,
lazy_table_reflection=True,
engine_args={
"pool_pre_ping": True,
"pool_recycle": 1800,
},
)

model = ChatOpenAI(
model=os.environ["OPENAI_MODEL"],
temperature=0,
)
toolkit = SQLDatabaseToolkit(db=db, llm=model)
tools = toolkit.get_tools()

system_prompt = """
You answer questions using a {dialect} database.
Work only with the tools and tables you are given.
Always list tables first, then inspect relevant schemas.
Generate SELECT statements only. Never use INSERT, UPDATE, DELETE,
DROP, ALTER, TRUNCATE, GRANT, or other data-changing statements.
Never use SELECT *. Select only needed columns.
Limit detail queries to 20 rows unless the user requests fewer.
Always call sql_db_query_checker before sql_db_query.
If a query fails, use the error and schema to correct it. Stop after two
failed executions and explain what information is missing.
If a business term or date range is ambiguous, ask for clarification.
Return the answer, important assumptions, and the time period used.
""".format(dialect=db.dialect)

agent = create_agent(
model=model,
tools=tools,
system_prompt=system_prompt,
)
return agent, db, tools
~~~

During development, print [tool.name for tool in tools] once to catch packaging or version mistakes.

## Connect LangChain SQL to PostgreSQL and MySQL

Do not concatenate a username and password into a connection string: @ , / , or : in a password can break the URL. SQLAlchemy’s URL.create handles these values correctly.

~~~python
import os
from sqlalchemy import URL

# PostgreSQL with psycopg 3
postgres_url = URL.create(
"postgresql+psycopg",
username=os.environ["DB_USER"],
password=os.environ["DB_PASSWORD"],
host=os.environ.get("DB_HOST", "localhost"),
port=int(os.environ.get("DB_PORT", "5432")),
database=os.environ["DB_NAME"],
)
postgres_agent, postgres_db, postgres_tools = make_sql_agent(
postgres_url,
allowed_tables=["campaigns", "orders", "customers"],
schema="analytics",
)
~~~

The explicit postgresql+psycopg dialect selects psycopg 3, as shown in the [SQLAlchemy PostgreSQL documentation](https://docs.sqlalchemy.org/en/21/dialects/postgresql.html).

~~~python
# MySQL with PyMySQL
mysql_url = URL.create(
"mysql+pymysql",
username=os.environ["DB_USER"],
password=os.environ["DB_PASSWORD"],
host=os.environ.get("DB_HOST", "localhost"),
port=int(os.environ.get("DB_PORT", "3306")),
database=os.environ["DB_NAME"],
query={"charset": "utf8mb4"},
)
mysql_agent, mysql_db, mysql_tools = make_sql_agent(
mysql_url,
allowed_tables=["campaigns", "orders", "customers"],
)
~~~

The [SQLAlchemy MySQL documentation](https://docs.sqlalchemy.org/en/21/dialects/mysql.html) specifies the mysql+pymysql form. Both agents use the same LangChain SQL code because SQLDatabase exposes the active dialect to the prompt.

| Difference | PostgreSQL | MySQL |
|---|---|---|
| Driver in this tutorial | psycopg 3 | PyMySQL |
| Default port | 5432 | 3306 |
| URL prefix | postgresql+psycopg | mysql+pymysql |
| Common date expression | date_trunc | DATE_FORMAT |
| Schema handling | Database and schema can be separate | Database commonly acts as the schema |

Do not mix dialect-specific date functions. Expose db.dialect to the checker and test representative questions on each engine.

## Run the ReAct SQL agent and handle errors

Start with a question you can verify manually: “For completed orders in June 2026, show revenue and customer count by campaign, highest revenue first.” This tests status, dates, a join, aggregation, and ordering.

~~~python
from langgraph.errors import GraphRecursionError

def ask(agent, question):
try:
result = agent.invoke(
{"messages": [{"role": "user", "content": question}]},
config={"recursion_limit": 12},
)
return result["messages"][-1].content
except GraphRecursionError:
return "The database assistant reached its step limit. Please narrow the question."
except Exception:
# Log the full exception and a trace ID on the server, not in the UI.
return "The database request failed. Please try again with a narrower date range."

answer = ask(
postgres_agent,
"For completed orders in June 2026, show revenue and customer "
"count by campaign, highest revenue first.",
)
print(answer)
~~~

Error recovery should be specific and finite.

| Failure observed | Agent response | Application response |
|---|---|---|
| Unknown table or column | List tables or inspect schema, then rewrite once | Record the failed SQL in a private trace |
| Syntax or dialect error | Run check_sql and correct the function or quoting | Keep a regression test for that question |
| Empty result | Verify filters and date boundaries | Say “no matching rows,” not “zero activity” |
| Timeout or lock | Stop; do not keep generating variants | Return a neutral error and alert on repeated failures |
| Repeated tool loop | End at the recursion limit | Ask the user to narrow or clarify the request |

The checker can improve SQL but cannot prove that “revenue” maps to the correct business column. Semantic errors require definitions and tests.

## Production accuracy and security for LangChain SQL with SQLDatabaseToolkit

I would not compromise here: “read only” is guidance, not a security boundary. Limit the agent’s database account to SELECT on approved views, excluding raw payment data, secrets, internal notes, and tenant rows the user should not see.

Enterprise text to SQL remains difficult, even with ReAct. The [Spider 2.0 paper](https://arxiv.org/abs/2411.07763) includes **632** enterprise workflow problems, often exceeding **1,000 columns**. Its agent baseline solved **17.0%** of tasks, versus 91.2% on Spider 1.0 and 73.0% on BIRD. That gap warns that success on a tidy demo database does not establish production accuracy.

Use this launch checklist:

| Item | What to check | Why it matters |
|---|---|---|
| Database identity | Read-only role; approved schemas and views only | Blocks writes even when the model or prompt fails |
| Tenant isolation | Row-level security or separate credentials | A table allowlist alone does not stop cross-customer reads |
| Query cost | 5.15 second timeout, row cap, concurrency limit | Prevents expensive scans and runaway load |
| Schema context | Clear column comments, stable views, two or three sample rows | Reduces guessed joins and misunderstood codes |
| Output privacy | Redaction and minimum-group-size rules | Stops sensitive values from appearing in answers |
| Review | Approval for sensitive or unusually expensive queries | Adds a person where impact is high |
| Evaluation | 50.200 representative questions with expected results | Measures correctness before and after changes |
| Observability | Tool calls, latency, row counts, errors, model and prompt version | Makes failures reproducible without exposing traces to users |

LangChain’s [human-in-the-loop review](https://docs.langchain.com/oss/python/langchain/sql-agent#6-implement-human-in-the-loop-review) can pause sql_db_query calls in higher-risk deployments. Pin tested package versions so dependency updates cannot silently change database behavior.

## Four practical LangChain SQL applications

The best first project has bounded questions, clean definitions, and an owner who can verify results. Start with one subject area and ten questions people already answer by hand, not “ask anything about every database.”

| Use case | Example question | Tables or views | Acceptance check |
|---|---|---|---|
| Marketing performance | “Which channels increased paid revenue month over month?” | campaign_spend, attributed_orders | Totals reconcile to the approved dashboard within 1% |
| IT operations | “Which services exceeded the incident target this quarter?” | incidents, services, sla_targets | Sample five incidents and verify severity and time-zone rules |
| Accounts receivable | “Which invoices are over 30 days late, by customer?” | open_invoices, customers | Result matches the finance aging report exactly |
| Customer success | “Which accounts had usage fall by at least 25%?” | account_usage_monthly, accounts | Confirm the baseline month and exclude incomplete current periods |

These examples are not claims about named companies; each tests attribution joins, time calculations, regulated financial definitions, or percentage comparisons.

For a first pilot:

1. Write 25 common questions and approved answers.
2. Expose only the views required for those questions.
3. Run each question five times and compare result sets, not just wording.
4. Classify failures as schema, SQL, business-definition, permission, or presentation errors.
5. When possible, fix the database view or definition before adding prompt text.
6. Release to a small group; review low-confidence or high-cost requests weekly.

This turns a LangChain SQL demo into a trusted service.

## ReAct SQL Agent Alternatives

Use a ReAct SQL agent for questions requiring discovery and correction, not every database interface.

| Approach | Best fit | Trade-off |
|---|---|---|
| Approved SQL templates | Repeated reports with fixed filters | Highest control, least flexibility |
| One-pass SQL chain | Small schema and predictable questions | Lower cost, weaker recovery from mistakes |
| SQLDatabaseToolkit ReAct agent | Varied, multi-table read questions | More model calls, latency, and possible loops |
| Custom LangGraph workflow | Regulated or complex routing | More engineering, stronger enforced order and review |
| BI semantic layer | Shared metrics used across the company | Definitions are consistent, but setup takes work |

Common concerns:

## Conclusion: build a safer SQL agent LangChain workflow

Start a useful **LangChain SQL** application small. SQLDatabaseToolkit supplies list_tables, get_schema, check_sql, and query_sql; their observations help the ReAct SQL agent plan, correct mistakes, and explain results. Because PostgreSQL and MySQL mainly differ in driver URL and SQL dialect, one agent structure supports both.

Remember:

- permissions protect the database; prompts do not;
- schema context and business definitions matter as much as the model; and
- accuracy must be measured with known questions and result sets.

Before wider release, create a read-only role, expose three well-defined views, and test 25 real questions. Once answers stabilize, add observability, human review, and subject areas one at a time.

## Frequently asked questions

### Can SQLDatabaseToolkit write data?

Because the query tool can pass SQL to the database, treat model-generated statements as unsafe and require a read-only role.

### Does check_sql guarantee a correct answer?

No. It catches common SQL mistakes but cannot know undocumented business definitions or prove the rows answer the question.

### Should the agent see every table?

No. Use <code>include_tables</code>, restricted views, and database grants; smaller context is easier to reason about and audit.

### Why not send the full schema in every prompt?

Large schemas add cost and distraction. Targeted get_schema calls after list_tables provide relevant context when needed.

### When should we build a custom graph?

When policy must enforce list, schema, check, approval, and execution as fixed steps instead of prompt suggestions. See LangChain’s [custom SQL-agent guide](https://docs.langchain.com/oss/python/langgraph/sql-agent).

Many teams use templates for recurring executive metrics and a ReAct SQL agent for exploratory questions, sharing the same governed views.

### Is a prompt that says “SELECT only” enough to make the agent safe?

No. Use a database account that can only read approved views or tables, because prompt instructions can be ignored or misapplied. Add tenant isolation, query timeouts, row limits, and output redaction where appropriate.

### How should I choose which tables the agent can access?

Start with a small allowlist containing only the views needed for a defined set of business questions. Prefer stable, documented views over raw operational tables, and enforce the same restrictions through database grants.

### Can the same LangChain SQL agent work with PostgreSQL and MySQL?

Yes, the overall agent and toolkit structure can be shared. Configure the correct SQLAlchemy driver URL and expose the active database dialect so the model does not mix engine-specific functions or quoting rules.

### What should happen when the generated SQL fails?

The agent should use the error and inspected schema to make a limited correction, then stop after a small number of failed attempts. Timeouts, locks, and repeated loops should produce a neutral user-facing message while detailed diagnostics remain in private logs.

### Does the query checker guarantee an accurate business answer?

No. It can identify many syntax, type, join, and dialect problems, but it cannot determine what an undocumented term such as “revenue” means. Accurate answers also require governed metric definitions, clear schema documentation, and tests against expected result sets.

### When is SQLDatabaseToolkit preferable to templates or a custom workflow?

It is useful for varied, exploratory questions that require table discovery, multi-table joins, and error correction. Use approved SQL templates for predictable reports, and consider a custom LangGraph workflow when review or execution order must be enforced by application logic.

### How should I validate a LangChain SQL agent before production?

Test it repeatedly with representative questions and compare returned rows and totals with approved answers, not merely the wording of its responses. Classify failures by schema, SQL, business definition, permissions, or presentation, then release gradually with logging and human review for higher-risk queries.

---

[View the canonical page](https://dbsilk.com/blog/langchain-sqldatabasetoolkit-tutorial/) · [Browse llms.txt](https://dbsilk.com/llms.txt)
