Funzione ADD_MONTHS non restituisce la data corretta in Oracle
-
27-10-2019 - |
Domanda
Visualizza i risultati di seguito query:
>> SELECT ADD_MONTHS(TO_DATE('30-MAR-11','DD-MON-RR'),-4) FROM DUAL;
30-NOV-10
>> SELECT ADD_MONTHS(TO_DATE('30-NOV-10','DD-MON-RR'),4) FROM DUAL;
31-MAR-11
Come posso ottenere '30 -MAR-11' , quando l'aggiunta di 4 mesi in una certa data?
Si prega di aiuto.
Soluzione
C'è un'altra domanda qui su Oracle e Java
Si afferma che
Dal di riferimento Oracle su add_months http : //download-west.oracle.com/docs/cd/B19306_01/server.102/b14200/functions004.htm
Se la data è l'ultimo giorno del mese o se il mese risultante ha meno giorni rispetto alla componente giorno della data, quindi il risultato è l'ultimo giorno del mese risultante. In caso contrario, il risultato ha lo stesso componente di giorno come data.
Quindi immagino che devi controllare manualmente indicando giorno e termina giorno per cambiare il comportamento della funzione. O forse con l'aggiunta di giorni invece di mesi. (Ma non ho trovato una funzione add_day
nella ref)
Altri suggerimenti
Per risolvere il problema, mi potrebbe eventualmente utilizzare questo algoritmo:
- Calcola la data obiettivo
TargetDate1
utilizzandoADD_MONTHS
. -
In alternativa calcolare la data di destinazione
TargetDate2
in questo modo:1) si applicano
ADD_MONTHS
alla prima del mese della data di fonte;
2) aggiungere la differenza di giorni tra la fonte e l'inizio dello stesso mese. -
Selezionare il
LEAST
tra ilTargetDate1
eTargetDate2
.
Così, alla fine, la data obiettivo conterrà una componente diversi giorni se componente giorno della data di origine è maggiore del numero del giorno nel mese di destinazione. In questo caso la data obiettivo sarà l'ultimo giorno del mese corrispondente.
Non sono davvero sicuro di mia conoscenza della sintassi SQL di Oracle, ma in fondo l'implementazione potrebbe essere simile a questo:
SELECT
LEAST(
ADD_MONTHS(SourceDate, Months),
ADD_MONTHS(TRUNC(SourceDate, 'MONTH'), Months)
+ (SourceDate - TRUNC(SourceDate, 'MONTH'))
) AS TargetDate
FROM (
SELECT
TO_DATE('30-NOV-10', 'DD-MON-RR') AS SourceDate,
4 AS Months
FROM DUAL
)
Ecco un'illustrazione dettagliata di come funziona il metodo:
SourceDate = '30-NOV-10'
Months = 4
TargetDate1 = ADD_MONTHS('30-NOV-10', 4) = '31-MAR-11' /* unacceptable */
TargetDate2 = ADD_MONTHS('01-NOV-10', 4) + (30 - 1)
= '01-MAR-11' + 29 = '30-MAR-11' /* acceptable */
TargetDate = LEAST('31-MAR-11', '30-MAR-11') = '30-MAR-11'
E qui ci sono alcuni più esempi per dimostrare casi diversi:
SourceDate | Months | TargetDate1 | TargetDate2 | TargetDate
-----------+--------+-------------+-------------+-----------
29-NOV-10 | 4 | 29-MAR-11 | 29-MAR-11 | 29-MAR-11
30-MAR-11 | -4 | 30-NOV-10 | 30-NOV-10 | 30-NOV-10
31-MAR-11 | -4 | 30-NOV-10 | 01-DEC-10 | 30-NOV-10
30-NOV-10 | 3 | 28-FEB-11 | 02-MAR-11 | 28-FEB-11
È possibile utilizzare intervallo di aritmetica per ottenere il risultato che si desidera
SQL> select date '2011-03-30' - interval '4' month
2 from dual;
DATE'2011
---------
30-NOV-10
SQL> ed
Wrote file afiedt.buf
1 select date '2010-11-30' + interval '4' month
2* from dual
SQL> /
DATE'2010
---------
30-MAR-11
Essere consapevoli, tuttavia, che ci sono delle trappole per intervallo di aritmetica, se si sta lavorando con i giorni che non esistono in ogni mese
SQL> ed
Wrote file afiedt.buf
1 select date '2011-03-31' + interval '1' month
2* from dual
SQL> /
select date '2011-03-31' + interval '1' month
*
ERROR at line 1:
ORA-01839: date not valid for month specified
Che ne dite di qualcosa di simile a questo:
SELECT
LEAST(
ADD_MONTHS(TO_DATE('30-MAR-11','DD-MON-RR'),-4),
ADD_MONTHS(TO_DATE('30-MAR-11','DD-MON-RR')-1,-4)+1
)
FROM
DUAL
;
Risultato: 30-NOV-10
SELECT
LEAST(
ADD_MONTHS(TO_DATE('30-NOV-10','DD-MON-RR'),4),
ADD_MONTHS(TO_DATE('30-NOV-10','DD-MON-RR')-1,4)+1
)
FROM
DUAL
;
Risultato: 30-MAR-11
la funzione
add_months
restituisce una data più n mesi.
Dal 30 novembre è l'ultimo giorno del mese, l'aggiunta di 4 mesi si tradurrà in una data che è la fine di 4 mesi. Questo è il comportamento previsto. Se le date non sono destinate a cambiare, una soluzione è quello di sottrarre un giorno dopo la nuova data è stato restituito
SQL> SELECT ADD_MONTHS(TO_DATE('30-NOV-10','DD-MON-RR'),4) -1 from dual;
ADD_MONTH
---------
30-MAR-11
SELECT TO_DATE('30-NOV-10','DD-MON-RR') +
(
ADD_MONTHS(TRUNC(TO_DATE('30-NOV-10','DD-MON-RR'),'MM'),4) -
TRUNC(TO_DATE('30-NOV-10','DD-MON-RR'),'MM')
) RESULT
FROM DUAL;
Questa sezione in paranthesis:
ADD_MONTHS(TRUNC(TO_DATE('30-NOV-10','DD-MON-RR'),'MM'),4) - TRUNC(TO_DATE('30-NOV-10','DD-MON-RR'),'MM')
ti dà il numero di giorni tra la data di immesso e 4 mesi più tardi. Quindi, aggiungendo il numero di giorni fino alla data dato dà la data esatta dopo 4 mesi.
Rif: http://www.dba-oracle.com/t_test_data_date_generation_sql.htm
Soluzione semplice:
ADD_MONTHS(date - 1, x) + 1
Ecco il trucco:
select add_months(to_date('20160228', 'YYYYMMDD')-1, 1)+1 from dual;
Enjoy!
CREATE OR REPLACE FUNCTION My_Add_Month(
STARTDATE DATE,
MONTHS_TO_ADD NUMBER
)
RETURN DATE
IS
MY_ADD_MONTH_RESULT DATE;
BEGIN
SELECT ORACLES_ADD_MONTH_RESULT + NET_DAYS_TO_ADJUST INTO MY_ADD_MONTH_RESULT FROM
(
SELECT T.*,CASE WHEN SUBSTRACT_DAYS > ADD_DAYS THEN ADD_DAYS - SUBSTRACT_DAYS ELSE 0 END AS NET_DAYS_TO_ADJUST FROM
(
SELECT T.*,EXTRACT(DAY FROM ORACLES_ADD_MONTH_RESULT) AS SUBSTRACT_DAYS FROM
(
SELECT ADD_MONTHS(STARTDATE,MONTHS_TO_ADD) AS ORACLES_ADD_MONTH_RESULT,EXTRACT(DAY FROM STARTDATE) AS ADD_DAYS FROM DUAL
)T
)T
)T;
RETURN TRUNC(MY_ADD_MONTH_RESULT);
END My_Add_Month;
/
--test & verification of logic & function both
SELECT T.*,ORACLES_ADD_MONTH_RESULT + NET_DAYS_TO_ADJUST AS MY_ADD_MONTH_RESULT,
My_Add_Month(STARTDATE,MONTHS_TO_ADD) MY_ADD_MONTH_FUNCTION_RESULT
FROM
(
SELECT T.*,CASE WHEN SUBSTRACT_DAYS > ADD_DAYS THEN ADD_DAYS - SUBSTRACT_DAYS ELSE 0 END AS NET_DAYS_TO_ADJUST FROM
(
SELECT T.*,EXTRACT(DAY FROM ORACLES_ADD_MONTH_RESULT) AS SUBSTRACT_DAYS FROM
(
SELECT T.*,ADD_MONTHS(STARTDATE,MONTHS_TO_ADD) AS ORACLES_ADD_MONTH_RESULT,EXTRACT(DAY FROM STARTDATE) AS ADD_DAYS FROM
(
SELECT TO_DATE('28/02/2014','DD/MM/YYYY') AS STARTDATE, 1 AS MONTHS_TO_ADD FROM DUAL
)T
)T
)T
)T;
Query-risultato
STARTDATE 2014/2/28
MONTHS_TO_ADD 1
ORACLES_ADD_MONTH_RESULT 2014/3/31
ADD_DAYS 28
SUBSTRACT_DAYS 31
NET_DAYS_TO_ADJUST -3
MY_ADD_MONTH_RESULT 2014/3/28
MY_ADD_MONTH_FUNCTION_RESULT 2014/3/28