TCSQLExec: Como executar instruções no banco de dados - Protheus

Desenvolvedor Protheus utilizando ferramenta FUNCSQL para manipular dados via SQL em um servidor futurista

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 < 0 indicativo 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:

  1. Begin Transaction: Marcamos o início da operação.

  2. TCSQLExec em Loop: Executamos todas as instruções SQL (separadas por ponto e vírgula) que o usuário inseriu.

  3. RollBack em Caso de Falha: Se qualquer uma das instruções SQL retornar erro, o comando RollBack desfaz todas as alterações pendentes daquela transação. A integridade do seu banco é preservada!

  4. 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:

SQL
-- 1. Cria uma tabela temporária de clientes inativos para agilizar a consulta
SELECT A1_COD, A1_LOJA INTO #TEMP_INATIVOS
FROM SA1010
WHERE 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 selecionados
UPDATE SA1010 SET A1_CC = '999999'
FROM #TEMP_INATIVOS T
WHERE SA1010.A1_COD = T.A1_COD AND SA1010.A1_LOJA = T.A1_LOJA;
-- 3. Limpa a tabela temporária
DROP 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:

SQL
-- 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ção
INSERT 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