web-dev-qa-db-fra.com

Obtenez la différence entre deux dates en mois et jours en sql

J'ai besoin d'obtenir la différence entre deux dates, disons que si la différence est de 84 jours, je devrais probablement avoir une sortie de 2 mois et 14 jours, le code que je viens de donner donne les totaux. Voici le code

SELECT Months_between(To_date('20120325', 'YYYYMMDD'),
       To_date('20120101', 'YYYYMMDD'))
       num_months,
       ( To_date('20120325', 'YYYYMMDD') - To_date('20120101', 'YYYYMMDD') )
       diff_in_days
FROM   dual; 

La sortie est:

NUM_MONTHS    DIFF_IN_DAYS
2.774193548       84

J'ai besoin par exemple que la sortie de cette requête soit de 2 mois et 14 jours au pire, sinon cela ne me dérange pas si je peux avoir les jours exacts après le chiffre des mois parce que ces jours ne sont pas vraiment 14 parce que tous les mois n'ont pas 30 jours.

14
select 
  dt1, dt2,
  trunc( months_between(dt2,dt1) ) mths, 
  dt2 - add_months( dt1, trunc(months_between(dt2,dt1)) ) days
from
(
    select date '2012-01-01' dt1, date '2012-03-25' dt2 from dual union all
    select date '2012-01-01' dt1, date '2013-01-01' dt2 from dual union all
    select date '2012-01-01' dt1, date '2012-01-01' dt2 from dual union all
    select date '2012-02-28' dt1, date '2012-03-01' dt2 from dual union all
    select date '2013-02-28' dt1, date '2013-03-01' dt2 from dual union all
    select date '2013-02-28' dt1, date '2013-04-01' dt2 from dual union all
    select trunc(sysdate-1)  dt1, sysdate               from dual
) sample_data

Résultats:

|                        DT1 |                       DT2 | MTHS |     DAYS |
----------------------------------------------------------------------------
|  January, 01 2012 00:00:00 |   March, 25 2012 00:00:00 |    2 |       24 |
|  January, 01 2012 00:00:00 | January, 01 2013 00:00:00 |   12 |        0 |
|  January, 01 2012 00:00:00 | January, 01 2012 00:00:00 |    0 |        0 |
| February, 28 2012 00:00:00 |   March, 01 2012 00:00:00 |    0 |        2 |
| February, 28 2013 00:00:00 |   March, 01 2013 00:00:00 |    0 |        1 |
| February, 28 2013 00:00:00 |   April, 01 2013 00:00:00 |    1 |        1 |
|   August, 14 2013 00:00:00 |  August, 15 2013 05:47:26 |    0 | 1.241273 |

Lien pour tester: SQLFiddle

19
jen

Mis à jour pour l'exactitude. Répondu à l'origine par @jen.

with DATES as (
   select TO_DATE('20120101', 'YYYYMMDD') as Date1,
          TO_DATE('20120325', 'YYYYMMDD') as Date2
   from DUAL union all
   select TO_DATE('20120101', 'YYYYMMDD') as Date1,
          TO_DATE('20130101', 'YYYYMMDD') as Date2
   from DUAL union all
   select TO_DATE('20120101', 'YYYYMMDD') as Date1,
          TO_DATE('20120101', 'YYYYMMDD') as Date2
   from DUAL union all
   select TO_DATE('20130228', 'YYYYMMDD') as Date1,
          TO_DATE('20130301', 'YYYYMMDD') as Date2
   from DUAL union all
   select TO_DATE('20130228', 'YYYYMMDD') as Date1,
          TO_DATE('20130401', 'YYYYMMDD') as Date2
   from DUAL
), MONTHS_BTW as (
   select Date1, Date2,
          MONTHS_BETWEEN(Date2, Date1) as NumOfMonths
   from DATES
)
select TO_CHAR(Date1, 'MON DD YYYY') as Date_1,
       TO_CHAR(Date2, 'MON DD YYYY') as Date_2,
       NumOfMonths as Num_Of_Months,
       TRUNC(NumOfMonths) as "Month(s)",
       ADD_MONTHS(Date2, - TRUNC(NumOfMonths)) - Date1 as "Day(s)"
from MONTHS_BTW;

