2009-07-09 14 views
35

necesito para calcular el número de COMPLETO meses en SQL, es decirCalculando el número de meses completos entre dos fechas en SQL

  • 2009-04-16 a 2009-05-15 => 0 mes completo
  • 2009-04-16 a 2009-05-16 => 1 mes entero
  • 2009-04-16 a 2009-06-16 => 2 meses completos

he intentado utilizar DATEDIFF, es decir,

SELECT DATEDIFF(MONTH, '2009-04-16', '2009-05-15') 

pero en vez de darme meses completos entre los dos fecha, me da la diferencia de la parte correspondiente al mes, es decir

1 

Alguien sabe cómo calcular el número de meses completos en SQL Server?

+5

2009-01-31 a 2009-02-28. ¿Eso es 0 o 1 mes completo? –

+0

esto debería ser un 0 – oscarkuo

Respuesta

40

La publicación original tenía algunos errores ... así que la reescribí y empaqué como una UDF.

CREATE FUNCTION FullMonthsSeparation 
(
    @DateA DATETIME, 
    @DateB DATETIME 
) 
RETURNS INT 
AS 
BEGIN 
    DECLARE @Result INT 

    DECLARE @DateX DATETIME 
    DECLARE @DateY DATETIME 

    IF(@DateA < @DateB) 
    BEGIN 
     SET @DateX = @DateA 
     SET @DateY = @DateB 
    END 
    ELSE 
    BEGIN 
     SET @DateX = @DateB 
     SET @DateY = @DateA 
    END 

    SET @Result = (
        SELECT 
        CASE 
         WHEN DATEPART(DAY, @DateX) > DATEPART(DAY, @DateY) 
         THEN DATEDIFF(MONTH, @DateX, @DateY) - 1 
         ELSE DATEDIFF(MONTH, @DateX, @DateY) 
        END 
        ) 

    RETURN @Result 
END 
GO 

SELECT dbo.FullMonthsSeparation('2009-04-16', '2009-05-15') as MonthSep -- =0 
SELECT dbo.FullMonthsSeparation('2009-04-16', '2009-05-16') as MonthSep -- =1 
SELECT dbo.FullMonthsSeparation('2009-04-16', '2009-06-16') as MonthSep -- =2 
+0

¡Gracias, amable señor! –

+3

Sí, sé que esta respuesta ya tiene más de 5 años, pero la encontré cuando busqué en Google. Solo hay un problema con esto, se cae al comparar el día final de un mes. FullMonthsSeparation ('2012-12-31', '2013-02-28') devuelve 1, no 2. –

+0

Ese es el comportamiento que solicitó OP. – Barett

-1

DATEDIFF() está diseñado para devolver los límites numéricos cruzados entre las dos fechas para el lapso especificado. Para que haga lo que desea, necesita hacer un ajuste adicional para tener en cuenta cuando las fechas cruzan un límite pero no completan el lapso completo.

-2

Busqué en Internet. Y la sugerencia que encontré es agregar +1 al final.

Intenta hacerlo de esta manera:

Declare @Start DateTime 
Declare @End DateTime 

Set @Start = '11/1/07' 
Set @End = '2/29/08' 

Select DateDiff(Month, @Start, @End + 1) 
+0

Eso no funcionará para la mayoría de los casos, verifíquelo en su primera. –

1

¿Cuál es su definición de un mes? Técnicamente, un mes puede ser 28,29,30 o 31 días dependiendo del mes y los años bisiestos.

Parece que está considerando un mes para ser 30 días ya que en su ejemplo no hizo caso de que mayo tiene 31 días, entonces ¿por qué no simplemente hacer lo siguiente?

SELECT DATEDIFF(DAY, '2009-04-16', '2009-05-15')/30 
    , DATEDIFF(DAY, '2009-04-16', '2009-05-16')/30 
    , DATEDIFF(DAY, '2009-04-16', '2009-06-16')/30 
+1

Creo que la pregunta que el OP quiere responder es: "¿cuántas veces puedo incrementar el 'mes' de la primera fecha antes de que pase el segundo?" (con un manejo adecuado de los años) –

+1

el hecho de que el PO "no tenga en cuenta que May tiene 31 días" muestra que no * consideró * un mes para ser 30 días – chiccodoro

1

