A diferença em meses entre as datas no MySQL

86

Estou tentando calcular o número de meses entre 2 campos de data e hora.

Existe uma maneira melhor do que obter o carimbo de data / hora Unix e dividir por 2 592 000 (segundos) e arredondar dentro do MySQL?

Darryl Hein
fonte

Respostas:

16

A função DATEDIFF pode fornecer o número de dias entre duas datas. O que é mais preciso, pois ... como você define um mês? (28, 29, 30 ou 31 dias?)

SoapBox
fonte
68
PERIODDIFF pode calcular o número de meses com precisão.
Max Caceres
10
"Mais preciso" é incrivelmente subjetivo quando se trata de datas. Se, por exemplo, você está em um negócio que tem ciclos mensais, a necessidade de saber quantos meses entre duas datas pode ser mais importante do que o número de dias, pelo motivo que você definiu acima. Quanto tempo dura um mês? Se preciso contar um mês, não posso simplesmente dividir por 30.
Thomas Paine
6
Esta não é a resposta correta; você deve marcar a resposta de Max correta, abaixo.
Graham Charles de
1
A resposta de Max não está correta se você quiser o número de meses inteiros, contabilizando corretamente os anos bissextos e o número diferente de dias em um mês. PERIODDIFFsimplesmente assume um valor AAAAMM, portanto, a menos que você mesmo considere os dias antes de passar os valores AAAAMM, tudo o que você está calculando é o número de meses, arredondado para o mês mais próximo se houver menos do que um número inteiro de meses. Veja a resposta de Zane abaixo.
rgvcorley
2
Por que há tantos votos positivos para o PERIODDIFFcomentário? Perioddiff não leva valores de dias, então você está calculando o número de meses arredondado para o mês mais próximo se houver menos do que um número inteiro de meses
rgvcorley
220

Diferença de mês entre quaisquer duas datas:

Estou surpreso que ainda não tenha sido mencionado:

Dê uma olhada na função TIMESTAMPDIFF () no MySQL.

O que isso permite que você faça é passar dois valores TIMESTAMPou DATETIME(ou mesmo DATEque o MySQL se converta automaticamente), bem como a unidade de tempo na qual deseja basear sua diferença.

Você pode especificar MONTHcomo unidade no primeiro parâmetro:

SELECT TIMESTAMPDIFF(MONTH, '2012-05-05', '2012-06-04')
-- Outputs: 0

SELECT TIMESTAMPDIFF(MONTH, '2012-05-05', '2012-06-05')
-- Outputs: 1

SELECT TIMESTAMPDIFF(MONTH, '2012-05-05', '2012-06-15')
-- Outputs: 1

SELECT TIMESTAMPDIFF(MONTH, '2012-05-05', '2012-12-16')
-- Outputs: 7

Basicamente, obtém o número de meses decorridos desde a primeira data na lista de parâmetros. Esta solução compensa automaticamente a quantidade variável de dias em cada mês (28,30,31), bem como leva em consideração os anos bissextos - você não precisa se preocupar com nada disso.


Diferença de mês com precisão:

É um pouco mais complicado se você quiser introduzir a precisão decimal no número de meses decorridos, mas aqui está como você pode fazer isso:

SELECT 
  TIMESTAMPDIFF(MONTH, startdate, enddate) +
  DATEDIFF(
    enddate,
    startdate + INTERVAL
      TIMESTAMPDIFF(MONTH, startdate, enddate)
    MONTH
  ) /
  DATEDIFF(
    startdate + INTERVAL
      TIMESTAMPDIFF(MONTH, startdate, enddate) + 1
    MONTH,
    startdate + INTERVAL
      TIMESTAMPDIFF(MONTH, startdate, enddate)
    MONTH
  )

Onde startdatee enddateestão seus parâmetros de data, seja de duas colunas de data em uma tabela ou como parâmetros de entrada de um script:

Exemplos:

With startdate = '2012-05-05' AND enddate = '2012-05-27':
-- Outputs: 0.7097

With startdate = '2012-05-05' AND enddate = '2012-06-13':
-- Outputs: 1.2667

