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.

outubro 15th, 2008 at 11:08
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?
outubro 15th, 2008 at 14:14
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.
outubro 15th, 2008 at 14:39
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
janeiro 26th, 2010 at 16:47
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”.
janeiro 26th, 2010 at 22:28
@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.
janeiro 27th, 2010 at 07:16
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).