ragmcpai-agentssqlembeddings

RAG vs SQL: Por que tu agente de IA deberia consultar, no crear embeddings

Michael San Martim · 2026-04-22

Cada semana, otro equipo anuncia que esta “construyendo RAG para su data warehouse.” Fragmentan sus tablas en vectores, los almacenan en Pinecone y le piden a un LLM que responda preguntas de negocio. Funciona para documentos. Falla catastroficamente para datos estructurados.

El problema: RAG sobre tablas

Esto es lo que pasa cuando usas RAG para datos estructurados:

# The RAG approach to answering "What was revenue in Q3?"
# Step 1: Chunk your tables into text
chunks = []
for row in orders_table:
chunks.append(f"Order {row.id}: customer {row.customer}, "
f"amount ${row.amount}, date {row.date}")
# Step 2: Embed chunks
embeddings = openai.embed(chunks) # $0.0001 per chunk, 1M rows = $100
# Step 3: Store in vector DB
pinecone_index.upsert(vectors=embeddings) # $70/month for 1M vectors
# Step 4: At query time, retrieve "relevant" chunks
query_embedding = openai.embed("What was revenue in Q3?")
results = pinecone_index.query(query_embedding, top_k=20)
# Step 5: Ask LLM to synthesize
answer = openai.chat(
f"Based on these records: {results}\n"
f"Answer: What was revenue in Q3?"
)
# Returns: "Based on the retrieved records, Q3 revenue appears to be
# approximately $2.3M" (actual answer: $4.7M)

Por que falla:

  1. La recuperacion pierde filas. La similitud vectorial encuentra fragmentos “relevantes”, no TODAS las filas que coinciden. Si tienes 50,000 ordenes del Q3, recuperaste 20.
  2. La agregacion es imposible. No puedes SUM, AVG o COUNT desde 20 muestras aleatorias.
  3. Los datos se desactualizan. Necesitas re-crear embeddings cada vez que los datos cambian.
  4. Es caro. Embeddings de millones de filas + hosting de vector DB + tokens LLM para contexto.
  5. Alucina con confianza. El LLM ve 20 filas y extrapola.

El enfoque correcto: SQL

La misma pregunta respondida con SQL:

SELECT SUM(amount) AS revenue
FROM orders
WHERE created_at >= '2024-07-01'
AND created_at < '2024-10-01'

Resultado: $4,712,843.00 — exacto, instantaneo, gratis.

MCP + SQL con DataSpoc Lens

DataSpoc Lens expone tu data lake cómo un servidor MCP. Los agentes de IA ejecutan consultas SQL reales en lugar de adivinar a partir de embeddings.

Configuracion

Terminal window
pip install dataspoc-lens[mcp]
dataspoc-lens add-bucket s3://company-lake
dataspoc-lens mcp

Como funcióna el agente

from dataspoc_lens import LensClient
client = LensClient()
# Agent discovers what data exists
tables = client.list_tables()
# ['raw.postgres.orders', 'raw.postgres.customers', 'raw.postgres.products']
# Agent inspects schema
schema = client.get_schema("raw.postgres.orders")
# [{"name": "id", "type": "INTEGER"},
# {"name": "customer_id", "type": "INTEGER"},
# {"name": "amount", "type": "DECIMAL"},
# {"name": "created_at", "type": "TIMESTAMP"}, ...]
# Agent writes and executes SQL
result = client.query("""
SELECT SUM(amount) AS revenue
FROM raw.postgres.orders
WHERE created_at >= '2024-07-01'
AND created_at < '2024-10-01'
""")
# [{"revenue": 4712843.00}]

Sin embeddings. Sin vector DB. Sin alucinaciones. Datos reales, respuestas reales.

Comparación de código lado a lado

Pipeline RAG (lo que los equipos estan construyendo)

# ingest.py — runs daily, takes 45 minutes for 1M rows
import pandas as pd
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_pinecone import PineconeVectorStore
df = pd.read_parquet("s3://company-lake/raw/postgres/orders/")
# Convert rows to text chunks
documents = []
for _, row in df.iterrows():
text = f"Order {row['id']}: {row['customer_name']} purchased "
text += f"${row['amount']:.2f} on {row['created_at']}"
documents.append(text)
# Split and embed
splitter = RecursiveCharacterTextSplitter(chunk_size=500)
chunks = splitter.create_documents(documents)
embeddings = OpenAIEmbeddings()
vectorstore = PineconeVectorStore.from_documents(chunks, embeddings, index_name="orders")
# query.py — at query time
retriever = vectorstore.as_retriever(search_kwargs={"k": 50})
docs = retriever.get_relevant_documents("What was Q3 revenue?")
# Gets 50 random-ish order records out of 250,000 Q3 orders

