Подтвердить что ты не робот

Вычисление количества полных месяцев между двумя датами в SQL

Мне нужно рассчитать число FULL месяц в SQL, т.е.

  • 2009-04-16 to 2009-05-15 = > 0 полный месяц
  • 2009-04-16 по 2009-05-16 = > 1 полный месяц
  • 2009-04-16 по 2009-06-16 = > 2 полных месяца

Я попытался использовать DATEDIFF, т.е.

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

но вместо того, чтобы дать мне полные месяцы между двумя датами, это дает мне разницу в части месяца, т.е.

1

Кто-нибудь знает, как вычислить количество полных месяцев в SQL Server?

4b9b3361

Ответ 1

В исходном посте были некоторые ошибки... поэтому я переписал и упаковал его как 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

Ответ 2

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

Ответ 3

Функция dateadd может быть использована для смещения до начала месяца. Если у endDate часть дня меньше, чем startDate, он будет перенесен на предыдущий месяц, поэтому datediff даст правильное количество месяцев.

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

Ответ 4

Это только для ORACLE, а не для SQL-Server:

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

И за полный месяц:

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

Может использоваться в Oracle 8i и выше.

Ответ 5

Какое ваше определение месяца? Технически месяц может составлять 28,29,30 или 31 день в зависимости от месяца и високосного года.

Кажется, вы рассматриваете месяц на 30 дней, так как в вашем примере вы проигнорировали, что май имеет 31 день, так почему бы просто не сделать следующее?

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

Ответ 6

DATEDIFF() предназначен для возврата числовых границ, пересекающихся между двумя датами для указанного диапазона. Чтобы заставить его делать то, что вы хотите, вам нужно внести дополнительную корректировку в учетную запись, когда даты пересекают границу, но не заполняют полный интервал.

Ответ 7

Попробуйте:

trunc(Months_Between(date2, date1))

Ответ 8

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

Это отлично работает для меня в SQL SERVER 2000.

Ответ 9

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;   

Ответ 10

Не нужно создавать функцию только для части @result. Например:

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;

Ответ 11

Этот ответ следует за форматом T-SQL. Я рассматриваю эту проблему как одно из линейного расстояния между двумя точками даты в формате даты и времени, вызывая их Time1 и Time2; Time1 должен быть привязан к значению "старше во времени", которое вы имеете в виду (например, дата рождения или дата создания виджета или дата начала поездки), а Time2 следует выровнять со значением "более новое время" (например, дата моментального снимка или датой завершения виджета или датой завершения поездки).

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

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

Решение использует простые измерения, преобразования и вычисления последовательных пересечений нескольких циклов разной длины; здесь: Century, Decade, Year, Month, Day (Спасибо, майянский календарь за концепцию!). Быстрая благодарность: я благодарю других участников Qaru за то, что они показали мне некоторые функции компонента в этом процессе, которые я сшил вместе. Я положительно оценил их в свое время на этом форуме.

Сначала постройте горизонт, который представляет собой линейный набор пересечений циклов Century, Decade, Year, Month, поэтапно по месяцам. Используйте для этого крестовую декартову функцию. (Подумайте об этом как о создании ткани, из которой мы будем вырезать длину между двумя точками "yyyy-mm" для измерения расстояния):

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

Затем преобразуйте точки времени Time1 и Time2 в формат "yyyy-mm" (подумайте об этом как о точках пересечения координат на всей ткани). Сохраните исходные версии точек времени и времени:

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

Затем выберите порядковое расстояние единиц "yyyy-mm", меньше одного, чтобы перейти на расстояние до кардинала (например, вырезать кусок ткани из всей ткани в обозначенных точках вырезания и получить свое сырое измерение):

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

Необработанный выход: Я называю это "необработанным расстоянием", потому что месячная составляющая кардинального расстояния "yyyy-mm" может быть слишком большой; компоненты дневного цикла в течение месяца необходимо сравнить, чтобы увидеть, должно ли это значение в прошлом месяце рассчитываться. В этом примере, в частности, исходное расстояние вывода равно "12". Но это не так, как 12/14, до 12/15, так что прошло всего 11 полных месяцев - его просто один день застенчивый, истекающий через 12-й месяц. Поэтому мы должны ввести внутримесячный дневной цикл, чтобы получить окончательный ответ. Вставьте сравнение позиции "месяц, день", чтобы определить, является ли последний месяц точки даты номинальным, или нет:

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

Конечный результат: Правильный ответ "11" теперь является нашим выходом. И поэтому я надеюсь, что это поможет. Спасибо!

Ответ 12

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))

Ответ 13

Я понимаю, что это старый пост, но я создал это интересное решение, которое, я думаю, легко реализовать с помощью оператора CASE.

Оцените разницу с помощью DATEDIFF, а затем протестируйте месяцы до и после использования DATEADD, чтобы найти наилучшую дату. Это предполагает, что 31 января - 28 февраля - 1 месяц (потому что это так).

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]

Ответ 14

ПРОСТОЙ И ЛЕГКИЙ СПОСОБ, просто скопируйте и вставьте этот ПОЛНЫЙ код в MS SQL и выполните:

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


SELECT

DATEDIFF(MONTH, @StartDate, @EndDate)+

(

case 

when format(@StartDate,'yyyy-MM') != format(@EndDate,'yyyy-MM') AND DATEPART(DAY,@StartDate) > DATEPART(DAY,@EndDate) AND DATEPART(DAY,@EndDate) = DATEPART(DAY,EOMONTH(@EndDate)) then 0

when format(@StartDate,'yyyy-MM') != format(@EndDate,'yyyy-MM') AND DATEPART(DAY,@StartDate) > DATEPART(DAY,@EndDate)  then -1 

else 0 

end

) 

as NumberOfMonths

Ответ 15

Я googled через Интернет. И предложение, которое я нашел, - добавить +1 к концу.

Попробуйте сделать это следующим образом:

Declare @Start DateTime
Declare @End DateTime

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

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