Probar:

trunc(Months_Between(date2, date1)) 
+1

¿qué rdbms usa trunc(), no sql-server? – Taryn

0
SELECT 12 * (YEAR(end_date) - YEAR(start_date)) + 
    ((MONTH(end_date) - MONTH(start_date))) + 
    SIGN(DAY(end_date)/DAY(start_date)); 

Esto funciona muy bien para mí en SQL SERVER 2000.

+0

(MS-SQL 2014) Para 2009-04-16 a 2009-05-15 esto devuelve 1 en lugar de 0. Para 2009-04-16 a 2009-05-16 esto devuelve 2 en lugar de 1. para 2009 -04-16 a 2009-06-16 esto devuelve 3 en lugar de 2. Me sorprende que haya una gran diferencia entre SQL 2000 y 2014 – brewmanz

3

Esto es para ORACLE solamente y no para SQL Server:

months_between(to_date ('2009/05/15', 'yyyy/mm/dd'), 
       to_date ('2009/04/16', 'yyyy/mm/dd')) 

A nd para el mes completo:

round(months_between(to_date ('2009/05/15', 'yyyy/mm/dd'), 
        to_date ('2009/04/16', 'yyyy/mm/dd'))) 

Se puede utilizar en Oracle 8i y superior.

3
select case when DATEPART(D,End_dATE) >=DATEPART(D,sTAR_dATE) 
THEN (case when DATEPART(M,End_dATE) = DATEPART(M,sTAR_dATE) AND DATEPART(YYYY,End_dATE) = DATEPART(YYYY,sTAR_dATE) 
     THEN 0 ELSE DATEDIFF(M,sTAR_dATE,End_dATE)END) 
ELSE DATEDIFF(M,sTAR_dATE,End_dATE)-1 END 
0
WITH 
-- Count how many months must be added to @StartDate to exceed @DueDate 
MONTHS_SINCE(n, [Month_hence], [IsFull], [RemainingDays]) AS ( 
SELECT 
    1 as n, 
    DATEADD(Day, -1, DATEADD(Month, 1, @StartDate)) AS Month_hence 
    ,CASE WHEN (DATEADD(Day, -1, DATEADD(Month, 1, @StartDate)) <= @LastDueDate) 
     THEN 1 
     ELSE 0 
    END AS [IsFull] 
    ,DATEDIFF(day, @StartDate, @LastDueDate) as [RemainingDays] 
UNION ALL 
SELECT 
    n+1, 
    --DateAdd(Month, 1, Month_hence) as Month_hence -- No, causes propagation of short month discounted days 
    DATEADD(Day, -1, DATEADD(Month, n+1, @StartDate)) as Month_hence 
    ,CASE WHEN (DATEADD(Day, -1, DATEADD(Month, n+1, @StartDate)) <= @LastDueDate) 
     THEN 1 
     ELSE 0  
    END AS [IsFull] 
    ,DATEDIFF(day, DATEADD(Day, -1, DATEADD(Month, n, @StartDate)), @LastDueDate) 
    FROM MONTHS_SINCE 
    WHERE Month_hence<(@LastDueDate --WHERE Period= 1 
    ) 
), --SELECT * FROM MONTHS_SINCE 
MONTH_TALLY (full_months_over_all_terms, months_over_all_terms, days_in_incomplete_month) AS ( 
SELECT 
    COALESCE((SELECT MAX(n) FROM MONTHS_SINCE WHERE isFull = 1),1) as full_months_over_all_terms, 
    (SELECT MAX(n) FROM MONTHS_SINCE) as months_over_all_terms, 
    COALESCE((SELECT [RemainingDays] FROM MONTHS_SINCE WHERE isFull = 0),0) as days_in_incomplete_month 
) SELECT * FROM MONTH_TALLY; 
+0

Para MS SQL Server – MentalArrow

2

La función dateadd se puede utilizar para compensar al comienzo del mes. Si endDate tiene una parte de día menos que startDate, se aplicará al mes anterior, por lo que la fecha de vencimiento dará la cantidad correcta de meses.

datediff(month, dateadd(day,-day(startDate)+1,start),dateadd(day,-day(startDate)+1,endDate)) 
+0

