Se você trabalha com Oracle, já deve ter aberto o SQL Developer só para rodar um SELECT rápido, ou lutado com o SQL*Plus para editar a linha anterior. O SQLcl resolve as duas coisas: é a linha de comando moderna da Oracle, leve, com autocomplete, histórico e gerenciamento de conexões. E, desde as versões recentes, ele traz um servidor MCP embutido, que permite que agentes de IA como o Claude Code (e também Codex, Copilot, Devin e companhia) conversem com o seu banco.
Neste artigo vamos do zero ao uso real: download, conexões salvas, exportar e importar conexões, ligar o MCP no Claude Code, investigar dados guiado pelas suas entidades JPA, gerar queries e, principalmente, colocar regras para a IA nunca apagar nem alterar nada sem pedir. No fim, criamos uma skill que conecta no banco com uma frase.
Sumário
- O que é o SQLcl
- Download e instalação
- Conectando no banco
- Conexões salvas e o connmgr
- Exportando e importando conexões
- O servidor MCP do SQLcl
- Configurando no Claude Code
- Adendo: Codex, Copilot, Devin e outros
- Regras primeiro: defesa em camadas
- Na prática: investigando dados guiado pelas entidades JPA
- Na prática: o Claude Code escrevendo queries
- Criando uma skill para conectar no banco
- Checklist final e referências
1. O que é o SQLcl
O SQLcl (Oracle SQL Developer Command Line) é uma interface de linha de comando em Java para o Oracle Database. Ele roda praticamente todos os scripts de SQL*Plus e acrescenta o que faltava: edição de várias linhas, autocomplete com Tab, histórico, formatos de saída (CSV, JSON, INSERT), comandos como INFO e DDL, Liquibase, Data Pump e um gerenciador de conexões. É gratuito, distribuído sob a Oracle Free Use License, e não precisa de Oracle Client instalado: basta Java.
A novidade que motiva este post é a opção -mcp: com ela o SQLcl sobe como um servidor Model Context Protocol, expondo ferramentas como “listar conexões”, “conectar” e “rodar SQL” para qualquer agente de IA compatível.
2. Download e instalação
A página oficial de download é oracle.com/database/sqldeveloper/technologies/sqlcl/download. Ela sempre aponta para a última versão, que também pode ser baixada direto por este link fixo, sem login:
https://download.oracle.com/otn_software/java/sqldeveloper/sqlcl-latest.zip
Pré-requisito: Java 17 ou 21 (um JDK ou JRE). Para usar o MCP, a documentação exige uma dessas duas versões. Aqui eu usei o OpenJDK 21.
Linux e macOS
java -version # confirme: 17 ou 21
cd /opt
sudo curl -LO https://download.oracle.com/otn_software/java/sqldeveloper/sqlcl-latest.zip
sudo unzip -q sqlcl-latest.zip # cria a pasta /opt/sqlcl
# coloque o sql no PATH (use ~/.zshrc no macOS)
echo 'export PATH=/opt/sqlcl/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
sql -V
# SQLcl: Release 26.2.2.0 Production Build: 26.2.2.233.1901
Windows
Baixe o zip, extraia em C:\ (vai criar C:\sqlcl) e adicione C:\sqlcl\bin ao PATH em Configurações › Sistema › Sobre › Configurações avançadas › Variáveis de ambiente. Pelo PowerShell:
Expand-Archive .\sqlcl-latest.zip -DestinationPath C:\
[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\sqlcl\bin", "User")
# abra um novo terminal e confira
sql -V
Para atualizar depois, é só baixar o zip de novo e substituir a pasta. Suas conexões ficam fora dela (em ~/.dbtools), então nada se perde.
3. Conectando no banco
O jeito mais direto é o formato EZConnect usuario/senha@host:porta/servico. Para não deixar a senha no histórico do shell, prefira abrir sem conexão (/nolog) e deixar o SQLcl pedir a senha:
# direto (a senha fica no histórico do shell, evite)
sql loja/********@localhost:1521/FREEPDB1
# melhor: sem senha na linha de comando
sql /nolog
SQL> conn loja@localhost:1521/FREEPDB1 # o SQLcl pede a senha
# Autonomous Database com wallet
sql -cloudconfig Wallet_MeuBanco.zip admin@meubanco_high
Algumas opções úteis do sql (todas saem do sql -h): -S (modo silencioso, bom para scripts), -name <conexão> (usa uma conexão salva), -thin/-thick (driver JDBC), -nohistory, -noupdates e -R <nível> (restrição, que vai ser importante mais à frente).
4. Conexões salvas e o connmgr
Digitar host, porta e serviço toda vez cansa. O SQLcl salva conexões com nome, e esse é também o mecanismo que o MCP usa: o agente de IA só enxerga as conexões que você salvou. Para salvar, use -save no próprio connect. Por padrão a senha não é salva; com -savepwd ela vai para um wallet seguro. Para sobrescrever uma conexão existente, acrescente -replace.
O comando connmgr gerencia tudo depois disso. Na versão 26.2.2 ele tem os subcomandos list, show, test, add (pastas), move, rename, clone, delete, export e import. Veja uma sessão real, criando a conexão somente leitura que vamos usar no resto do artigo e organizando em uma pasta /dev:
Dois detalhes que aprendi testando: conn -save dev/loja não cria pastas (a barra vira parte do nome). Crie a pasta com connmgr add -folder e use connmgr move. E para renomear, o comando é connmgr rename -conn nome-antigo nome-novo.
Com a conexão salva, conectar vira uma palavra:
sql -name loja-dev-ro # já abre conectado
SQL> conn -name loja-dev # troca de conexão dentro do SQLcl
SQL> connmgr show loja-dev-ro # mostra detalhes (senha mascarada)
SQL> connmgr clone -original loja-dev -username outro_usuario loja-dev-outro
SQL> connmgr delete -conn loja-dev-outro
As conexões ficam em ~/.dbtools (no Windows, %USERPROFILE%\.dbtools), com as senhas guardadas em um wallet seguro.
5. Exportando e importando conexões
Trocou de máquina ou quer passar as conexões do time para um colega novo? O connmgr export gera um arquivo criptografado. A chave de criptografia não é digitada no comando: ela vem de um secret do SQLcl, criado com secret set. Atenção: nos meus testes o secret vale só para a sessão atual do SQLcl, então crie o secret e exporte (ou importe) na mesma sessão.
Opções que valem conhecer:
- export: passe nomes de conexões específicas em vez de
-all-connections, ou-folder /devpara exportar uma pasta.-preserve-foldersmantém a estrutura de pastas e-overwritesobrescreve o arquivo. - import:
-listsó mostra o conteúdo, sem importar.-duplicates IGNORE|RENAME|REPLACEdecide o que fazer com nomes repetidos (o padrão é IGNORE).-strip-passwordsimporta sem as senhas, ótimo para compartilhar com o time. O import também aceita o JSON exportado pelo SQL Developer. - Se aparecer “Warning: Could not add connection … to folder ”” durante o import, confira com
connmgr list: nos meus testes as conexões foram importadas mesmo assim.
6. O servidor MCP do SQLcl
O MCP é um protocolo aberto que padroniza como um agente de IA descobre e chama ferramentas externas. Quando você roda sql -mcp, o SQLcl não abre o prompt SQL>: ele fica conversando JSON-RPC pela entrada e saída padrão (transporte stdio). Quem inicia o processo é o próprio cliente de IA.
Consultei a lista de ferramentas direto no servidor (sqlcl-mcp-server 26.2.2.0). A documentação ainda mostra nomes antigos com hífen, mas na versão atual eles são estes:
| Ferramenta | O que faz |
|---|---|
connections_list | Lista as conexões salvas (filtra por nome ou usuário) |
connect / disconnect | Abre ou fecha uma conexão salva, pelo nome |
sql_run | Executa SQL ou PL/SQL e devolve o resultado em CSV |
sqlcl_run | Executa comandos do SQLcl (INFO, DDL, Liquibase, Data Pump…) |
schema_information | Resumo do schema (tabelas, colunas, relacionamentos) em vários níveis de detalhe |
request_status | Acompanha consultas assíncronas longas |
skills_sync, annotation_generate | Recursos extras (skills mantidas pela Oracle e anotações de schema) |
Três comportamentos que você precisa conhecer antes de soltar uma IA no banco:
- AUTOCOMMIT ligado. Ao conectar, o próprio servidor avisa “AUTOCOMMIT is ON”. Um
DELETEexecutado pelo agente é gravado na hora, sem chance deROLLBACK. - Tudo fica registrado. Cada consulta ganha o comentário
/* LLM in use is <modelo> */e, se o usuário puder criar tabelas, o SQLcl grava um log na tabelaDBTOOLS$MCP_LOGdo schema conectado. As colunasMODULEeACTIONdeV$SESSIONtambém mostram o cliente e o modelo. Com o usuário somente leitura (semCREATE TABLE), a tabela de log não é criada. - Nível de restrição. A opção
-Rdesativa comandos do SQLcl que mexem no sistema de arquivos. O nível 4 é o mais restritivo e, segundo a documentação, é o padrão no modo MCP. Só reduza (por exemplo-R 1, que libera scripts mas não comandos do host) se realmente precisar, e deixe o nível explícito na configuração, como faremos a seguir.
Veja o log gerado quando conectei com o usuário dono do schema (saída real):
select id, mcp_client, model, end_point_name, substr(log_message, 1, 60) msg
from dbtools$mcp_log order by id;
-- ID MCP_CLIENT MODEL END_POINT_NAME MSG
-- 1 claude-code claude-opus-5-5 connect Connect to LOJA
-- 2 claude-code claude-opus-5-5 sql_run select /* LLM in use is claude-opus-5-5 */ count(*) from ped...
A própria documentação da Oracle recomenda: usuário com privilégio mínimo, nada de produção, auditoria ligada e limpeza periódica da tabela de log. Vamos aplicar tudo isso.
7. Configurando no Claude Code
No diretório do seu projeto, um comando registra o servidor. Use o caminho completo do sql (no Windows, C:\sqlcl\bin\sql.exe):
claude mcp add --scope project sqlcl -- /opt/sqlcl/bin/sql -R 4 -mcp
Com --scope project, o Claude Code grava um .mcp.json na raiz, que você pode versionar para o time inteiro usar a mesma configuração (ele não tem senha nenhuma):
{
"mcpServers": {
"sqlcl": {
"type": "stdio",
"command": "/opt/sqlcl/bin/sql",
"args": ["-R", "4", "-mcp"],
"env": {}
}
}
}
Os outros escopos são local (padrão, só você, só neste projeto) e user (você, em todos os projetos). Abra o Claude Code, rode /mcp e o servidor sqlcl deve aparecer como conectado, com as ferramentas listadas. Dentro do Claude Code elas se chamam mcp__sqlcl__sql_run, mcp__sqlcl__connect e assim por diante; esses nomes serão usados nas regras de permissão.
Pronto, já dá para pedir “liste minhas conexões do SQLcl”. Mas antes do primeiro pedido de verdade, configure as regras. É o assunto mais importante deste artigo.
8. Adendo: Codex, Copilot, Devin e outros
Como o MCP é um padrão, o mesmo sql -mcp funciona em qualquer cliente que suporte servidores MCP via stdio. Muda só o arquivo de configuração:
OpenAI Codex (CLI, extensão de IDE e app usam o mesmo arquivo), em ~/.codex/config.toml:
[mcp_servers.sqlcl]
command = "/opt/sqlcl/bin/sql"
args = ["-R", "4", "-mcp"]
GitHub Copilot no VS Code, em .vscode/mcp.json (repare que a chave é servers):
{
"servers": {
"sqlcl": {
"command": "/opt/sqlcl/bin/sql",
"args": ["-R", "4", "-mcp"]
}
}
}
Claude Desktop usa o mesmo formato mcpServers do Claude Code, no arquivo claude_desktop_config.json. Cline usa cline_mcp_settings.json, também com mcpServers. Devin, Cursor, Windsurf e outros têm uma tela ou arquivo de “MCP servers” onde você informa o comando e os argumentos; a ideia é sempre a mesma. As regras que veremos a seguir também valem: cada ferramenta tem seu arquivo de instruções (AGENTS.md no Codex, .github/copilot-instructions.md no Copilot) e suas próprias opções de aprovação de ferramentas. E, principalmente, as camadas que ficam no banco e no SQLcl protegem você em qualquer cliente.
9. Regras primeiro: defesa em camadas
Um agente de IA é ótimo para explorar dados, mas ele age. Se você disser “limpe esses registros duplicados”, ele pode muito bem executar um DELETE, e com AUTOCOMMIT ligado não tem volta. Por isso eu não confio em uma camada só. Monte cinco:
Camada 5: um usuário somente leitura
Comece por baixo, que é a camada mais forte. Crie um usuário que só tem SELECT nas tabelas do schema (rodei como SYSTEM no PDB):
create user loja_ro identified by "********";
grant create session to loja_ro;
begin
for t in (select table_name from all_tables where owner = 'LOJA') loop
execute immediate 'grant select on loja.' || t.table_name || ' to loja_ro';
end loop;
end;
/
Depois salve a conexão loja-dev-ro com esse usuário, como vimos na seção 4. Se alguém tentar apagar algo por ela, o Oracle responde assim (saída real):
ORA-41900: missing DELETE privilege on "LOJA"."CLIENTE"
Camada 1: CLAUDE.md com as regras do projeto
O CLAUDE.md na raiz do projeto é lido pelo Claude Code em toda sessão. É onde você diz o que pode e o que não pode, e onde estão as entidades. Este é o do projeto de exemplo:
# loja-api
API de pedidos em Spring Boot + JPA (Hibernate) sobre Oracle.
Entidades JPA: `src/main/java/br/com/loja/domain`.
## Banco de dados (SQLcl MCP)
- Conexão padrão: `loja-dev-ro` (usuário somente leitura). Tabelas no schema `LOJA`.
- Use `loja-dev` (leitura e escrita) **somente** se eu pedir explicitamente.
- Nunca se conecte a conexões com `prod` no nome.
- Antes de escrever SQL, leia a entidade JPA correspondente e use os nomes de `@Table` / `@Column` / `@JoinColumn`.
- Toda consulta exploratória deve ter `FETCH FIRST 50 ROWS ONLY` (ou menos).
- Mostre a query que você vai rodar antes do resultado.
## Regras de segurança (obrigatórias)
- Este projeto é **somente leitura**: não execute INSERT, UPDATE, DELETE, MERGE, DDL, GRANT, COMMIT/ROLLBACK nem blocos PL/SQL.
- Se a solução exigir alteração, **pergunte antes** e entregue o script em um bloco de código para eu revisar e executar manualmente.
- Nunca apague nada. Se eu pedir para apagar, confirme o `WHERE` e mostre quantas linhas seriam afetadas com um `SELECT COUNT(*)` antes.
- Não mostre dados pessoais completos (e-mail, CPF, telefone): mascare.
Se o seu objetivo não é “nunca altere”, e sim “altere só com minha autorização”, ajuste a seção de segurança: por exemplo, “antes de qualquer INSERT/UPDATE/DELETE, mostre o comando, o SELECT COUNT(*) com o mesmo WHERE e espere eu responder sim“. O importante é a regra estar escrita, e não depender do bom senso do modelo.
Camadas 2 e 3: permissões e um hook que bloqueia escrita
Em .claude/settings.json você define o que roda sem perguntar (allow) e o que sempre pede aprovação (ask). Listar conexões e ler o schema é inofensivo; conectar e executar SQL pedem sua confirmação. O mesmo arquivo registra um hook PreToolUse, um script que o Claude Code roda antes de cada chamada de ferramenta:
{
"permissions": {
"allow": [
"mcp__sqlcl__connections_list",
"mcp__sqlcl__schema_information"
],
"ask": [
"mcp__sqlcl__connect",
"mcp__sqlcl__sql_run",
"mcp__sqlcl__sqlcl_run"
]
},
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__sqlcl__sql_run|mcp__sqlcl__sqlcl_run",
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/sql_guard.py\""
}
]
}
]
}
}
O hook recebe a chamada em JSON pela entrada padrão. Se ele terminar com código de saída 2, a chamada é bloqueada e a mensagem escrita no stderr volta para o modelo como explicação. Este é o .claude/hooks/sql_guard.py: remove comentários e textos entre aspas (para não ser enganado por 'delete' dentro de uma string), procura palavras de escrita e, para comandos do SQLcl, só permite os de leitura:
#!/usr/bin/env python3
"""Bloqueia comandos que alteram dados/estrutura enviados ao SQLcl MCP."""
import json, re, sys
evento = json.load(sys.stdin)
ferramenta = evento.get("tool_name", "")
entrada = evento.get("tool_input", {})
def sem_comentarios_e_textos(sql: str) -> str:
sql = re.sub(r"/\*.*?\*/", " ", sql, flags=re.S) # /* ... */
sql = re.sub(r"--[^\n]*", " ", sql) # -- ...
sql = re.sub(r"'(?:''|[^'])*'", "''", sql) # 'textos'
return sql.lower()
PROIBIDOS = r"\b(insert|update|delete|merge|drop|truncate|alter|create|grant|revoke|rename|execute\s+immediate|dbms_\w+|commit|rollback)\b"
if ferramenta.endswith("__sql_run"):
sql = sem_comentarios_e_textos(entrada.get("sql", ""))
if re.search(PROIBIDOS, sql) or re.match(r"\s*(begin|declare)\b", sql):
print("Bloqueado pelo sql_guard: este projeto só permite consultas (SELECT/WITH). "
"Mostre o script ao usuário e peça para ele executar manualmente.", file=sys.stderr)
sys.exit(2)
if ferramenta.endswith("__sqlcl_run"):
comando = entrada.get("sqlcl", "").strip().lower()
PERMITIDOS = ("info", "desc", "describe", "ddl", "help", "show", "history")
if not comando.startswith(PERMITIDOS):
print(f"Bloqueado pelo sql_guard: comando SQLcl não permitido ({comando.split()[0] if comando else ''}).", file=sys.stderr)
sys.exit(2)
sys.exit(0)
O hook é propositalmente conservador: prefere bloquear um SELECT que tenha a palavra update no nome de uma coluna a deixar passar uma escrita. Ele não substitui o usuário somente leitura; é uma rede a mais.
O experimento: vale a pena ter tudo isso?
Testei. Com o CLAUDE.md no lugar, pedi “apague o cliente 4, pode executar direto”. O Claude Code recusou: citou as regras do projeto, ofereceu um SELECT COUNT(*) para conferir o impacto e entregou o DELETE em um bloco de código para eu executar manualmente. Ótimo.
Depois tirei o CLAUDE.md e a skill e repeti o pedido. O modelo conectou, notou que o AUTOCOMMIT estava ligado, disse “vou prosseguir com o comando exato solicitado” e chamou o sql_run com o DELETE. Quem salvou foi o hook:
Essa é a lição principal: instrução é importante, mas não é garantia. Coloque as regras por escrito, deixe o Claude Code pedir aprovação para executar SQL, bloqueie escrita com código e, acima de tudo, conecte com um usuário que simplesmente não tem permissão para alterar nada. E, claro: nunca aponte isso para produção.
10. Na prática: investigando dados guiado pelas entidades JPA
Este é o uso que mais me economiza tempo. As suas entidades JPA descrevem o que a aplicação espera do banco: colunas obrigatórias, tamanhos, enums, relacionamentos e, muitas vezes, regras de negócio no Javadoc. O banco guarda o que realmente existe. O Claude Code, com acesso aos dois, cruza uma coisa com a outra.
O projeto de exemplo tem as entidades Cliente, Produto, Pedido, ItemPedido e Pagamento. Um trecho do Pedido:
public enum StatusPedido { NOVO, PAGO, ENVIADO, CANCELADO }
@Entity
@Table(name = "PEDIDO")
public class Pedido {
@ManyToOne(optional = false)
@JoinColumn(name = "CLIENTE_ID")
private Cliente cliente;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private StatusPedido status;
/** Regra de negócio: deve ser igual à soma de quantidade * precoUnitario dos itens. */
@Column(name = "VALOR_TOTAL", nullable = false, precision = 12, scale = 2)
private BigDecimal valorTotal;
@OneToMany(mappedBy = "pedido")
private List<ItemPedido> itens;
/** Regra de negócio: pedido PAGO precisa ter um Pagamento. */
@OneToOne(mappedBy = "pedido")
private Pagamento pagamento;
}
O pedido que fiz ao Claude Code foi este:
As entidades JPA estão em src/main/java/br/com/loja/domain. Conecte no banco de dev e verifique se os dados respeitam o que as entidades esperam: colunas obrigatórias, tamanhos, enums, unicidade, relacionamentos e as regras de negócio descritas nos comentários. Liste os problemas encontrados com o impacto de cada um.
O que aconteceu, resumido (sessão real):
Repare no primeiro achado. Ele não é só um “dado estranho”: o Claude Code entendeu, pela anotação @Enumerated(EnumType.STRING), que esse registro derruba a aplicação em tempo de execução. É o tipo de bug que você passaria uma tarde depurando a partir de um stack trace. Uma das consultas que ele gerou para a regra do valor total foi esta:
select p.id, p.valor_total,
sum(i.quantidade * i.preco_unitario) soma_itens
from loja.pedido p
join loja.item_pedido i on i.pedido_id = p.id
group by p.id, p.valor_total
having p.valor_total <> sum(i.quantidade * i.preco_unitario)
fetch first 50 rows only;
A IA não é infalível: no banco de exemplo eu tinha plantado quatro problemas, e ela achou três. O quarto era um e-mail duplicado que só difere em maiúsculas (ana.souza@ e Ana.Souza@). A verificação de unicidade dela comparou os e-mails de forma exata e não pegou. Bastou uma pergunta de acompanhamento, “verifique também e-mails que só diferem por maiúsculas e minúsculas”, e ela gerou esta consulta, já mascarando os dados como manda o CLAUDE.md:
SELECT c.ID, REGEXP_REPLACE(c.EMAIL, '(^.{2}).*(@.*$)', '\1***\2') EMAIL_MASCARADO
FROM LOJA.CLIENTE c
WHERE LOWER(c.EMAIL) IN (
SELECT LOWER(EMAIL) FROM LOJA.CLIENTE GROUP BY LOWER(EMAIL) HAVING COUNT(*) > 1
)
ORDER BY LOWER(c.EMAIL)
FETCH FIRST 50 ROWS ONLY
-- ID EMAIL_MASCARADO
-- 1 an***@email.com
-- 4 An***@Email.com
E explicou o porquê: o unique = true da entidade vira uma constraint no Oracle, que compara VARCHAR2 diferenciando maiúsculas, então o banco aceita os dois registros. Se a aplicação normaliza o e-mail no login, esses dois clientes colidem. A lição para o seu prompt: diga o que é importante para você e trate a resposta como um ótimo ponto de partida, não como auditoria final.
Outras perguntas que funcionam muito bem nesse modo:
- “O endpoint GET /pedidos/42 está dando 500. Olhe o stack trace no log, leia as entidades envolvidas e confira o pedido 42 no banco.”
- “Compare as entidades com as tabelas: tem coluna no banco sem mapeamento, ou campo mapeado que não existe?”
- “Quais índices existem em PEDIDO? A consulta do PedidoRepository.findByClienteAndStatus vai usar algum?”
- “Quantos pedidos foram criados por dia na última semana, por status?”
11. Na prática: o Claude Code escrevendo queries
A outra metade do valor está em escrever SQL (e JPQL) que já foi testado contra os dados reais. O pedido:
Preciso de um relatório de faturamento por produto: quantidade vendida e receita, só de pedidos pagos, enviados ou entregues, do maior para o menor. Teste no banco, me dê a SQL comentada e uma versão JPQL para um repository Spring Data.
Três coisas chamaram minha atenção na resposta. Primeiro, ele leu o enum e avisou que ENTREGUE não existe em StatusPedido, então filtrou só PAGO e ENVIADO, sem alterar nada e sinalizando a divergência. Segundo, a primeira execução falhou com ORA-00942 porque o usuário loja_ro não é dono das tabelas; ele percebeu sozinho, acrescentou o prefixo loja. e rodou de novo. Terceiro, mostrou o resultado real antes de entregar o código:
| SKU | Produto | Qtd. | Receita |
|---|---|---|---|
| MON-003 | Monitor 27″ | 2 | 3.198,00 |
| TEC-001 | Teclado mecânico | 2 | 699,80 |
| MOU-002 | Mouse sem fio | 2 | 259,80 |
| HEA-004 | Headset USB | 1 | 259,00 |
-- Faturamento por produto: quantidade vendida e receita
-- Considera pedidos PAGO e ENVIADO (ENTREGUE não existe no enum StatusPedido; CANCELADO é ignorado)
SELECT p.id AS produto_id,
p.sku,
p.descricao,
SUM(ip.quantidade) AS qtd_vendida,
SUM(ip.quantidade * ip.preco_unitario) AS receita
FROM item_pedido ip
JOIN pedido pe ON pe.id = ip.pedido_id
JOIN produto p ON p.id = ip.produto_id
WHERE pe.status IN ('PAGO', 'ENVIADO') -- ajustar se ENTREGUE for adicionado ao enum
GROUP BY p.id, p.sku, p.descricao
ORDER BY receita DESC;
E a versão para a aplicação, com um DTO em record e a consulta no repository:
public record FaturamentoProdutoDTO(
Long produtoId,
String sku,
String descricao,
Long qtdVendida,
BigDecimal receita) {
}
public interface ItemPedidoRepository extends JpaRepository<ItemPedido, Long> {
@Query("""
SELECT new br.com.loja.dto.FaturamentoProdutoDTO(
i.produto.id, i.produto.sku, i.produto.descricao,
SUM(i.quantidade), SUM(i.quantidade * i.precoUnitario))
FROM ItemPedido i
WHERE i.pedido.status IN :statusPermitidos
GROUP BY i.produto.id, i.produto.sku, i.produto.descricao
ORDER BY SUM(i.quantidade * i.precoUnitario) DESC
""")
List<FaturamentoProdutoDTO> faturamentoPorProduto(
@Param("statusPermitidos") List<StatusPedido> statusPermitidos);
}
// uso
repo.faturamentoPorProduto(List.of(StatusPedido.PAGO, StatusPedido.ENVIADO));
Esse ciclo de “escrever, rodar, corrigir e mostrar o resultado” é o que diferencia usar o MCP de simplesmente pedir SQL para um chat: a query que chega para você já rodou contra o schema de verdade.
12. Criando uma skill para conectar no banco
Uma skill do Claude Code é uma pasta com um arquivo SKILL.md: um nome, uma descrição de quando usar e o passo a passo. O Claude Code lê só a descrição de cada skill no início e carrega o conteúdo quando o pedido combina. Em todas as minhas sessões de teste ele acionou a skill sozinho quando eu disse “conecte no banco de dev”. Você também pode chamar diretamente com /oracle-db.
Crie .claude/skills/oracle-db/SKILL.md no projeto (para valer em todos os seus projetos, use ~/.claude/skills/oracle-db/SKILL.md):
---
name: oracle-db
description: Conecta no banco Oracle do projeto pelo SQLcl MCP e prepara a sessão para investigar dados. Use quando o usuário pedir para "conectar no banco", "olhar no dev", consultar tabelas, investigar dados ou validar entidades JPA contra o banco.
---
# Conectar no Oracle (SQLcl MCP)
## Apelidos de ambiente
| O usuário diz | Conexão salva | Modo |
|-----------------------|----------------|-------------------------|
| dev, desenvolvimento | `loja-dev-ro` | somente leitura |
| dev-rw, escrita | `loja-dev` | só com pedido explícito |
Qualquer outra coisa: rode `connections_list` e pergunte qual usar. Nunca invente nomes.
Recuse conexões com `prod` no nome.
## Passo a passo
1. Chame `connections_list` com `name` filtrando pelo projeto (`loja`).
2. Chame `connect` com a conexão resolvida pela tabela acima.
3. Rode e mostre um resumo curto:
select user, sys_context('USERENV','CON_NAME') pdb,
(select version_full from product_component_version where rownum = 1) versao
from dual
4. Chame `schema_information` com `schema: LOJA` e `level: BRIEF`.
5. Se o usuário mencionou entidades, leia os arquivos em
`src/main/java/br/com/loja/domain` antes de escrever qualquer SQL.
## Regras da sessão
- Somente leitura. Nada de DML, DDL ou PL/SQL. Se for preciso alterar algo,
gere o script, explique o impacto e **pergunte antes**.
- Consultas exploratórias sempre com `FETCH FIRST 50 ROWS ONLY`.
- Mascare e-mails e documentos no resultado.
- Termine dizendo em qual conexão está e que nada foi alterado.
Algumas decisões por trás dessa skill:
- A descrição é o gatilho. Coloque nela as frases que você realmente usa (“olhar no dev”, “conectar no banco”). É ela que faz o Claude Code escolher a skill.
- Apelidos em vez de nomes técnicos. Você fala “dev”, a skill resolve para
loja-dev-ro. E o caminho padrão é sempre o somente leitura. - Verificação logo de cara. O passo 3 mostra usuário, PDB e versão. Você bate o olho e sabe exatamente onde está conectado antes da primeira consulta.
- As regras repetidas. Elas já estão no
CLAUDE.md, mas a skill pode ir para~/.claude/skillse ser usada em projetos que não têm esse arquivo. Redundância aqui é intencional.
Tem vários bancos? Acrescente linhas na tabela de apelidos (hml, qa…) ou crie uma skill por sistema. E, se preferir, o próprio SQLcl tem a ferramenta skills_sync, que instala skills mantidas pela Oracle para tarefas específicas do banco.
13. Checklist final
sql -V funcionando, Java 17 ou 21conn -save ... -savepwd, testada com connmgr testconnmgr export -key (backup e onboarding do time).mcp.json com sql -R 4 -mcpCLAUDE.md com as regras: somente leitura, pergunte antes, nunca apague, mascare dados.claude/settings.json com ask para sql_run e o hook sql_guard.pyoracle-db para conectar com uma fraseDBTOOLS$MCP_LOG quando usar um usuário que possa criá-laCom isso, o Claude Code (ou o Codex, o Copilot, o Devin…) vira um colega que conhece as suas entidades, lê o banco, cruza as duas coisas e escreve a query testada, sem nunca ter a chance de apagar nada. Se você montar esse fluxo aí, me conta nos comentários o que ele encontrou no seu banco.