With startdate = '2012-02-27' AND enddate = '2012-06-02':
-- Outputs: 3.1935
Zane Bien
fonte
8
Esta é a resposta correta, todos os votos positivos para o PERIODDIFFcomentário sobre a resposta selecionada são muito
errados
5
Obrigado, no meu caso eu precisava de datas inclusivas, então substituo todos "enddate" por "date_add (enddate, intervalo 1 dia)". Então, 01-03-2014 a 31-05-2014 dará 3,00 em vez de 2,97
Sven Tore
2
Muito obrigado ..... obrigado especial por "Mês de diferença com precisão:". Você é a estrela do mysql
Vidhi
1
Acordado. Essa é a resposta correta. Exatamente o que eu estava procurando! Obrigado.
Jon Vote
105

PERIOD_DIFF calcula meses entre duas datas.

Por exemplo, para calcular a diferença entre now () e uma coluna de tempo em sua_tabela:

select period_diff(date_format(now(), '%Y%m'), date_format(time, '%Y%m')) as months from your_table;
Max Caceres
fonte
1
Alguma ideia do que ele faz quando poderia ser 11 meses e 1 dia, então 12 meses, mas se você mudar para 30 dias, então ainda seria 11?
Darryl Hein
1
11 meses e 1 dia retornariam 11 meses com o exemplo acima. PERIOD_DIFF não tem sentido de 30 vs 31 dias meses.
Max Caceres
27

Eu uso também PERIODDIFF . Para obter o ano e o mês da data, uso a função EXTRACT :

  SELECT PERIOD_DIFF(EXTRACT(YEAR_MONTH FROM NOW()), EXTRACT(YEAR_MONTH FROM time)) AS months FROM your_table;
Smolla
fonte
2
Incrível! Ele usa 50% menos tempo que o DATE_FORMATmétodo, obrigado! +1
David Rodrigues
1
Aqui para um carimbo de dataSELECT PERIOD_DIFF(EXTRACT(YEAR_MONTH FROM NOW()), EXTRACT(YEAR_MONTH FROM FROM_UNIXTIME(time, "%Y%m%d"))) AS months FROM your_table;
Smolla
2
Isso está incorreto, PERIOD_DIFFnão leva valores de dia, então você está calculando o número de meses arredondado para o mês mais próximo se houver menos de um número inteiro de meses. Você deve editar sua resposta para deixar isso claro.
rgvcorley
12

Como muitas das respostas aqui mostram, a resposta 'certa' depende exatamente do que você precisa. No meu caso, preciso arredondar para o número inteiro mais próximo .

Considere estes exemplos: 1º de janeiro -> 31 de janeiro: São 0 meses inteiros e quase 1 mês de duração. 1 ° de janeiro -> 1 ° de fevereiro? É 1 mês inteiro e exatamente 1 mês de duração.

Para obter o número de meses inteiros (completos) , use:

SELECT TIMESTAMPDIFF(MONTH, '2018-01-01', '2018-01-31');  => 0
SELECT TIMESTAMPDIFF(MONTH, '2018-01-01', '2018-02-01');  => 1

Para obter uma duração arredondada em meses, você pode usar:

SELECT ROUND(TIMESTAMPDIFF(DAY, '2018-01-01', '2018-01-31')*12/365.24); => 1
SELECT ROUND(TIMESTAMPDIFF(DAY, '2018-01-01', '2018-01-31')*12/365.24); => 1

A precisão é de +/- 5 dias e para intervalos de mais de 1000 anos. A resposta de Zane é obviamente mais precisa, mas é muito prolixa para o meu gosto.

IanS
fonte
Não concordo ... SELECT TIMESTAMPDIFF (MÊS, '31/01/2018', '04/02/2018'); fornece 0 meses de intervalo ... SELECT TIMESTAMPDIFF (MONTH, '2018-01-31', '2018-03-01'); dá 1 ... se 5 dias é 0, 29 dias é 1?
Scott
@Scott, há um mês inteiro (chamado de fevereiro) entre janeiro e março. TIMESTAMPDIFF apenas fornece o número de meses completos entre as datas. Se você deseja que qualquer período de um mês seja igual a um mês, use a versão arredondada que forneci.
IanS
8

