sql >> Base de Datos >  >> RDS >> Sqlserver

DATEDIFF () devuelve resultados incorrectos en SQL Server? Lee esto.

Si obtiene resultados realmente extraños al usar DATEDIFF() en SQL Server y está convencido de que la función contiene un error, no se arranque los pelos todavía. Probablemente no sea un error.

Hay escenarios en los que los resultados producidos por esta función pueden ser bastante extravagantes. Y si no comprende cómo funciona realmente la función, los resultados se verán completamente erróneos.

Con suerte, este artículo puede ayudar a aclarar cómo DATEDIFF() está diseñada para funcionar y proporciona algunos escenarios de ejemplo en los que los resultados podrían no ser los esperados.

Ejemplo 1:365 días no siempre es un año

Pregunta: Cuando es 365 días no un año?

Respuesta: Al usar DATEDIFF() ¡por supuesto!

Aquí hay un ejemplo donde uso DATEDIFF() para devolver el número de días entre dos fechas y, a continuación, el número de años entre las mismas dos fechas.

DECLARE 
  @startdate datetime2 = '2016-01-01  00:00:00.0000000', 
  @enddate datetime2 = '2016-12-31 23:59:59.9999999';
SELECT 
  DATEDIFF(day, @startdate, @enddate) Days,
  DATEDIFF(year, @startdate, @enddate) Years;

Resultado:

+--------+---------+
| Days   | Years   |
|--------+---------|
| 365    | 0       |
+--------+---------+

Si cree que este resultado es incorrecto y que DATEDIFF() obviamente tiene un error, sigue leyendo, no todo es lo que parece.

Lo creas o no, este es en realidad el resultado esperado. Este resultado está exactamente de acuerdo con cómo DATEDIFF() está diseñado para funcionar.

Ejemplo 2:¿100 nanosegundos =1 año?

Tomemos las cosas de otra manera.

DECLARE @startdate datetime2 = '2016-12-31 23:59:59.9999999', 
  @enddate datetime2 = '2017-01-01 00:00:00.0000000';

SELECT DATEDIFF(year,     @startdate,   @enddate) Year,
  DATEDIFF(quarter,       @startdate,   @enddate) Quarter,
  DATEDIFF(month,         @startdate,   @enddate) Month,
  DATEDIFF(dayofyear,     @startdate,   @enddate) DOY,
  DATEDIFF(day,           @startdate,   @enddate) Day,
  DATEDIFF(week,          @startdate,   @enddate) Week,
  DATEDIFF(hour,          @startdate,   @enddate) Hour,
  DATEDIFF(minute,        @startdate,   @enddate) Minute,
  DATEDIFF(second,        @startdate,   @enddate) Second,
  DATEDIFF(millisecond,   @startdate,   @enddate) Millisecond,
  DATEDIFF(microsecond,   @startdate,   @enddate) Microsecond,
  DATEDIFF(nanosecond,    @startdate,   @enddate) Nanosecond;

Resultados (mostrado con salida vertical):

Year        | 1
Quarter     | 1
Month       | 1
DOY         | 1
Day         | 1
Week        | 1
Hour        | 1
Minute      | 1
Second      | 1
Millisecond | 1
Microsecond | 1
Nanosecond  | 100

Solo hay una diferencia de cien nanosegundos (0,0000001 segundos) entre las dos fechas/horas, pero obtenemos exactamente el mismo resultado para cada parte de la fecha, excepto los nanosegundos.

¿Cómo puede suceder esto? ¿Cómo puede haber una diferencia de 1 microsegundo y 1 año de diferencia al mismo tiempo? ¿Sin mencionar todas las fechas intermedias?

Puede parecer una locura, pero esto tampoco es un error. Estos resultados están exactamente de acuerdo con cómo DATEDIFF() se supone que funciona.