Si bien este código puede responder a la pregunta, sería mejor para explicar _how_ resuelve el problema sin introducir otros y _por qué_ usarlo. Las respuestas de solo código no son útiles a largo plazo. – JAL

0

No es necesario crear la función solo la parte @result. Por ejemplo:

Select Name, 
(SELECT CASE WHEN 
DATEPART(DAY, '2016-08-28') > DATEPART(DAY, '2016-09-29') 
THEN DATEDIFF(MONTH, '2016-08-28', '2016-09-29') - 1 
ELSE DATEDIFF(MONTH, '2016-08-28', '2016-09-29') END) as NumberOfMonths 

FROM 
tableExample; 
0

Esta respuesta sigue el formato T-SQL. Conceptualizo este problema como una de una distancia de tiempo lineal entre dos puntos de fecha en formato datetime, llámalos Time1 y Time2; Time1 debe estar alineado con el valor 'anterior en el tiempo' con el que está tratando (por ejemplo, una fecha de nacimiento o una fecha de creación de widget o una fecha de inicio de viaje) y Time2 debe estar alineado con el valor 'más nuevo en el tiempo' (por ejemplo, una fecha de instantánea o una fecha de finalización de widget o una fecha de punto de control de viaje).

DECLARE @Time1 DATETIME 
SET @Time1 = '12/14/2015' 

DECLARE @Time2 DATETIME 
SET @Time2 = '12/15/2016' 

La solución aprovecha medición simple, la conversión y los cálculos de las intersecciones de serie de ciclos múltiples de diferentes longitudes; aquí: Siglo, Década, Año, Mes, Día (¡gracias calendario maya por el concepto!). Una rápida nota de agradecimiento: agradezco a otros colaboradores de Stack Overflow por mostrarme algunas de las funciones de los componentes en este proceso que he cosido. He valorado positivamente estos en mi tiempo en este foro.

Primero, construya un horizonte que sea el conjunto lineal de las intersecciones de los ciclos Siglo, Decenio, Año, Mes, incrementales por mes. Use la función cartesiana de unión cruzada para esto. (Piense en esto como la creación de la tela de la que vamos a cortar una longitud entre dos puntos de 'AAAA-MM' con el fin de medir la distancia):

SELECT 
Linear_YearMonths = (centuries.century + decades.decade + years.[year] + months.[Month]), 
1 AS value 
INTO #linear_months 
FROM 
(SELECT '18' [century] UNION ALL 
SELECT '19' UNION ALL 
SELECT '20') centuries 
CROSS JOIN 
(SELECT '0' [decade] UNION ALL 
SELECT '1' UNION ALL 
SELECT '2' UNION ALL 
SELECT '3' UNION ALL 
SELECT '4' UNION ALL 
SELECT '5' UNION ALL 
SELECT '6' UNION ALL 
SELECT '7' UNION ALL 
SELECT '8' UNION ALL 
SELECT '9') decades 
CROSS JOIN 
(SELECT '1' [year] UNION ALL 
SELECT '2' UNION ALL 
SELECT '3' UNION ALL 
SELECT '4' UNION ALL 
SELECT '5' UNION ALL 
SELECT '6' UNION ALL 
SELECT '7' UNION ALL 
SELECT '8' UNION ALL 
SELECT '9' UNION ALL 
SELECT '0') years 
CROSS JOIN 
(SELECT '-01' [month] UNION ALL 
SELECT '-02' UNION ALL 
SELECT '-03' UNION ALL 
SELECT '-04' UNION ALL 
SELECT '-05' UNION ALL 
SELECT '-06' UNION ALL 
SELECT '-07' UNION ALL 
SELECT '-08' UNION ALL 
SELECT '-09' UNION ALL 
SELECT '-10' UNION ALL 
SELECT '-11' UNION ALL 
SELECT '-12') [months] 
ORDER BY 1 

A continuación, convertir sus puntos de fecha TIME1 y Time 2 en el 'aaaa -mm 'formato (Piense en estos como los puntos de corte coordinados en toda la tela). Conservar el original de fecha y hora versiones de los puntos así:

SELECT 
Time1 = @Time1, 
[YYYY-MM of Time1] = CASE 
WHEN LEFT(MONTH(@Time1),1) <> '1' OR MONTH(@Time1) = '1' 
    THEN (CAST(YEAR(@Time1) AS VARCHAR) + '-' + '0' + CAST(MONTH(@Time1) AS VARCHAR)) 
    ELSE (CAST(YEAR(@Time1) AS VARCHAR) + '-' + CAST(MONTH(@Time1) AS VARCHAR)) 
    END, 
Time2 = @Time2, 
[YYYY-MM of Time2] = CASE 
WHEN LEFT(MONTH(@Time2),1) <> '1' OR MONTH(@Time2) = '1' 
    THEN (CAST(YEAR(@Time2) AS VARCHAR) + '-' + '0' + CAST(MONTH(@Time2) AS VARCHAR)) 
    ELSE (CAST(YEAR(@Time2) AS VARCHAR) + '-' + CAST(MONTH(@Time2) AS VARCHAR)) 
    END 
INTO #datepoints 

A continuación, seleccione la distancia ordinal de unidades 'AAAA-MM', menos uno para convertir la distancia cardinal (es decir, cortar un trozo de paño de la la nada en los puntos de corte identificados y obtener su medición en bruto):

SELECT 
d.*, 
Months_Between = (SELECT (SUM(l.value) - 1) FROM #linear_months l 
      WHERE l.[Linear_YearMonths] BETWEEN d.[YYYY-MM of Time1] AND d.[YYYY-MM of Time2]) 
FROM #datepoints d 

Raw Output: yo llamo a esto una 'distancia prima' porque el componente mes de la 'aaaa-mm' distancia cardenal puede ser uno demasiados; los componentes del ciclo diurno dentro del mes deben compararse para ver si este valor del último mes debe contar. Específicamente en este ejemplo, la distancia de salida sin procesar es '12'. Pero esto es incorrecto ya que el 12/14 es antes del 12/15, por lo que solo han transcurrido 11 meses completos, es solo un día antes de que transcurra el 12 ° mes. Por lo tanto, tenemos que traer el ciclo del día intra-mes para llegar a una respuesta final. Inserte una comparación posición 'mes, día' entre la de determinar si el mes más reciente punto de la fecha cuenta nominalmente, o no:

SELECT 
d.*, 
Months_Between = (SELECT (SUM(l.value) - 1) FROM AZ_VBP.[MY].[edg_Linear_YearMonths] l 
      WHERE l.[Linear_YearMonths] BETWEEN d.[YYYY-MM of Time1] AND d.[YYYY-MM of Time2]) 
     + (CASE WHEN DAY(Time1) < DAY(Time2) 
       THEN -1 
       ELSE 0 
       END) 
FROM #datepoints d 

Final Output: La respuesta correcta del '11' es ahora nuestra producción. Y entonces, espero que esto ayude. ¡Gracias!

0
select CAST(DATEDIFF(MONTH, StartDate, EndDate) AS float) - 
    (DATEPART(dd,StartDate) - 1.0)/DATEDIFF(DAY, StartDate, DATEADD(MONTH, 1, StartDate)) + 
    (DATEPART(dd,EndDate)*1.0)/DATEDIFF(DAY, EndDate, DATEADD(MONTH, 1, EndDate)) 
0

me di cuenta que es una entrada antigua, pero creó esta solución interesante que creo que es fácil de implementar utilizando una instrucción CASE.

Estime la diferencia usando DATEDIFF, y luego pruebe los meses anteriores y posteriores a DATEADD para encontrar la mejor fecha. Esto supone que del 31 de enero al 28 de febrero es de 1 mes (porque lo es).

DECLARE @First date = '2015-08-31' 
DECLARE @Last date = '2016-02-28' 

SELECT 
    @First as [First], 
    @Last as [Last], 
    DateDiff(Month, @First, @Last) as [DateDiff Thinks], 
    CASE 
     WHEN DATEADD(Month, DATEDIFF(Month, @First, @Last) +1, @First) <= @Last Then DATEDIFF(Month, @First, @Last) +1 
     WHEN DATEADD(Month, DATEDIFF(Month, @First, @Last) , @First) <= @Last Then DATEDIFF(Month, @First, @Last) 
     WHEN DATEADD(Month, DATEDIFF(Month, @First, @Last) -1, @First) <= @Last Then DATEDIFF(Month, @First, @Last) -1 
    END as [Actual Months Apart] 
Cuestiones relacionadas