Do manual do MySQL:

PERIOD_DIFF (P1, P2)

Retorna o número de meses entre os períodos P1 e P2. P1 e P2 devem estar no formato YYMM ou YYYYMM. Observe que os argumentos de período P1 e P2 não são valores de data.

mysql> SELECT PERIOD_DIFF (200802,200703); -> 11

Portanto, pode ser possível fazer algo assim:

Select period_diff(concat(year(d1),if(month(d1)<10,'0',''),month(d1)), concat(year(d2),if(month(d2)<10,'0',''),month(d2))) as months from your_table;

Onde d1 e d2 são as expressões de data.

Tive que usar as instruções if () para ter certeza de que os meses eram um número de dois dígitos como 02 em vez de 2.

Vincent Ramdhanie
fonte
8

Prefiro assim, porque todo mundo vai entender claramente à primeira vista:

SELECT
    12 * (YEAR(to) - YEAR(from)) + (MONTH(to) - MONTH(from)) AS months
FROM
    tab;
Stanislav Basovník
fonte
Stanislav - este método é exatamente correto se você quiser saber os dados POR NÚMERO DO MÊS. Tenho alguns relatórios que mostram as vendas por mês (Jan / Fev / Mar etc.), mas há tantos dados (gráficos por trimestre / ano / anos anteriores etc.) que não consigo agrupar por nada. Preciso obter os dados brutos e percorrê-los - quando for 31/01, muitos dos cálculos colocam o "próximo mês" em março, quando nós, humanos, sabemos que é fevereiro. SE for 2/4, alguns cálculos dizem que 31/01 é "este mês", mas 28/01 era "mês passado"
Scott
6

Existe uma maneira melhor? sim. Não use carimbos de data / hora do MySQL. Além do fato de que eles ocupam 36 bytes, eles não são nada convenientes para trabalhar. Eu recomendaria usar a data juliana e os segundos a partir da meia-noite para todos os valores de data / hora. Eles podem ser combinados para formar um UnixDateTime. Se for armazenado em um DWORD (inteiro não assinado de 4 bytes), as datas até 2.106 podem ser armazenadas como segundos desde o período, 01/01/1970 DWORD max val = 4.294.967.295 - Um DWORD pode conter 136 anos de segundos

É muito bom trabalhar com datas julianas ao fazer cálculos de data. Valores UNIXDateTime são bons para trabalhar ao fazer cálculos de data / hora. Também não são bons de olhar, então eu uso os timestamps quando preciso de uma coluna que não irei fazer muitos cálculos com, mas eu quero uma indicação rápida.

Converter para Juliano e vice-versa pode ser feito muito rapidamente em um bom idioma. Usando ponteiros, baixei para cerca de 900 Clks (também é uma conversão de STRING para INTEGER, é claro)

Quando você entra em aplicativos sérios que usam informações de data / hora como, por exemplo, os mercados financeiros, as datas julianas são de fato.

Mike Trader
fonte
Onde você conseguiu a informação sobre o fato de eles ocuparem 36 bytes ? O requisito de armazenamento dos estados do manual do MySQL para a coluna TIMESTAMP é de 4 bytes . dev.mysql.com/doc/refman/5.1/en/storage-requirements.html
poncha
comece a planejar o bug Y2106 agora! ;-)
ErichBSchulz
5

A consulta será como:

select period_diff(date_format(now(),"%Y%m"),date_format(created,"%Y%m")) from customers where..

Fornece vários meses do calendário desde a data de criação criada em um registro do cliente, permitindo que o MySQL faça a seleção do mês internamente.

Shubham Verma
fonte
2
DROP FUNCTION IF EXISTS `calcula_edad` $$
CREATE DEFINER=`root`@`localhost` FUNCTION `calcula_edad`(pFecha1 date, pFecha2 date, pTipo char(1)) RETURNS int(11)
Begin

  Declare vMeses int;
  Declare vEdad int;

  Set vMeses = period_diff( date_format( pFecha1, '%Y%m' ), date_format( pFecha2, '%Y%m' ) ) ;

  /* Si el dia de la fecha1 es menor al dia de fecha2, restar 1 mes */
  if day(pFecha1) < day(pFecha2) then
    Set vMeses = VMeses - 1;
  end if;

  if pTipo='A' then
    Set vEdad = vMeses div 12 ;
  else
    Set vEdad = vMeses ;
  end if ;
  Return vEdad;