Y para hacer las cosas aún más confusas, podríamos obtener diferentes resultados según el tipo de datos. Pero llegaremos a eso pronto. Primero veamos cómo DATEDIFF() la función realmente funciona.

La definición real de DATEDIFF()

La razón por la que obtenemos los resultados que obtenemos es porque DATEDIFF() la función se define de la siguiente manera:

Esta función devuelve el recuento (como un valor entero con signo) de los límites de parte de fecha especificados cruzados entre la fecha de inicio especificada y fecha de finalización .

Preste especial atención a las palabras "límites de parte de fecha cruzados". Esta es la razón por la que obtenemos los resultados que obtenemos en los ejemplos anteriores. Es fácil asumir que DATEDIFF() usa el tiempo transcurrido para sus cálculos, pero no lo hace. Utiliza el número de límites de parte de fecha cruzados.

En el primer ejemplo, las fechas no cruzaron ningún límite de parte del año. El año de la primera fecha fue exactamente el mismo que el año de la segunda fecha. No se cruzaron fronteras.

En el segundo ejemplo, teníamos el escenario opuesto. Las fechas cruzaron cada límite de parte de fecha al menos una vez (100 veces por nanosegundos).

Ejemplo 3:un resultado diferente para la semana

Ahora, supongamos que ha pasado un año entero. Y aquí estamos exactamente un año después con los valores de fecha/hora, excepto que los valores de año se han incrementado en uno.

Deberíamos obtener los mismos resultados, ¿verdad?

DECLARE @startdate datetime2 = '2017-12-31 23:59:59.9999999', 
  @enddate datetime2 = '2018-01-01 00:00:00.0000000';

SELECT DATEDIFF(year,     @startdate,   @enddate) Year,
  DATEDIFF(quarter,       @startdate,   @enddate) Quarter,
  DATEDIFF(month,         @startdate,   @enddate) Month,
  DATEDIFF(dayofyear,     @startdate,   @enddate) DOY,
  DATEDIFF(day,           @startdate,   @enddate) Day,
  DATEDIFF(week,          @startdate,   @enddate) Week,
  DATEDIFF(hour,          @startdate,   @enddate) Hour,
  DATEDIFF(minute,        @startdate,   @enddate) Minute,
  DATEDIFF(second,        @startdate,   @enddate) Second,
  DATEDIFF(millisecond,   @startdate,   @enddate) Millisecond,
  DATEDIFF(microsecond,   @startdate,   @enddate) Microsecond,
  DATEDIFF(nanosecond,    @startdate,   @enddate) Nanosecond;

Resultados:

Year        | 1
Quarter     | 1
Month       | 1
DOY         | 1
Day         | 1
Week        | 0
Hour        | 1
Minute      | 1
Second      | 1
Millisecond | 1
Microsecond | 1
Nanosecond  | 100

Incorrecto.

La mayoría de ellos son iguales, pero esta vez la semana devolvió 0 .

¿Eh?

Esto sucedió porque las fechas de entrada tienen el mismo calendario semana valores. Dio la casualidad de que las fechas elegidas para el ejemplo 2 tenían diferentes valores de semana calendario.

Para ser más específicos, el ejemplo 2 cruzó los límites de las partes de la semana que van de '2016-12-31' a '2017-01-01'. Esto se debe a que la última semana de 2016 finalizó el 2016-12-31 y la primera semana de 2017 comenzó el 2017-01-01 (domingo).

Pero en el ejemplo 3, la primera semana de 2018 en realidad comenzó en nuestra fecha de inicio de 2017-12-31 (domingo). Nuestra fecha de finalización, al ser el día siguiente, cayó dentro de la misma semana. Por lo tanto, no se cruzaron los límites de las partes de la semana.

Esto obviamente asume que el domingo es el primer día de cada semana. Resulta que el DATEDIFF() la función hace suponga que el domingo es el primer día de la semana. Incluso ignora su SET DATEFIRST configuración (esta configuración le permite especificar explícitamente qué día se considera el primer día de la semana). El razonamiento de Microsoft para ignorar SET DATEFIRST es que asegura el DATEDIFF() la función es determinista. Aquí hay una solución si esto es un problema para usted.

