2009-07-09 3 views
35

Мне нужно рассчитать количество ПОЛНОГО месяца в SQL, т.е.Расчет количества полных месяцев между двумя датами в 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?

+5

2009-01-31 - 2009-02-28. Это 0 или 1 месяц? –

+0

это должно быть 0 – oscarkuo

ответ

40

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

Спасибо, сэр! –

+3

Да, я знаю, что этот ответ уже более 5 лет, но я наткнулся на него, когда гуглинг. Есть только одна проблема с этим, она падает при сравнении конечного дня месяца. FullMonthsSeparation ('2012-12-31', '2013-02-28') возвращает 1, а не 2. –

+0

Это поведение, о котором попросил ОП. – Barett

-1

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

-2

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

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

Declare @Start DateTime 
Declare @End DateTime 

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

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

Это не сработает для многих случаев, проверьте его на первом. –

1

Что ваше определение месяца? Технически месяц может составлять 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 
+1

Я думаю, что вопрос, который хочет ответить OP: «Сколько раз я могу увеличить« месяц »первой даты до того, как он пройдет второй?» (с соответствующей обработкой лет) –

+1

Тот факт, что ОП «не учитывает, что май имеет 31 день», показывает, что он * не * считал месяц 30 дней – chiccodoro

1

Try:

trunc(Months_Between(date2, date1)) 
+1

, что rdbms использует trunc(), а не 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)); 

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

+0

(MS-SQL 2014) На 2009-04-16 по 2009-05-15 это возвращает 1 вместо 0. На 2009-04-16 по 2009-05-16 это возвращает 2 вместо 1. За 2009 год -04-16 до 2009-06-16 это возвращает 3 вместо 2. Я удивлен, что существует такая разница между SQL 2000 и 2014 – brewmanz

3

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

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

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

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

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

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

Для MS SQL Server – MentalArrow

2

DateAdd функция может быть использована для компенсации в начале месяца. Если endDate имеет дневную часть, меньшую, чем startDate, она будет перенесена на предыдущий месяц, таким образом, fariff даст правильное количество месяцев.

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

Хотя этот код может ответить на вопрос, было бы лучше объяснять _how_, он решает проблему, не вводя других, а кто-то ее использует. Ответы только на код не полезны в долгосрочной перспективе. – JAL

0

Не нужно создавать функцию только для части @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; 
0

Этот ответ следует за форматом 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 за то, что они показали мне некоторые функции компонента в этом процессе, которые я сшил вместе. Я положительно оценил их в свое время на этом форуме.

Сначала постройте горизонт, который представляет собой линейный набор пересечений циклов Century, Decade, Year, Month, incremental by 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 точки даты в «гггг -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 

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

Final Output: Правильный ответ «11» теперь наш выход. И поэтому я надеюсь, что это поможет. Благодаря!

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

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

Оцените разницу с помощью 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] 
Смежные вопросы