End

select calcula_edad(curdate(),born_date,'M') --  for number of months between 2 dates
Rama
fonte
2

Execute este código e ele criará uma função datedeifference que lhe dará a diferença no formato de data aaaa-mm-dd.

DELIMITER $$

CREATE FUNCTION datedifference(date1 DATE, date2 DATE) RETURNS DATE
NO SQL

BEGIN
    DECLARE dif DATE;
    IF DATEDIFF(date1, DATE(CONCAT(YEAR(date1),'-', MONTH(date1), '-', DAY(date2)))) < 0    THEN
                SET dif=DATE_FORMAT(
                                        CONCAT(
                                            PERIOD_DIFF(date_format(date1, '%y%m'),date_format(date2, '%y%m'))DIV 12 , 
                                            '-',
                                            PERIOD_DIFF(date_format(date1, '%y%m'),date_format(date2, '%y%m'))% 12 , 
                                            '-',
                                            DATEDIFF(date1, DATE(CONCAT(YEAR(date1),'-', MONTH(DATE_SUB(date1, INTERVAL 1 MONTH)), '-', DAY(date2))))),
                                        '%Y-%m-%d');
    ELSEIF DATEDIFF(date1, DATE(CONCAT(YEAR(date1),'-', MONTH(date1), '-', DAY(date2)))) < DAY(LAST_DAY(DATE_SUB(date1, INTERVAL 1 MONTH))) THEN
                SET dif=DATE_FORMAT(
                                        CONCAT(
                                            PERIOD_DIFF(date_format(date1, '%y%m'),date_format(date2, '%y%m'))DIV 12 , 
                                            '-',
                                            PERIOD_DIFF(date_format(date1, '%y%m'),date_format(date2, '%y%m'))% 12 , 
                                            '-',
                                            DATEDIFF(date1, DATE(CONCAT(YEAR(date1),'-', MONTH(date1), '-', DAY(date2))))),
                                        '%Y-%m-%d');
    ELSE
                SET dif=DATE_FORMAT(
                                        CONCAT(
                                            PERIOD_DIFF(date_format(date1, '%y%m'),date_format(date2, '%y%m'))DIV 12 , 
                                            '-',
                                            PERIOD_DIFF(date_format(date1, '%y%m'),date_format(date2, '%y%m'))% 12 , 
                                            '-',
                                            DATEDIFF(date1, DATE(CONCAT(YEAR(date1),'-', MONTH(date1), '-', DAY(date2))))),
                                        '%Y-%m-%d');
    END IF;

RETURN dif;
END $$
DELIMITER;
enor
fonte
1
Você acha que poderia limpar e comentar seu código um pouco para que fique um pouco mais compreensível? Obrigado
Darryl Hein
1

Isso depende de como você deseja que o número de meses seja definido. Responda a estas perguntas: 'Qual é a diferença em meses: 15 de fevereiro de 2008 - 12 de março de 2009'. É definido por um número claro de dias que depende dos anos bissextos - em que mês se trata, ou mesmo dia do mês anterior = 1 mês.

Um cálculo para dias :

15 de fevereiro -> 29 (ano bissexto) = 14 1º de março de 2008 + 365 = 1º de março de 2009. 1º de março -> 12 de março = 12 dias. 14 + 365 + 12 = 391 dias. Total = 391 dias / (média de dias no mês = 30) = 13,03333

Um cálculo de meses :

15 de fevereiro de 2008 - 15 de fevereiro de 2009 = 12 de 15 de fevereiro -> 12 de março = menos de 1 mês Total = 12 meses, ou 13 se 15 de fevereiro - 12 de março for considerado 'o mês anterior'

