quinta-feira, 23 de agosto de 2007

Arredondamento de Datas

Essa semana eu peguei um probleminha interessante: a partir de uma tabela que tinha um campo [HoraInicial] e outro [HoraFinal], eu precisava exportar para uma segunda tabela, porém arredondada a cada 30 minutos, ou seja, em [HoraInicial], caso a hora seja 08:15h, arredondar para 08:00h; Caso seja 08:45, arredondar para 08:30h. Já em [HoraFinal], caso a hora seja 18:15h, arredondar para 18:30h; Caso seja 18:45, arredondar para 19:00h. Outra observação importante é que o campo no banco é do tipo Varchar, com os valores no formato '00:00'.

Considerando somente a [HoraFinal] eu poderia ter feito o seguinte usando a maneira tradicional:
- Pego os dois últimos caracteres e os dois primeiros e assumo como Minutos e Horas respectivamente;
- Se Minutos < 30 Então eu zero os minutos e +1 à hora; Senão eu assumo minutos = 30.
- Converto para SMALLDATETIME

O script somente para este campo seria semelhante ao SELECT abaixo:
DECLARE @HORA AS VARCHAR(5)
BEGIN
SET @HORA = '08:17'
SELECT CAST(
CASE WHEN CAST(RIGHT(@HORA, 2) AS INT) < 30 THEN
'1900-01-01 ' + LEFT(@HORA, 2) + ':30'
ELSE
'1900-01-01 ' + CAST(
CAST(LEFT(@HORA, 2) AS INT) + 1
AS VARCHAR(5)) + ':00'
END
AS SMALLDATETIME)
END

Conclusão: Funciona!!! O Problema seria se ao invés de 30 minutos nós tivessemos que fazer a cada 10 minutos, por exemplo. A quantidade de CASE WHEN cresceria absurdamente, poluindo meu código, pois eu precisaria verificar quando a Hora fosse < 10, 20, 30... Preferi fazer algo um pouquinho mais inteligente.

Os tipos de dados SMALLDATETIME e DATETIME representam valores númericos (FLOAT) onde a parte inteira representa os dias (partindo de '1900-01-01 00:00:00') e a parte fracionária as horas. Assim, '1900-01-02' representa o inteiro 1, e '12:00:00' representa a metade de um dia, ou seja, '.5'. Ao multiplicar uma fração por 24 é possível recuperar um float representando a qtde de horas ( '08:30' = 8,5 ). Ao multiplicar por 2 é possível fazer com que a parte inteira represente um período de 30 minutos (1/2 hora) ao invés de 60. Se ao invés de 2 fosse por 4, isso faria o inteiro representar um perído de 15 minutos (1/4 hora). O script ficaria semelhante à :
DECLARE @HORA AS VARCHAR(5)
BEGIN
SET @HORA = '08:17'
SELECT CAST( CEILING( CAST(
CAST('1900-01-01 ' + @HORA AS SMALLDATETIME)
AS FLOAT) * 24 * 2 ) / 2 / 24 AS SMALLDATETIME) HORA_FINAL
END

CEILING() é uma função que retorna o inteiro mais próximo acima de um determinado número. Por exemplo, CEILING(15.3) retorna 16. Para a [HoraIncial], é possível usar a função FLOOR(). FLOOR(15.7) retorna 15.

Nenhum comentário: