web-dev-qa-db-fra.com

Calculer le nombre de mois entiers entre deux dates en SQL

J'ai besoin de calculer le nombre deCOMPLETmois en SQL, c.-à-d.

  • 2009-04-16 au 2009-05-15 => 0 mois complet
  • 2009-04-16 à 2009-05-16 => 1 mois complet
  • 2009-04-16 à 2009-06-16 => 2 mois complets

J'ai essayé d'utiliser DATEDIFF, c'est-à-dire.

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

mais au lieu de me donner des mois entiers entre les deux dates, cela me donne la différence de la partie mois, c.-à-d.

1

quelqu'un sait comment calculer le nombre de mois complets dans SQL Server?

45
oscarkuo

Le message original contenait des bugs ... je l'ai donc réécrit et emballé sous forme de fichier 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
45
Joe Davis
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
5
Shankar

La fonction dateadd peut être utilisée pour aller au début du mois. Si endDate a une partie de jour inférieure à startDate, elle sera reportée au mois précédent, ainsi datiff donnera le nombre correct de mois.

DATEDIFF(MONTH, DATEADD(DAY,-DAY(startDate)+1,startDate),DATEADD(DAY,-DAY(startDate)+1,endDate))
4
Jason Brady

Ceci est pour Oracle seulement et pas pour SQL-Server:

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

Et pour un mois complet:

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

Peut être utilisé dans Oracle 8i et supérieur.

3
Stephan Schielke

Quelle est votre définition d'un mois? Techniquement, un mois peut être de 28,29,30 ou 31 jours, selon le mois et les années bissextiles.

Il semble que vous envisagiez un mois comme étant 30 jours puisque, dans votre exemple, vous avez ignoré que le mois de mai avait 31 jours, alors pourquoi ne pas simplement procéder comme suit?

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
2
MyItchyChin

Cette réponse suit le format T-SQL. Je conceptualise ce problème comme une distance linéaire temporelle entre deux points de date au format datetime, appelez-les Time1 et Time2; Time1 doit être aligné sur la valeur "plus ancien dans le temps" (par exemple, une date de naissance ou une date de création ou un début de voyage) et Time2 doit être aligné sur la valeur "plus récent dans le temps" (par exemple, une date instantanée). ou une date d'achèvement du widget ou une date atteinte par un point de contrôle de trajet). 

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

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

La solution s'appuie sur des mesures, des conversions et des calculs simples des intersections en série de multiples cycles de différentes longueurs. ici: Siècle, Décennie, Année, Mois, Jour (Merci au Calendrier Maya pour le concept!). Un petit mot de remerciement: je remercie les autres contributeurs de Stack Overflow de m'avoir montré certaines des fonctions composant ce processus que j'ai cousues ensemble. J'ai évalué positivement ces derniers dans mon temps sur ce forum.

Commencez par construire un horizon représentant l’ensemble linéaire des intersections des cycles Siècle, Décennie, Année, Mois, incrémental par mois. Utilisez la fonction cartésienne de jointure croisée pour cela. (Pensez à cela comme à la création du tissu à partir duquel nous allons couper une longueur entre deux points "aaaa-mm" afin de mesurer la distance):

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

Ensuite, convertissez vos points de date Time1 et Time2 au format "aaaa-mm" (considérez-les comme les points de coupe des coordonnées sur tout le tissu). Conservez également les versions originales datetime des points:

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

Ensuite, sélectionnez la distance ordinale des unités "aaaa-mm", moins une pour la convertir en distance cardinale (c.-à-d. Coupez un morceau de tissu de tout le tissu aux points de coupe identifiés et obtenez sa mesure brute):

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

Sortie brute: J'appelle cela une «distance brute», car la composante mois de la distance cardinale «aaaa-mm» peut en être une de trop; Les composantes du cycle journalier du mois doivent être comparées pour déterminer si cette dernière valeur doit compter. Dans cet exemple en particulier, la distance de sortie brute est «12». Mais cette erreur, car 12/14 est antérieure à 12/15, 11 mois complets sont donc écoulés - il ne reste qu'un jour avant le 12ème mois. Nous devons donc intégrer le cycle de jours intra-mensuel pour obtenir une réponse finale. Insérez une comparaison de position 'mois, jour' entre le pour déterminer si le dernier mois de la date compte de façon nominale ou non:

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

Résultat final: .__ La réponse correcte de «11» est maintenant notre résultat. Et alors, j'espère que cela aide. Merci!

0
E.Gannon

Il n'est pas nécessaire de créer la fonction uniquement la partie @result. Par exemple: 

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
Adrian

SIMPLE ET FACILE, il suffit de copier et coller ce code complet dans MS SQL et d’exécuter

declare @StartDate date = '2019-01-31' declare @EndDate date = '2019-02-28'

SÉLECTIONNER

DATEDIFF (MOIS, @StartDate, @EndDate) +

(

cas 

quand format (@ StartDate, 'aaaa-MM')! = format (@ EndDate, 'aaaa-MM') ET DATEPART (JOUR, @ StartDate)> DATEPART (JOUR, @ EndDate) ET DATEPART (JOUR, @ EndDate) = DATEPART (JOUR, EOMONTH (@EndDate)) puis 0

quand format (@ StartDate, 'aaaa-MM')! = format (@ EndDate, 'aaaa-MM') ET DATEPART (DAY, @ StartDate)> DATEPART (DAY, @ EndDate) puis -1 

sinon 0 

fin

comme NumberOfMonths

0
Shehan Silva

Essayer: 

trunc(Months_Between(date2, date1))
0
Prosserc
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
MentalArrow
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
Ren Yuzhi
SELECT 12 * (YEAR(end_date) - YEAR(start_date)) +
    ((MONTH(end_date) - MONTH(start_date))) +
    SIGN(DAY(end_date) / DAY(start_date));

Cela fonctionne bien pour moi sur SQL SERVER 2000.

0
Weksley

Je sais que c’est un vieux message, mais j’ai créé cette solution intéressante qui, à mon avis, est facile à mettre en œuvre à l’aide d’une déclaration CASE.

Estimez la différence à l'aide de DATEDIFF, puis testez les mois avant et après l'utilisation de DATEADD pour trouver la meilleure date. Cela suppose que le 31 janvier au 28 février soit 1 mois (parce que c'est le cas).

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]
0
kevro