Manejando fechas de la manera correcta en SQL Server

Posted on
  • martes, 23 de septiembre de 2008
  • by
  • Román
  • in
  • Etiquetas: , , , ,
  • Creo que siempre existe un bache en el tema del manejo de fechas en SQL Server, de modo que voy a tratar de clarificar algunos puntos en este artículo.

    Existen dos tipos de datos para almacenar fecha en SQL Server, Datetime y SmallDatetime.

    La diferencia entre ambas es el espacio que ocupan y la precisión de cada tipo de datos. Datetime es el tipo de datos mas preciso.

    El punto es saber como es que SQL Server almacena internamente las fechas. Algunos pensarán en los formatos comunes mm/dd/yyyy o dd/mm/yyyy o formatos similares. Pero no es así.
    SQL Server en realidad almacena las fechas como un número entero de 8 bytes, con los primeros 4 bytes almacenados fecha y los otros 4 bytes almacenado la hora. El Smalldatetime por su parte ocupa la mitad de esto, 4 bytes totales, 2 para fecha y 2 para la hora.

    Ahora bien, como gente astuta que sois me preguntareis porque motivois (me pintó el hispánico) los selects traen la fecha en un formato dado (americano/español, japonés, coreano, papuense, etc)????
    Ese formato me genera un problema al momento de filtrar por fechas, porque 3/1/2008 podría en realidad resultar el 1 de marzo del 2008 y no el 3 de enero del 2008. Me explico hasta aquí??

    Como hago entonces para librarme de las configuraciones regionales, seteos, formatos y ser libre de todas estas cosas y despreocuparme por siempre y que mis consultas filtradas por fechas funcionen siempre??

    Alguno de ustedes dirá que quizás usando el Standard ANSI y yo le contesto SI ! Esa es la respuesta !. El estandar ansi es así:
    YYYYMMDD HH:mm:ss’
    Ese es el formato que yo tendría que usar para mis consultas.
    Veamos el caso ejemplificado con Northwind la cual viene con SQL Server 2000 o pueden bajarlo directamente desde Microsoft en el peor de los casos.
    /* Creo un usuario con idioma predeterminado en Español */ 
    sp_addlogin 'usuario','pepenervos','master','Español'
     
    /* vamos a darle acceso a Northwind */
    use Northwind
    GO
    sp_grantdbaccess 'usuario' 
    GO
    Nos logueamos en el Query Analizer como "usuario".
    Con la siguiente instruccion confirmamos el idioma español para el usuario "usuario":
    Select @@Language
    La consulta devuelve:
    ---------------------------------------------------------
    Español
    (1 filas afectadas)
    Ahora usamos la tabla Orders y hacemos la consulta de fechas de la forma tradicional y la segunda usando el formato ANSI propuesto.
    use northwind
    go
     
    -- la opcion que siempre hemos usado hasta hoy que os he mostrado el camino
    select count(*) from orders where orderdate >='01-08-1997'
     
    -- La opción que cambiará vuestras vidais.
    select count(*) from orders where orderdate >='19970801'
    Ambas consultas devuelven 460 registros para pedidos posteriores al 1 de agosto del 97.
    Cambiamos el lenguaje al idioma inglés y volvemos a probar a ver que zapa.
    use northwind
    go
     
    SET LANGUAGE us_english
    GO
     
    -- ya dijimos que esta opción no corre mas.
    select count(*) from orders where orderdate >='01-08-1997'
     
    -- la opción ANSI que hizo que dejeis de sufrir.
    select count(*) from orders where orderdate >='19970801'
     
    La primera consulta devolvió 670 registros y la segunda sigue devolviendo la cantidad correcta, 460 registros. Lo que ocurrió es que al cambiar el formato regional la primera consulta en realidad está buscando registros mayores el 8 de enero y eso no tiene nada que ver con el 1 de agosto que nosotros queremos filtrar !

    La segunda sentencia sigue siendo correcta, de modo que ya saben, a usar el formato ANSI que se acaba el mundo.
    Y si quiero traer solamente los registros de un solo día, teniendo en cuenta que el ansi me toma la hora y eso puede hacer que no devuelva todos los registros de ese dia, podemos escribir algo como lo que sigue:

    Select * from orders Where orderdate >='19970805' and orderdate <>

    En pocas palabras, le sumo un dia a la fecha que estoy buscando y hago que el filtro funcione por menor a ese dia, de ese modo vienen todos los registros del dia que quiero recuperar.

    Y listo el pollo. Una solución sencilla y efectiva al tema de las fechas.


    Hugo Román Bernachea
    Mail de contacto: SQLServer777@gmail.com

    Microsoft Certified DBA
    Microsoft Certified Trainer
    Twitter: @bernachea

    2 comentarios:

    Shide..! dijo...

    Es gracioso porque actualmente haciendo consultas a un SQLSERVER2005, que sólo tiene el GETDATE() o el GETUTCDATE() para coger la fecha actual y mezclándolo con el DATEADD() me he dado cuenta de que la fecha que coge no es siempre la misma. Por ejemplo:
    Hoy es 5-10-2011.
    haciendo un:
    SELECT DATEADD(MONTH, -4, GETDATE())
    me dice la fecha correcta, que restando 4 meses sería 2011-06-05 11:47:40.957.
    Ahora bien, usando ésto mismo en una consulta para comparar fechas y sacar los registros mayores a ésta fecha, saca un día menos! Parece raro, pero yo mismo estoy incrédulo. Ej:
    SELECT fecha FROM tabla1 WHERE fecha >= DATEADD(MONTH, -4, GETDATE())
    Ésto teóricamente, según el SELECT de antes, debería sacar registros del mismo día que antes en adelante. Pues no es así... la última consulta saca los registros a partir del día 6-6-2011.
    Le he dado mil vueltas a ésto y aún no he encontrado la explicación lógica. He probado también a restarle la hora en la que estamos con el DATEADD() para dejarlo a las 00:xx:00 y tampoco... :/
    Resumiendo; Si quieres meter el DATEADD() en una consulta con WHERE, debes añadir un día a los días que tienes que restar (-(4+1)) para que pille bien el día de inicio.
    Una chapuza por parte del SQLSERVER2005...

    Anónimo dijo...

    Esta consulta quedo incompleta ??

    Select * from orders Where orderdate >='19970805' and orderdate <>

    Publicar un comentario