Se você atua com o ERP Protheus da Totvs, sabe que enfrentamos rotineiramente desafios de manutenção de dados que exigem mais do que as ferramentas padrão. O bom e velho Replace na tela do MPSDU/APSDU é prático e seguro, mas a vida real no dia a dia do desenvolvimento e suporte técnico frequentemente nos apresenta cenários de correção que demandam uma ação mais profunda e complexa.
Eu já perdi a conta de quantas vezes me vi diante de uma inconsistência que só poderia ser resolvida com uma instrução SQL meticulosamente elaborada. E é exatamente para ter essa capacidade de intervenção direta, mas com a máxima segurança e controle, que desenvolvi e aprimorei o script que chamo de FUNCSQL.
O Verdadeiro Poder Reside na TCSQLExec
O segredo não é o meu script, mas sim a função padrão do ADVPL que ele orquestra: a poderosa TCSQLExec.
A TCSQLExec é a função nativa que permite ao ADVPL se comunicar diretamente com o Sistema Gerenciador de Banco de Dados (SGBD) — seja ele SQL Server, Oracle, PostgreSQL ou outro — e executar comandos puros de SQL (Structured Query Language).
A própria documentação oficial da Totvs é clara:
TCSQLExec( < cStatement > )Executa uma instrução de sintaxe SQL diretamente no banco de dados em que está conectado. Retorna o status da execução, sendo
< 0indicativo de falha.
Apesar de ser extremamente útil, o uso da TCSQLExec exige extremo cuidado. Por submeter comandos diretamente ao SGBD, ela não atualiza os campos de controle criados pelo TOTVS | DBAccess e depende da sintaxe específica do seu SGBD. É por isso que ela deve ser encapsulada em um ambiente controlado.
FUNCSQL: Segurança e Atomicidade na Execução
Meu script FUNCSQL nasceu da necessidade de aplicar o poder da TCSQLExec de forma responsável. A ideia surgiu e foi aprimorada a partir de uma rotina que meu amigo e colega de trabalho já utilizava há anos. A partir do script que ele usava, escrevi um simplificado e com algumas melhorias para uso próprio e para compartilhar aqui no blog.
A maior inovação do FUNCSQL é a implementação do controle de Transação (Transaction Control), que é a nossa rede de segurança:
Begin Transaction: Marcamos o início da operação.TCSQLExecem Loop: Executamos todas as instruções SQL (separadas por ponto e vírgula) que o usuário inseriu.RollBackem Caso de Falha: Se qualquer uma das instruções SQL retornar erro, o comandoRollBackdesfaz todas as alterações pendentes daquela transação. A integridade do seu banco é preservada!End Transaction(Commit): Se todos os comandos forem bem-sucedidos, as alterações são confirmadas no banco de dados.
É essa lógica que transforma a FUNCSQL na nossa Chave-Mestra de Precisão, permitindo ajustes complexos sem o risco de deixar o banco de dados em um estado inconsistente.
Casos Reais: Quando o Replace Não Consegue Acompanhar
Quando o replace padrão não é suficiente? Basicamente, em duas situações: Complexidade de Lógica ou Performance em Massa.
Exemplo 1: Atualização Lógica Condicional
Você precisa atualizar o campo de centro de custo (CC) na tabela de Clientes (SA1) para todos os clientes cuja filial (A1_FILIAL) é '01' E que não fizeram nenhum pedido de venda nos últimos 12 meses (verificando a tabela SC5). O replace padrão do Protheus não consegue cruzar essas informações em múltiplas tabelas para o critério de seleção.
Com a FUNCSQL, você pode usar um UPDATE com JOIN ou subqueries diretas, como no exemplo abaixo, que demonstra a necessidade de múltiplas queries atômicas:
-- 1. Cria uma tabela temporária de clientes inativos para agilizar a consultaSELECT A1_COD, A1_LOJA INTO #TEMP_INATIVOSFROM SA1010WHERE D_E_L_E_T <> '*' AND A1_FILIAL = '01'AND NOT EXISTS (SELECT 1 FROM SC5010 WHERE C5_CLIENTE = A1_COD AND C5_LOJACLI = A1_LOJA AND C5_EMISSAO > '20240101');-- 2. Executa o UPDATE apenas nos clientes selecionadosUPDATE SA1010 SET A1_CC = '999999'FROM #TEMP_INATIVOS TWHERE SA1010.A1_COD = T.A1_COD AND SA1010.A1_LOJA = T.A1_LOJA;-- 3. Limpa a tabela temporáriaDROP TABLE #TEMP_INATIVOS;Exemplo 2: Performance em Alto Volume
Corrigir um campo que está incorreto em centenas de milhares de registros de uma tabela como a de Movimentações Internas (SD3). Um replace via camada de aplicação pode levar horas. Uma instrução UPDATE executada via TCSQLExec dentro do FUNCSQL pode reduzir esse tempo para minutos, pois o processamento é delegado ao SGBD.
Exemplo 3: Popular Tabelas na Implantação de Módulos (Insert Manual Rápido)
Durante a fase de implantação de um novo módulo, muitas vezes precisamos inserir rapidamente uma lista inicial de cadastros ou parâmetros. Fazer a inclusão manual ou via rotinas lentas não é viável. A FUNCSQL permite que você insira esses dados diretamente na tabela, como o Cadastro de Produtos (SB1), de forma rápida e controlada:
-- Insere o produto de código 00001 na tabela SB1 (Cadastro de Produtos)INSERT INTO SB1010 ( B1_FILIAL, B1_COD, B1_DESC, B1_UM, B1_TIPO, R_E_C_N_O_)VALUES ( '01', '00001', 'CANETA BIC AZUL', 'UN', 'PA', (SELECT MAX(R_E_C_N_O_) + 1 FROM SB1010) -- Lógica simplificada de RECNO);-- Insere um segundo produto para demonstraçãoINSERT INTO SB1010 ( B1_FILIAL, B1_COD, B1_DESC, B1_UM, B1_TIPO, R_E_C_N_O_)VALUES ( '01', '00002', 'LAPIS PRETO HB', 'UN', 'PA', (SELECT MAX(R_E_C_N_O_) + 1 FROM SB1010) );Atenção: Na prática, o cálculo do R_E_C_N_O_ deve seguir a regra do seu ambiente, mas o ponto é: a FUNCSQL facilita a execução de múltiplos INSERTs de forma transacionalmente segura.
A Regra de Ouro: Documentação e Compliance
Como profissionais de tecnologia (desenvolvedores, analistas, cientistas, etc), nossa prioridade máxima é a integridade dos dados. Por isso, a execução do FUNCSQL ou de qualquer TCSQLExec exige um protocolo rígido que eu sigo à risca:
Aprovação Formal: Nunca execute sem a aprovação por escrito das áreas de negócio responsáveis. A forma de registrar isso por escrito pode variar: pode ser via e-mail, formulário assinado ou qualquer que seja o processo formal acordado pela empresa: aqui, solicitamos um ticket ao qual adicionamos um formulário, que deve ser assinado por toda a hierarquia das áreas envolvidas: solicitante, gestor do departamento, controladoria e às vezes até mesmo da diretoria. Sim, segurança exige burocracia.
Log/Documentação da Alteração: O script SQL exato que será executado deve ser anexado ao chamado formal (ticket) de manutenção ou em documento anexo ao formulário formal (ou e-mail, dependendo de como você pretende controlar isso na sua empresa).
Testes em Homologação: Obviamente, o comando é sempre testado exaustivamente em bases de Homologação ou Teste antes de seguir para o ambiente de produção.
A FUNCSQL é uma solução poderosa que garante que, mesmo nos ajustes mais avançados, o compliance e a segurança do seu ambiente Protheus sejam mantidos.
O Código-Fonte: FUNCSQL.PRW
Para aqueles que desejam implementar e testar essa solução em seus ambientes de desenvolvimento/homologação, abaixo está o código completo da rotina FUNCSQL.PRW, com a documentação simplificada e foco na clareza.
#include 'protheus.ch'
#include 'parmtype.ch'
//--------------------------------------------------------------------------------
/*
Função: FUNCSQL
Autor: Éder - The WebScrolls
Data: 10/11/2025
Versão: 2.1
Descrição: Rotina genérica e controlada para a execução de instruções SQL (UPDATE/DELETE/INSERT)
diretamente no Banco de Dados (SGBD) através de uma interface de diálogo.
É uma ferramenta de uso restrito a profissionais qualificados.
Uso: FUNCSQL()
Retorno: NIL
*/
//--------------------------------------------------------------------------------
User Function FUNCSQL()
Local cQueryText := Space(0) // Variável que armazenará a instrução SQL digitada pelo usuário.
Local oDlg := NIL // Objeto principal do MsDialog (Janela de Diálogo).
Local oMemo := NIL // Objeto para a área de texto (Memo) para a query.
// === 1. DEFINIÇÃO DA JANELA DE DIÁLOGO (MsDialog) ===
// Cria a janela principal para entrada da query SQL.
// Coordenadas em Pixel: 340 (altura) x 500 (largura).
Define MsDialog oDlg Title "Execução de Query SQL Avançada" From 3, 0 to 340, 500 Pixel
// === 2. CAMPO DE ENTRADA DE TEXTO (MEMO) ===
// Cria o campo de texto multi-linha (Memo) para o usuário digitar a instrução SQL.
@ 5, 5 Get oMemo Var cQueryText Memo Size 240, 145 Of oDlg Pixel
// [Opcional] Bloqueia o uso do botão direito do mouse no campo para fins de segurança/controle.
// oMemo:bRClicked := { || AllwaysTrue() }
// === 3. BOTÕES DE AÇÃO (SButton) ===
// Botão 'Executar' (Tipo 1 - Ação Primária):
// Chama a função estática QryExec para processar a query e fecha a janela.
Define SButton From 153, 175 Type 1 Action (QryExec(cQueryText), oDlg:End()) Enable Of oDlg Pixel
// Botão 'Cancelar' (Tipo 2 - Ação Secundária):
// Fecha a janela de diálogo sem executar o comando.
Define SButton From 153, 145 Type 2 Action oDlg:End() Enable Of oDlg Pixel
// === 4. ATIVAÇÃO DA JANELA ===
// Ativa e centraliza o diálogo na tela.
Activate MsDialog oDlg Center
Return
//--------------------------------------------------------------------------------
//--------------------------------------------------------------------------------
/*
Função: QryExec (Estática)
Autor: Éder - The WebScrolls
Data: 10/11/2025
Versão: 2.1
Descrição: Executa uma ou mais instruções SQL separadas por ponto e vírgula,
dentro de um bloco de transação para garantir que a operação
completa seja bem-sucedida (atomicidade).
Parâmetros: _cQry String - A string contendo as instruções SQL a serem executadas.
Retorno: NIL
*/
//--------------------------------------------------------------------------------
Static Function QryExec(_cQry)
Local nX := 0 // Variável de controle do loop.
Local aStatements // Array com as instruções SQL separadas.
Local nStatus // Status de retorno da função TCSQLExec.
Local cStatement := "" // Variável para armazenar cada instrução SQL individual.
// Divide a string de entrada em um array de instruções, usando ';' como delimitador.
aStatements := StrToKarr(_cQry, ";")
// Inicia uma Transação: Garante que todas as operações serão desfeitas se
// houver falha em qualquer um dos comandos SQL.
Begin Transaction
// Loop sobre cada instrução SQL (Statement) no array.
For nX := 1 To Len(aStatements)
// Remove espaços em branco desnecessários e quebras de linha.
cStatement := AllTrim(aStatements[nX])
// Executa apenas se a instrução não estiver vazia.
If !Empty(cStatement)
nStatus := TCSQLExec(cStatement) // Executa a instrução SQL no SGBD.
If (nStatus < 0) // Verifica se houve erro na execução.
// Se houver erro, desfaz as alterações (ROLLBACK), exibe o erro e interrompe.
RollBack // Desfaz as alterações.
MsgStop("Erro de SQL (Statement: " + cStatement + ") | Detalhe: " + TCSQLError(), "Falha na Execução SQL")
Return // Sai da função.
EndIf
EndIf
Next nX
// Confirma as alterações no banco de dados (COMMIT) se todas as instruções foram bem-sucedidas.
End Transaction
Alert("Query(s) executada(s) com sucesso e Transação Confirmada!", "Sucesso")
Return
//--------------------------------------------------------------------------------

Comentários
Postar um comentário