Data

6 mar, 2013

Deu erro por incompatibilidade de tipos de dados. E agora?

Publicidade

Dia destes eu estava criando um procedimento armazenado e, como sempre, focando minha atenção nas coisas que eu considerava mais importantes:

  1. A lógica de negócios;
  2. A estrutura adequada das declarações SQL para fazer uso dos índices de cada tabela;
  3. Tratamento de erros e controle da transação.

Depois de algum trabalho, meu procedimento estava pronto. Foi criado usando o Command Line Processor (CLP) para rodar no DB2 em LINUX 64-bits. A sintaxe desta rotina é mostrada no código ao final do artigo.

Salvei este código no arquivo meuscript.db2 e executei o comando para criação da rotina. Para quem não conhece DB2 CLP, o parâmetro “-td” especifica o caractér de identificação de instruções dentro de um procedimento armazenado.

db2 -td@ -vf  meuscript.db2

A rotina foi criada com sucesso. Em seguida, fui testá-la:

db2  "CALL meuesquema.procedimento1 ('2013-01-22 00:00:00', '2013-03-30 00:00:00')"

Foi aqui que tive uma surpresa, porque observei que minha transação nunca se completava (ROLLBACK). Rodando passo a passo as instruções da lógica de negócios, identifiquei uma mensagem de erro de SQL informando “tipos de dados incompatíveis”.

Examinando meu código com mais atenção, encontrei o problema. Observe que na instrução de INSERT eu especifiquei o uso da função COALESCE:

COALESCE(T.END_TIMESTAMP, '00:00:00')

Isso quer dizer o seguinte: se o campo END_TIMESTAMP for não nulo, use o seu valor. Do contrário, use o  valor “00:00:00”.

Note que o horário “00:00:00” parece um TIMESTAMP, mas não é. No DB2, o tipo de dados TIMESTAMP aceita valores entre 0001-01-01-00.00.00.000000000 e 9999-12-31-24.00.00.000000000.

Sendo assim, o correto seria alterar o valor da função COALESCE para usar o menor TIMESTAMP reconhecido:

COALESCE(T.END_TIMESTAMP, '0001-01-01-00:00:00')

Depois de recriar a rotina e repetir o teste, vemos que tudo passa a funcionar adequadamente. Até parece piada o tempo que perdemos depurando erros elementares, mas isso tem a haver com a nossa falta de atenção com os detalhes. Nos preocupamos muito com o que nos parece complexo: lógicas de negócio, processos, etc. Mas não costumamos dar a devida atenção aos detalhes, como sintaxes ou tipos de dados.

Então fica a dica: cuidado com os erros bobos. Isso pode comprometer sua produtividade.

-- script DDL (criatabelas.ddl)
-- criacao de tabelas
CREATE TABLE meuesquema.tabela1 (nome VARCHAR(50), userid VARCHAR(50), start_timestamp TIMESTAMP , end_timestamp TIMESTAMP ); 

INSERT INTO meuesquema.tabela1 VALUES ('wagner', 'wcrivelini', '2013-03-01 0:00:00', null); 

CREATE TABLE meuesquema.tabela2 (campo1 VARCHAR(50), campo2 VARCHAR(50), campo3 TIMESTAMP );

-- script da rotina (meuscript.db2)
--procedimento armazenado em questão
CREATE PROCEDURE meuesquema.procedimento1 (
	startDate 	TIMESTAMP,
	endDate  	TIMESTAMP)
LANGUAGE SQL
DYNAMIC RESULT SETS 1
BEGIN

--===============================================
--error handling
--===============================================

	DECLARE SQLSTATE	CHAR(5) DEFAULT '00000';
	DECLARE l_error      	CHAR(5) DEFAULT '00000';
	DECLARE intError    	INT;

	DECLARE CONTINUE HANDLER FOR NOT FOUND 
	SET l_error = '00000';

	DECLARE CONTINUE HANDLER FOR SQLEXCEPTION, SQLWARNING

	BEGIN
		SET l_error = SQLSTATE;

		IF SUBSTR(l_error, 1, 1) >= '5' AND SUBSTR(l_error, 1, 1) <= '9' THEN 
			RESIGNAL;
		END IF;
	END;

--===============================================
-- business logic
--===============================================
	INSERT INTO meuesquema.tabela2 (campo1, campo2, campo3)
	SELECT 	
		UPPER(T.NOME) AS CAMPO1, 
		T.USERID AS CAMPO2,
		COALESCE(T.END_TIMESTAMP, '00:00:00') AS CAMPO3
	FROM  meuesquema.tabela1 T
	WHERE T.START_TIMESTAMP BETWEEN startDate  AND endDate;

	SET l_error = SQLSTATE ;

	IF LEFT(l_error, 2 ) > '02' THEN
		SET intError = intError + 1 ;
	END IF;

--===============================================
-- finishing transaction
===============================================
	  IF intError > 0 THEN
        ROLLBACK;
        RETURN intError ;
    END IF;

    COMMIT;

END
@