Вычисление количества полных месяцев между двумя датами в SQL - PullRequest
47 голосов
/ 10 июля 2009

Мне нужно рассчитать число ПОЛНЫЙ месяц в SQL, т.е.

  • 2009-04-16 - 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?

Ответы [ 15 ]

48 голосов
/ 10 июля 2009

В оригинальном сообщении были некоторые ошибки ... поэтому я переписал и упаковал его как 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
6 голосов
/ 11 июня 2011
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 голосов
/ 11 ноября 2015

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

DATEDIFF(MONTH, DATEADD(DAY,-DAY(startDate)+1,startDate),DATEADD(DAY,-DAY(startDate)+1,endDate))
4 голосов
/ 10 августа 2010

Это только для ORACLE и не для SQL-сервера:

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 и выше.

3 голосов
/ 10 июля 2009

Какое у вас определение месяца? Технически месяц может составлять 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
0 голосов
/ 24 февраля 2019

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

объявлять @StartDate date = '2019-01-31' объявлять @EndDate date = '2019-02-28'

SELECT

DATEDIFF (MONTH, @StartDate, @EndDate) +

(

кейс

когда форматируется (@ StartDate, 'yyyy-MM')! = Format (@ EndDate, 'yyyy-MM') И DATEPART (ДЕНЬ, @ StartDate)> DATEPART (ДЕНЬ, @ EndDate) И DATEPART (ДЕНЬ, @ EndDate) = DATEPART (ДЕНЬ, EOMONTH (@EndDate)), затем 0

когда формат (@ StartDate, 'yyyy-MM')! = Format (@ EndDate, 'yyyy-MM') И DATEPART (ДЕНЬ, @ StartDate)> DATEPART (ДЕНЬ, @ EndDate) тогда -1

остальное 0

конец

)

как число месяцев

0 голосов
/ 16 января 2018

Я понимаю, что это старый пост, но я создал это интересное решение, которое, я думаю, легко реализовать с помощью оператора 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]
0 голосов
/ 02 мая 2017
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 голосов
/ 28 января 2017

Этот ответ соответствует формату T-SQL. Я рассматриваю эту проблему как одно из линейно-временного расстояния между двумя точками даты в формате datetime , называем их 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 (спасибо календарю майя за концепцию!). Небольшое спасибо: я благодарю других участников Stack Overflow за то, что они продемонстрировали мне некоторые функции компонентов в этом процессе, которые я собрал вместе. Я положительно оценил их в свое время на этом форуме.

Сначала создайте горизонт, который представляет собой линейный набор пересечений циклов Века, Десятилетия, Год, Месяц, с приращением по месяцам. Для этого используйте декартову функцию перекрестного соединения. (Думайте об этом как о создании ткани, из которой мы будем отрезать отрезок между двумя точками «гггг-мм» для измерения расстояния):

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 в формат «гггг-мм» (думайте о них как о точечных координатах на всей ткани). Сохраните также исходные datetime версии точек:

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

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

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

Сырье: Я называю это «необработанным расстоянием», потому что компонент месяца кардинального расстояния «гггг-мм» может быть слишком большим; Компоненты дневного цикла в месяце необходимо сравнить, чтобы увидеть, должно ли учитываться это значение за последний месяц. В частности, в этом примере необработанное выходное расстояние равно 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» теперь наш вывод. И так, я надеюсь, это поможет. Спасибо!

0 голосов
/ 28 сентября 2016

Нет необходимости создавать функцию только для части @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;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...