Pipeline SQL (lo que deberias construir)

# query.py — that's it, no ingest step needed
from dataspoc_lens import LensClient
client = LensClient()
result = client.query("SELECT SUM(amount) FROM orders WHERE quarter = 'Q3-2024'")
# Scans all 250,000 rows in 0.3 seconds, returns exact answer

La comparación

DimensionRAG sobre tablasMCP + SQL
Precision~60% (pierde filas, no puede agregar)99%+ (computo exacto)
Velocidad3-8 segundos (embedding query + recuperar + LLM)0.1-2 segundos (ejecucion SQL)
FrescuraDesactualizado hasta re-crear embeddings (horas/dias)Tiempo real (consulta datos en vivo)
Costo por consulta$0.02-0.10 (embedding + tokens LLM)$0.001 (solo computo DuckDB)
Infra mensual$200+ (vector DB + embedding API)$0 (DuckDB es gratis)
Auditabilidad”Basado en el contexto recuperado…”Consulta SQL exacta, reproducible
AgregacionesImposible (SUM de 20 filas != SUM de 250K)Nativo (eso es lo que SQL hace)
JoinsNo puede unir a traves de embeddingsJoins multi-tabla nativos
ActualizacionesRe-crear embeddings de todo el datasetAutomatico (lee el Parquet mas reciente)

Cuando RAG ES la opcion correcta

RAG es excelente para documentos no estructurados:

  • Reportes PDF, contratos legales, tickets de soporte
  • Busqueda de documentación
  • Bases de conocimiento con contenido en prosa
  • Cualquier dato donde la busqueda por palabra clave/semantica es el patrón de acceso

La regla es simple:

Tablas y números = SQL. Documentos y prosa = RAG.

Una arquitectura hibrida

Para la mayoria de las empresas, la mejor arquitectura combina ambos:

┌─────────────────────────────────────────────────┐
│ AI Agent │
├─────────────────────────────────────────────────┤
│ │
│ Structured data? ──► DataSpoc Lens (MCP + SQL) │
│ │
│ Unstructured docs? ──► RAG (vector search) │
│ │
└─────────────────────────────────────────────────┘
from dataspoc_lens import LensClient
from langchain_pinecone import PineconeVectorStore
lens = LensClient()
vectorstore = PineconeVectorStore(index_name="docs")
def route_question(question: str) -> str:
"""Route to SQL for data questions, RAG for document questions."""
data_keywords = ["revenue", "count", "average", "total", "growth",
"last month", "quarterly", "how many", "top 10"]
if any(kw in question.lower() for kw in data_keywords):
# SQL path — exact answers from structured data
result = lens.ask(question)
return result["answer"]
else:
# RAG path — semantic search over documents
docs = vectorstore.similarity_search(question, k=5)
return synthesize_from_docs(docs, question)

El costo de equivocarse

Un ejemplo real de una startup Serie B:

MetricaSu configuración RAGDespues de cambiar a SQL
Costo mensual$2,400 (Pinecone + embeddings OpenAI)$0 (DuckDB sobre Parquet)
Precision de consultas58% (validado contra números reales)99.7%
Frescura de datos6 horas de retraso (re-embedding diario)Tiempo real
Mantenimiento2 ingenieros tiempo parcialCero (autoservicio)
Tiempo de respuesta4.2 segundos promedio0.8 segundos promedio

Estaban gastando $2,400/mes para obtener respuestas incorrectas lentamente.

Comenzando

Terminal window
# If your data is already in Parquet/S3:
pip install dataspoc-lens[mcp]
dataspoc-lens add-bucket s3://your-bucket
dataspoc-lens mcp # Start MCP server for AI agents
# If your data is in Postgres/MySQL:
pip install dataspoc-pipe
dataspoc-pipe init && dataspoc-pipe add postgres --tables orders
dataspoc-pipe run # Extracts to Parquet
# Then query with SQL
dataspoc-lens query "SELECT SUM(amount) FROM raw.postgres.orders WHERE created_at > '2024-01-01'"

Deja de crear embeddings de tus tablas. Comienza a consultarlas.

Recomendados