DBX – Executando SQL diretamente na Conexão
Na programação com componentes DBExpress, o componente TSQLConnection tem dois métodos muito interessantes:
- TSQLConnection.Execute – para comandos SQL que incluem parâmetros ou esperam o retorno de cursores.
- TSQLConnection.ExecuteDirect – para comandos SQL sem parâmetros.
A utilização destes métodos elimina a necessidade de utilizar SQL DataSet para tarefas diretas. Ambos métodos retornam o número de registros afetados pelas instrução SQL, diferente do que está escrito na ajuda do Delphi 2007.
No método ExecuteSQLDirect a seguir, temos algumas técnicas interessantes:
- Overload do método, podendo utiliza-lo de forma simples e a assinatura completa para a necessidade de tratar todos os parâmetros.
- Assert, para verificar se os parâmetros passados estão como esperado. Esta é uma técnica importante para identificar erros durante o desenvolvimento e que pode ser eliminado da versão final com uma diretiva de compilação.
- Controle de Transações com o DBX4, no Delphi 2007 o controle de transações foi reimplementado e os métodos existentes nas versões anteriores marcados como deprecated.
- Manter códigos ligados a conexão e transação em um único lugar.
function ExecuteSQLDirect(SQLConnection: TSQLConnection;
const SQL: string; var AffectedRows: Integer): boolean; overload;
var
Transaction: TDBXTransaction;
begin
Assert(SQLConnection <> nil, 'SQLConnection can''t be nil');
Assert(SQL <> EmptyStr, 'SQL can''t be empty');
Transaction:= SQLConnection.BeginTransaction;
try
try
AffectedRows:= SQLConnection.ExecuteDirect(SQL);
except
AffectedRows:= 0;
end;
finally
Result:= AffectedRows > 0;
if Result then
SQLConnection.CommitFreeAndNil(Transaction)
else
SQLConnection.RollbackFreeAndNil(Transaction);
end;
end;
function ExecuteSQLDirect(const SQL: string): boolean; overload;
var
Rows: Integer;
begin
Result:= ExecuteSQLDirect(MainDataModule.SQLConnection,
SQL, Rows);
end;
Implementei este código no mesmo Data Module onde mantenho o componente de conexão TSQLConnection, permitindo que eu tenha uma versão mais simples de ExecuteSQLDirect, passando o TSQLConnection.
O código a seguir foi implementado em outro Data Module, note o método DeleteAllColumns, este é o método que será chamado a partir das ações dos formulários, separando o código de tratamento dos dados. E se algum erro acontecer durante a exclusão dos registros, uma exceção de tipo específico a este domínio será levantada, ajudando na identificação dos possível erros em tempo de execução.
procedure TDataModuleImport.DeleteAllColumns;
const
SQL = 'DELETE FROM FPL_IMP_COLUMN WHERE FILE_ID = %d';
EMsg = 'Não foi possível excluir todas as colunas.';
begin
if CDSImpFileID.IsNull then
Exit;
if not ExecuteSQLDirect(Format(SQL, [CDSImpFileID.Value])) then
raise EColumnError.Create(EMsg);
end;
Note que a mensagem de erro neste método está em português e no outro método, quando utilizei o Assert, as mensagens estão em inglês. Isto é, por que as mensagens de Assert são para o programador e não para o usuário final.

7 Comments
Olá!
Uso uma técnica parecida para simplificar o uso do método Execute do TSQLConnection, mas agora com seu post me surgiu uma dúvida:
Se eu não criar uma transação explicitamente e fechá-la, como vc fez no exemplo, qual o comportamento adotado pela conexão?
Usará a transação ativa no momento? Fazendo isso posso ter problemas? Transações perdidas, abertas por muito tempo?
Daniel,
Vai depender do driver que você estiver usando e dos requisitos do banco de dados, tem alguns bancos de dados que requerem uma transação, mesmo para fazer uma consulta simples.
No caso do Interbase/Firebird é comum que os drivers criem uma transação interna, que tem o tempo de vida igual ao tempo de conexão, neste caso, se você não criar uma transação, esta padrão será usada, e o commit só ocorrerá quando a conexão fechar, mas se por acaso a aplicação por qualquer motivo travar, esta conexão ficará aberta no servidor e será descartada, e tudo que você poderá ser perdido, então a criação de uma transação da forma que sugeri traz segurança para as operações com banco de dados.
Cesar,
Desculpe por não citar meu cenário. Uso Delphi 2007, Firebird 2.1 e driver da CoreLab.
Por exemplo, uso a técnica citada para gerar a chave primária de algumas tabelas quando não posso usar generator, executando um “select max”, entre outras coisas. Portanto, pelo que entendi, seria melhor eu criar e finalizar a transação para este tipo de instrução.
Entretanto, caso seja executada uma instrução SQL (update ou delete) que esteja dentro do contexto de outra transação criada explicitamente, então não poderei usar a técnica citada. O script abaixo deixa mais claro:
Crio uma transação
try
ExecuteSQLDirect da instrução A
ExecuteSQLDirect da instrução B
ExecuteSQLDirect da instrução C
Commit
except
Rollback
end
Oi Cesar…
Eu estou num dilema brabo e talvez vc possa me ajudar (foi assim que cheguei no seu blog). Estou tentanto fazer um atualizador de metadata e de dados no meu banco (nesse caso, FB 1.5) e estou usando DBExpress com Delphi 7… Só que não consigo executar Scripts através do DBX… ou seja, ele só consegue executar um comando por vez (TSQLQuery). Já tentei outros comps e nada e não queria usar outros comps de acesso à dados (como o MDO/IBX/Zeos). Tem alguma sugestão ou dica? Ou você acha que no DBX do D7 realmente não tem como fazer isso?
Obrigado desde já e parabéns pelo blog, que agora está no meu “Favoritos”.
@Alexssandro Marcelino, cada instrução SQL é executada de uma vez, para controlar o contexto é utilizado a Transação.
Todos os componentes que trabalhei até hoje, aceitam um comando por vez, alguns se vc não especificar uma transação, ele vai uma transação padrão.
Minha sugestão é pra vc criar um separador de cada comando, passar todos os comandos pra um método que:
1) inicia a transação
2) faz um loop executando cada comando individualmente dentro de um try/except
3) se tudo der certo vc finaliza a transação com o commit
4) se ocorrer alguma exceção, vc cancela a transação com o rollback.
Já fiz este tipo de rotina várias vezes, e nos fontes do framework Jazz tem isto, mas lá o código é bem abstrato, não da pra ler numa seqüência, precisaria monitorar como ele executa pra ver isto acontecendo.
Ok Cesar. Obrigado pela atenção. Na verdade eu precisava de um executor de scripts, como o que existe na paleta de componentes do Mercury (MDO).
Boa tarde Cesar, fiz esta função e funcionou bem, com sentenças simples como INSERT, UPDATE, DELETE, mas tentei rodar uma sentença criando uma TRIGGER, já não deu, deu erro no SET TERM.Você por acaso conhece alguma solução para isso?