Entonces, en pocas palabras, sus resultados podrían parecer "incorrectos" para cualquier parte de fecha dependiendo de las fechas/horas. Tus resultados pueden verse muy mal cuando usas la parte de la semana. Y podrían verse aún más mal si usas un SET DATEFIRST valor distinto de 7 (para el domingo) y está esperando DATEDIFF() para honrar eso.

Pero los resultados no están mal, y no es un error. Es más como un "te pillé" para aquellos que no saben cómo funciona realmente la función.

Todos estos errores también se aplican a DATEDIFF_BIG() función. Funciona igual que DATEDIFF() con la excepción de que devuelve el resultado como bigint firmado (a diferencia de un int para DATEDIFF() ).

Ejemplo 4:los resultados dependen del tipo de datos

También podría obtener resultados inesperados debido al tipo de datos que utiliza para las fechas de entrada. Los resultados a menudo diferirán según el tipo de datos de las fechas de entrada. Pero no puedes culpar a DATEDIFF() para esto, ya que se debe únicamente a las capacidades y limitaciones de los distintos tipos de datos. No puede esperar obtener resultados de alta precisión con un valor de entrada de baja precisión.

Por ejemplo, siempre que la fecha de inicio o la fecha de finalización tengan un smalldatetime valor, los segundos y milisegundos siempre devolverán 0. Esto se debe a que smalldatetime el tipo de datos solo es exacto al minuto.

Esto es lo que sucede si cambiamos el ejemplo 2 para usar smalldatetime en lugar de datetime2 :

DECLARE @startdate smalldatetime = '2016-12-31 23:59:59', 
  @enddate smalldatetime = '2017-01-01 00:00:00';

SELECT DATEDIFF(year,     @startdate,   @enddate) Year,
  DATEDIFF(quarter,       @startdate,   @enddate) Quarter,
  DATEDIFF(month,         @startdate,   @enddate) Month,
  DATEDIFF(dayofyear,     @startdate,   @enddate) DOY,
  DATEDIFF(day,           @startdate,   @enddate) Day,
  DATEDIFF(week,          @startdate,   @enddate) Week,
  DATEDIFF(hour,          @startdate,   @enddate) Hour,
  DATEDIFF(minute,        @startdate,   @enddate) Minute,
  DATEDIFF(second,        @startdate,   @enddate) Second,
  DATEDIFF(millisecond,   @startdate,   @enddate) Millisecond,
  DATEDIFF(microsecond,   @startdate,   @enddate) Microsecond,
  DATEDIFF(nanosecond,    @startdate,   @enddate) Nanosecond;

Resultado:

Year        | 0
Quarter     | 0
Month       | 0
DOY         | 0
Day         | 0
Week        | 0
Hour        | 0
Minute      | 0
Second      | 0
Millisecond | 0
Microsecond | 0
Nanosecond  | 0

La razón por la que todos son cero es porque ambas fechas de entrada son en realidad idénticas:

DECLARE @startdate smalldatetime = '2016-12-31 23:59:59', 
  @enddate smalldatetime = '2017-01-01 00:00:00';
SELECT  
  @startdate 'Start Date',   
  @enddate 'End Date';

Resultado:

+---------------------+---------------------+
| Start Date          | End Date            |
|---------------------+---------------------|
| 2017-01-01 00:00:00 | 2017-01-01 00:00:00 |
+---------------------+---------------------+

Las limitaciones del smalldatetime El tipo de datos hizo que los segundos se redondearan, lo que luego provocó un flujo en efecto y todo se redondeó. Incluso si no termina con valores de entrada idénticos, aún podría obtener un resultado inesperado debido a que el tipo de datos no proporciona la precisión que necesita.