RAG vs SQL: Por Que Seu Agente de IA Deveria Consultar, Não Gerar Embeddings
Michael San Martim · 2026-04-22
Toda semana, outra equipe anuncia que está “construindo RAG para seu data warehouse”. Eles dividem suas tabelas em vetores, armazenam no Pinecone e pedem a um LLM para responder perguntas de negócio. Funciona para documentos. Falha catastroficamente para dados estruturados.
O Problema: RAG em Tabelas
Veja o que acontece quando você usa RAG para dados estruturados:
# The RAG approach to answering "What was revenue in Q3?"
# Step 1: Chunk your tables into textchunks = []for row in orders_table: chunks.append(f"Order {row.id}: customer {row.customer}, " f"amount ${row.amount}, date {row.date}")
# Step 2: Embed chunksembeddings = openai.embed(chunks) # $0.0001 per chunk, 1M rows = $100
# Step 3: Store in vector DBpinecone_index.upsert(vectors=embeddings) # $70/month for 1M vectors
# Step 4: At query time, retrieve "relevant" chunksquery_embedding = openai.embed("What was revenue in Q3?")results = pinecone_index.query(query_embedding, top_k=20)
# Step 5: Ask LLM to synthesizeanswer = 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 falha:
- Recuperação perde linhas. Similaridade vetorial encontra chunks “relevantes”, não TODAS as linhas correspondentes. Se você tem 50.000 pedidos no Q3, recuperou 20.
- Agregação é impossível. Você não pode SUM, AVG ou COUNT a partir de 20 amostras aleatórias.
- Dados ficam desatualizados. Você precisa re-embed cada vez que dados mudam.
- É caro. Embeddings de milhões de linhas + hospedagem de vector DB + tokens LLM para contexto.
- Alucina com confiança. O LLM vê 20 linhas e extrapola.
A Abordagem Correta: SQL
A mesma pergunta respondida com SQL:
SELECT SUM(amount) AS revenueFROM ordersWHERE created_at >= '2024-07-01' AND created_at < '2024-10-01'Resultado: $4.712.843,00 — exato, instantâneo, grátis.
MCP + SQL com DataSpoc Lens
O DataSpoc Lens expõe seu data lake como um servidor MCP. Agentes de IA executam consultas SQL reais em vez de adivinhar a partir de embeddings.
Setup
pip install dataspoc-lens[mcp]dataspoc-lens add-bucket s3://company-lakedataspoc-lens mcpComo o Agente Funciona
from dataspoc_lens import LensClient
client = LensClient()
# Agent discovers what data existstables = client.list_tables()# ['raw.postgres.orders', 'raw.postgres.customers', 'raw.postgres.products']
# Agent inspects schemaschema = 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 SQLresult = 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}]Sem embeddings. Sem vector DB. Sem alucinação. Dados reais, respostas reais.
Comparação de Código Lado a Lado
Pipeline RAG (o que equipes estão construindo)
# ingest.py — runs daily, takes 45 minutes for 1M rowsimport pandas as pdfrom langchain.text_splitter import RecursiveCharacterTextSplitterfrom langchain_openai import OpenAIEmbeddingsfrom langchain_pinecone import PineconeVectorStore
df = pd.read_parquet("s3://company-lake/raw/postgres/orders/")
# Convert rows to text chunksdocuments = []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 embedsplitter = RecursiveCharacterTextSplitter(chunk_size=500)chunks = splitter.create_documents(documents)embeddings = OpenAIEmbeddings()vectorstore = PineconeVectorStore.from_documents(chunks, embeddings, index_name="orders")
# query.py — at query timeretriever = 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 ordersPipeline SQL (o que você deveria construir)
# query.py — that's it, no ingest step neededfrom 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 answerA Comparação
| Dimensão | RAG em Tabelas | MCP + SQL |
|---|---|---|
| Precisão | ~60% (perde linhas, não agrega) | 99%+ (computação exata) |
| Velocidade | 3-8 segundos (embed query + recuperar + LLM) | 0,1-2 segundos (execução SQL) |
| Atualidade | Desatualizado até re-embed (horas/dias) | Tempo real (consulta dados vivos) |
| Custo por consulta | $0,02-0,10 (embedding + tokens LLM) | $0,001 (apenas compute DuckDB) |
| Infra mensal | $200+ (vector DB + API de embedding) | $0 (DuckDB é grátis) |
| Auditabilidade | ”Baseado no contexto recuperado…” | Consulta SQL exata, reproduzível |
| Agregações | Impossível (SUM de 20 linhas != SUM de 250K) | Nativo (é pra isso que SQL serve) |
| Joins | Não dá para fazer join entre embeddings | Joins multi-tabela nativos |
| Atualizações | Re-embed dataset inteiro | Automático (lê o Parquet mais recente) |
Quando RAG É a Escolha Certa
RAG é excelente para documentos não estruturados:
- Relatórios em PDF, contratos legais, tickets de suporte
- Busca em documentação
- Bases de conhecimento com conteúdo em prosa
- Qualquer dado onde busca por palavra-chave/semântica é o padrão de acesso
A regra é simples:
Tabelas e números = SQL. Documentos e prosa = RAG.
Uma Arquitetura Híbrida
Para a maioria das empresas, a melhor arquitetura combina ambos:
┌─────────────────────────────────────────────────┐│ AI Agent │├─────────────────────────────────────────────────┤│ ││ Structured data? ──► DataSpoc Lens (MCP + SQL) ││ ││ Unstructured docs? ──► RAG (vector search) ││ │└─────────────────────────────────────────────────┘from dataspoc_lens import LensClientfrom 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)O Custo de Errar Isso
Um exemplo real de uma startup Series B:
| Métrica | Setup RAG deles | Após mudar para SQL |
|---|---|---|
| Custo mensal | $2.400 (Pinecone + embeddings OpenAI) | $0 (DuckDB em Parquet) |
| Precisão das consultas | 58% (validado contra números reais) | 99,7% |
| Atualidade dos dados | Atraso de 6 horas (re-embedding diário) | Tempo real |
| Manutenção | 2 engenheiros parcialmente | Zero (self-service) |
| Tempo para responder | 4,2 segundos em média | 0,8 segundos em média |
Eles estavam gastando $2.400/mês para obter respostas erradas lentamente.
Começando
# If your data is already in Parquet/S3:pip install dataspoc-lens[mcp]dataspoc-lens add-bucket s3://your-bucketdataspoc-lens mcp # Start MCP server for AI agents
# If your data is in Postgres/MySQL:pip install dataspoc-pipedataspoc-pipe init && dataspoc-pipe add postgres --tables ordersdataspoc-pipe run # Extracts to Parquet
# Then query with SQLdataspoc-lens query "SELECT SUM(amount) FROM raw.postgres.orders WHERE created_at > '2024-01-01'"Pare de gerar embeddings das suas tabelas. Comece a consultá-las.