Klathzazt
fonte
1
SELECT * 
FROM emp_salaryrevise_view 
WHERE curr_year Between '2008' AND '2009' 
    AND MNTH Between '12' AND '1'
Darryl Hein
fonte
1

Eu precisava da diferença do mês com precisão. Embora a solução de Zane Bien esteja na direção certa, seu segundo e terceiro exemplos fornecem resultados imprecisos. Um dia em fevereiro dividido pelo número de dias em fevereiro não é igual a um dia em maio dividido pelo número de dias em maio. Portanto, o segundo exemplo deve gerar ((31-5 + 1) / 31 + 13/30 =) 1,3043 e o terceiro exemplo ((29-27 + 1) / 29 + 2/30 + 3 =) 3,1701.

Acabei com a seguinte consulta:

SELECT
    '2012-02-27' AS startdate,
    '2012-06-02' AS enddate,
    TIMESTAMPDIFF(DAY, (SELECT startdate), (SELECT enddate)) AS days,
    IF(MONTH((SELECT startdate)) = MONTH((SELECT enddate)), 0, (TIMESTAMPDIFF(DAY, (SELECT startdate), LAST_DAY((SELECT startdate)) + INTERVAL 1 DAY)) / DAY(LAST_DAY((SELECT startdate)))) AS period1,     
    TIMESTAMPDIFF(MONTH, LAST_DAY((SELECT startdate)) + INTERVAL 1 DAY, LAST_DAY((SELECT enddate))) AS period2,
    IF(MONTH((SELECT startdate)) = MONTH((SELECT enddate)), (SELECT days), DAY((SELECT enddate))) / DAY(LAST_DAY((SELECT enddate))) AS period3,
    (SELECT period1) + (SELECT period2) + (SELECT period3) AS months
Michel
fonte
1

Função PERIOD_DIFF ()

Uma das maneiras é MySQL PERIOD_DIFF () retorna a diferença entre dois períodos. Os pontos devem estar no mesmo formato, ou seja, AAAAM ou AAAM. Deve-se notar que os períodos não são valores de data.

Código:

SELECT PERIOD_DIFF(200905,200811);

insira a descrição da imagem aqui

Amitesh Bharti
fonte
0

Você pode obter anos, meses e dias desta forma:

SELECT 
username
,date_of_birth
,DATE_FORMAT(CURDATE(), '%Y') - DATE_FORMAT(date_of_birth, '%Y') - (DATE_FORMAT(CURDATE(), '00-%m-%d') < DATE_FORMAT(date_of_birth, '00-%m-%d')) AS years
,PERIOD_DIFF( DATE_FORMAT(CURDATE(), '%Y%m') , DATE_FORMAT(date_of_birth, '%Y%m') ) AS months
,DATEDIFF(CURDATE(),date_of_birth) AS days
FROM users
Artur Kędzior
fonte
0

Você também pode tentar isso:

select MONTH(NOW())-MONTH(table_date) as 'Total Month Difference' from table_name;

OU

select MONTH(Newer_date)-MONTH(Older_date) as 'Total Month Difference' from table_Name;
Shubham Verma
fonte
0

Resposta simples com data de início ins_frm e data de término como ins_to

SELECT convert(TIMESTAMPDIFF(year, ins_frm, ins_to),UNSIGNED) as yrs,
       mod(TIMESTAMPDIFF(MONTH, ins_frm, ins_to),12) mnths
FROM table_name

Apreciar :)))

Mo Kassab
fonte
0

Tente isto

SELECT YEAR(end_date)*12 + MONTH(end_date) - (YEAR(start_date)*12 + MONTH(start_date))
Maceo
fonte
Bem-vindo ao Stack Overflow! Certifique-se de verificar a data das perguntas e se as respostas existentes têm a mesma solução.
lehiester
-1

Esta consulta funcionou para mim :)

SELECT * FROM tbl_purchase_receipt
WHERE purchase_date BETWEEN '2008-09-09' AND '2009-09-09'

Ele simplesmente pega duas datas e recupera os valores entre elas.

Khaleel Rshid
fonte
resposta totalmente incorreta, ele quer uma contagem de meses
Shane Rowatt