SQLFiddle Démo:

 + -------------- + -------------- + -------------- --- + ----------- + -------- + 
 | DATE_1 | DATE_2 | NUM_OF_MONTHS | MOIS (S) | JOUR (S) | 
 + -------------- + -------------- + --------- -------- + ----------- + -------- + 
 | 01 JAN 2012 | MAR 25 2012 | 2.774193548387 | 2 | 24 | 
 | JAN 01 2012 | JAN 01 2013 | 12 | 12 | 0 | 
 | JAN 01 2012 | JAN 01 2012 | 0 | 0 | 0 | 
 | 28 FÉVRIER 2013 | MAR 01 2013 | 0,129032258065 | 0 | 1 | 
 | 28 FÉVRIER 2013 | AVR 01 2013 | 1.129032258065 | 1 | 1 | 
 + -------------- + -------------- + ------ ----------- + ----------- + -------- + 

Remarquez, comment pour les deux dernières dates, Oracle rapporte incorrectement la partie décimale des mois (qui donne les jours). 0.1290 correspond exactement à 4 jours avec Oracle considérant 31 jours dans un mois (pour mars et avril).

2
Ravi Thapliyal

Je pense que votre question n'est pas assez bien définie, pour la raison suivante.

Les réponses reposant sur mois_entre doivent traiter le problème suivant: que la fonction signale exactement un mois entre 2013-02-28 et 2013-03-31, et entre 2013-01-28 et 2013-02-28, et entre 2013- 01-31 et 2013-02-28 (je soupçonne que certains répondeurs n'ont pas utilisé ces fonctions dans la pratique, ou vont maintenant devoir revoir un code de production!)

Il s'agit d'un comportement documenté, dans lequel les dates qui sont à la fois les dernières de leurs mois respectifs ou qui tombent le même jour du mois sont considérées comme un nombre entier de mois d'intervalle.

Ainsi, vous obtenez le même résultat de "1" lorsque vous comparez le 2013-02-28 avec 2013-01-28 ou avec 2013-01-31, mais en le comparant avec 2013-01-29 ou 2013-01-30 donne 0,967741935484 et 0,935483870968 respectivement - de sorte qu'à mesure qu'une date s'approche de l'autre, la différence signalée par cette fonction peut augmenter.

Si ce n'est pas une situation acceptable, vous devrez écrire une fonction plus complexe, ou simplement vous fier à un calcul qui suppose 30 jours (par exemple) par mois. Dans ce dernier cas, comment allez-vous gérer le 2013-02-28 et 2013-03-31?

2
David Aldridge

Ici, je fais juste la différence entre aujourd'hui et un CREATED_DATEDATE champ dans une table, qui est évidemment une date dans le passé:

SELECT  
((FLOOR(ABS(MONTHS_BETWEEN(CREATED_DATE, SYSDATE))) / 12) * 12) || ' months, '  AS MONTHS,
-- we take total days - years(as days) - months(as days) to get remaining days
FLOOR((SYSDATE - CREATED_DATE) -      -- total days
(FLOOR((SYSDATE - CREATED_DATE)/365)*12)*(365/12) -      -- years, as days
-- this is total months - years (as months), to get number of months, 
-- then multiplied by 30.416667 to get months as days (and remove it from total days)
FLOOR(((SYSDATE - CREATED_DATE)/365)*12 - (FLOOR((SYSDATE - CREATED_DATE)/365)*12)) * (365/12))
|| ' days ' AS DAYS 
FROM MyTable

J'utilise (365/12) ou 30.416667 comme facteur de conversion, car j'utilise le nombre total de jours et je supprime les années et les mois (en jours) pour obtenir le nombre de jours restant. C'était assez bon pour moi, de toute façon.

1
vapcguy

c'est ce que vous avez dit?

select trunc(months_between(To_date('20120325', 'YYYYMMDD'),to_date('20120101','YYYYMMDD'))) months,
             round(To_date('20120325', 'YYYYMMDD')-add_months(to_date('20120101','YYYYMMDD'),
                           trunc(months_between(To_date('20120325', 'YYYYMMDD'),to_date('20120101','YYYYMMDD'))))) days
        from dual;
1
planben

La solution que je poste considérera un mois avec 30 jours

  select CONCAT (CONCAT (num_months,' MONTHS '), CONCAT ((days-(num_months)*30),' DAYS '))
  from ( 
  SELECT floor(Months_between(To_date('20120325', 'YYYYMMDD'),
   To_date('20120101', 'YYYYMMDD')))
   num_months,
   ( To_date('20120325', 'YYYYMMDD') - To_date('20120101', 'YYYYMMDD') )
   days
  FROM   dual);
0
Harshit