How do I replace TO_CHAR with SELECT EXTRACT(YEAR FROM sysdate) -2 FROM DUAL? - to-char

My where clause is
WHERE ABC.HD_ID = XYZ.HD_ID AND NVL (TO_CHAR (XYZ.PLANNED, 'YYYY), TO_CHAR (XYZ.REAL, 'YYYY')) >= TO_CHAR (SYSDATE, 'YYYY') - 2);
which is throwing an error.
But I need to update with info from this statement
`SELECT EXTRACT(YEAR FROM sysdate)-2 FROM DUAL;`
What should the updated WHERE clause be?your text
I tried
`WHERE ABC.HD_ID = XYZ.HD_ID
AND NVL (TO_CHAR (XYZ.PLANNED, 'YYYY),
TO_CHAR (XYZ.REAL, 'YYYY')) >=
TO_NUMBER (TO_CHAR (YEAR FROM sysdate)-2 FROM DUAL);`
but still getting an error.

Related

How to find record which throws an error in a SELECT (Oracle)

I have a problematic SQL Sentence, when it runs
SELECT code, my_date, my_time
FROM my_table
WHERE to_date(to_char(my_date, 'YYYY-MM-DD') || ' ' || my_time, 'YYYY-MM-DD HH24:MI:SS') > sysdate - 5
I always get: ORA-01841: (full) year must be between -4713 and +9999, and not be 0
This is the definition of my table:
CREATE TABLE my_table (code NUMBER(10), my_date DATE, my_time VARCHAR2(8));
ALTER TABLE my_table ADD CONSTRAINT pk_my_table PRIMARY KEY (code);
CREATE INDEX i_my_table_001 ON my_table (my_date);
But If I add an extra restriction, I never get the error:
SELECT code, my_date, my_time
FROM my_table
WHERE my_date > trunc(sysdate - 5)
AND to_date(to_char(my_date, 'YYYY-MM-DD') || ' ' || my_time, 'YYYY-MM-DD HH24:MI:SS') > sysdate - 5
All validations are ok. Not invalid dates, not invalid times
Is there any way to find which is the 'offending' record?
Brute force is not an option because table has more than 25M records.
If you are on Oracle 12.2 or higher, you can use the VALIDATE_CONVERSION function to see which rows could be giving you an error.
Example Query
WITH
my_table (code, my_date, my_time)
AS
(SELECT 1, date '2020-01-01', '08:00:00' FROM DUAL
UNION ALL
SELECT 2, date '2020-01-31', '25:00:00' FROM DUAL)
SELECT *
FROM my_table
WHERE validate_conversion (to_char(my_date, 'YYYY-MM-DD') || ' ' || my_time AS DATE, 'YYYY-MM-DD HH24:MI:SS') = 0;
Result
CODE MY_DATE MY_TIME
_______ ______________ ___________
2 2020-01-31 25:00:00
Update
One update that may be a bit easier than cloning your database and upgrading is to create your own function to validate the date and call that from a test SQL statement like the on below. I'm not sure if defining a function within a common table expression is supported in 12.1, but if it is not, you can make it a standalone function and call that from your query.
WITH
FUNCTION validate_date (p_date DATE, p_time VARCHAR2)
RETURN NUMBER
AS
l_test_date DATE;
BEGIN
l_test_date :=
TO_DATE (TO_CHAR (p_date, 'YYYY-MM-DD') || ' ' || p_time, 'YYYY-MM-DD HH24:MI:SS');
RETURN 1;
EXCEPTION
WHEN OTHERS
THEN
RETURN 0;
END;
SELECT *
FROM (SELECT 1 AS code, DATE '2020-01-01' AS my_date, '08:00:00' AS my_time FROM DUAL
UNION ALL
SELECT 2, DATE '2020-01-31', '25:00:00' FROM DUAL) my_table
WHERE validate_date (my_date, my_time) = 0;

Oracle NVL function not allows second parameter as datetime

select nvl(trunc(null),trunc(sysdate)) from dual;
While executing above query i am getting following error
ORA-00932: inconsistent datatypes: expected NUMBER got DATE
look like when i take string or number instead of trunc(sysdate) it run fine.
From here:
The first parameter to NVL is determining the expected datatype of the
returned column, with the trunc function defaulting that to NUMBER
because it's parameter is NULL. The second parameter to NVL needs to
match that datatype which it doesn't because it is a date.
SQL> select nvl(trunc(sysdate), sysdate) as mydate from dual;
MYDATE
-------------------
26/05/2006 00:00:00
SQL> select nvl(trunc(null), sysdate) as mydate from dual;
select nvl(trunc(null), sysdate) as mydate from dual
*
ERROR at line 1:
ORA-00932: inconsistent datatypes: expected NUMBER got DATE
or you can do like this:
SELECT NVL(TO_DATE(TRUNC(NULL)),SYSDATE) FROM dual;
The second parameter must be of the same datatype as the first parameter. The trunc function is overloaded. Apparently when you pass it a null, it interprets the value as a number and returns a number. Any of the following works:
SELECT NVL (NULL, TRUNC (SYSDATE)) FROM DUAL;
SELECT NVL (TRUNC (TO_DATE (NULL)), TRUNC (SYSDATE)) FROM DUAL;
SELECT NVL (TRUNC (CAST (NULL AS DATE)), TRUNC (SYSDATE)) FROM DUAL;
Follow up:
If the variable that is being used is properly defined as a date, then you should not have a problem. As an example, the following runs without error:
DECLARE
FUNCTION f
RETURN DATE IS
BEGIN
RETURN NULL;
END f;
BEGIN
DBMS_OUTPUT.put_line (NVL (TRUNC (f), TRUNC (SYSDATE)));
END;
In order to get an answer that actually solves your problem, I would recommend editing the question to include code that actually demonstrates the problem.

PLSQL: random timestamp between 2 timestamps

I have a timestamp which represents the current timestamp:
now := CURRENT_TIMESTAMP;
tomorrow := now + INTERVAL '1' DAY;
Now I want to render a random timestamp between those 2 values,
I tried with DBMS_RANDOM but it doesn't work
Any Help?
This will generate a random timestamp between the current timestamp and sysdate+1:
SELECT TO_DATE(SYSDATE, 'MM/DD/YYYY HH24:MI:SS') +
dbms_random.value(0, TO_DATE(SYSDATE, 'MM/DD/YYYY HH24:MI:SS') -
TO_DATE(SYSDATE, 'MM/DD/YYYY HH24:MI:SS')+1)
FROM dual;
As PL/SQL:
DECLARE
l_ran_time TIMESTAMP;
BEGIN
SELECT SYSDATE +
dbms_random.value(0, SYSDATE -
SYSDATE+1)
INTO l_ran_time
FROM dual;
dbms_output.put_line(l_ran_time);
END;
/

SQL Developer - Using start and end date variables in where clause of my query

I have a query that I run everyday that requires a StartDate and EndDate value. The StartDate and EndDate used to be a manual input but I am trying to get away from that and calculate the StartDate and EndDate to be used in the query. I've developed code to capture the StartDate and EndDate in variables:
DECLARE
c_DateMask VARCHAR2(20) := 'DD-Mon-YYYY';
c_TimeMask VARCHAR2(20) := 'HH24:MI';
v_Month char(4) := 'Prev';
v_StartDate date;
v_EndDate date;
v_Environment char(7) := 'Prod';
BEGIN
if v_MONTH = 'Prev'
THEN
select TO_DATE ('01-' || TO_CHAR (ADD_MONTHS (SYSDATE, -1),'mon-yyyy')) into v_StartDate from dual;
select Last_day(TO_DATE('01-' || TO_CHAR (ADD_MONTHS (SYSDATE, -1),'mon-yyyy'))) into v_EndDate from dual;
ELSE
select TO_DATE ('01-' || TO_CHAR (ADD_MONTHS (SYSDATE, 0),'mon-yyyy')) into v_StartDate from dual;
CASE
WHEN v_Environment = 'Prod'
THEN
-- Production Environment --
select
to_char(sysdate, 'dd-Mon-yyyy ') ||
case
when to_char(sysdate, 'mi') between 00 and 20
then to_char(sysdate, 'hh24')-1||':58'||':00'
when to_char(sysdate, 'mi') between 21 and 40
then to_char(sysdate, ' hh24')||':18'||':00'
when to_char(sysdate, 'mi') between 41 and 60
then to_char(sysdate, ' hh24')||':38'||':00'
END
into v_EndDate from dual;
WHEN v_Environment = 'OldTest'
THEN
-- Test Environment --
select
to_char(sysdate, 'dd-Mon-yyyy ') ||
case
when to_char(sysdate, 'mi') between 10 and 30
then to_char(sysdate, 'hh24')||':08'||':00'
when to_char(sysdate, 'mi') between 31 and 50
then to_char(sysdate, ' hh24')||':28'||':00'
when to_char(sysdate, 'mi') between 51 and 60
then to_char(sysdate, ' hh24')||':48'||':00'
END
into v_EndDate from dual;
end case;
end if;
I then want to use the variables in my select statement below:
-----------------
/* KPI Figures */
-----------------
SELECT
SysDate as RunTime
, v_StartDate
, v_EndDate
, TTM_OFF_CONTRIBUTOR
, SUM(NVL(TTM_PER_OFF_FEE,0)) Fees
FROM [Table]
Where
TTM_PROCESSED_DATE >= v_StartDate
AND TTM_PROCESSED_DATE <= v_EndDate
group by SysDate, v_StartDate, v_EndDate, TTM_OFF_CONTRIBUTOR
END;
It all works up until when I try to use the variable values in the KPI Figures query. What am I missing?
Update:
Regarding Phil's answer: I tried but it didn't work and I get the following error:
PLS-00428: an INTO clause is expected in this SELECT statement.
I am sure I saw another response yesterday which is now gone relating to being able to assign multiple values to variables or something.
Is that what I need and how would I do that?
The variables v_StartDate and v_EndDate are only in scope within the PL/SQL block where they are declared. It looks like you are then trying to use them outside the block in a separate query. To do that you will need to create SQL Developer bind variables outside the PL/SQL block like this:
var v_start_date varchar2(11)
var v_end_date varchar2(11)
Then reference these as bind variables in both the PL/SQL block and the SQL query:
declare
...
begin
....
:v_start_date := '01-' || TO_CHAR (ADD_MONTHS (SYSDATE, -1), 'mon-yyyy');
:v_end_date := TO_CHAR(Last_day(TO_DATE('01-'
|| TO_CHAR (ADD_MONTHS (SYSDATE, -1),'mon-yyyy'))));
-- (NB No need to select from dual)
...
end;
SQL:
...
Where
TTM_PROCESSED_DATE >= TO_DATE(:v_StartDate)
AND TTM_PROCESSED_DATE <= TO_DATE(:v_EndDate)
Note that these variables cannot be declared with a type of DATE, so they need to be converted back to dates in the query (using the correct format mask).
I suggest you wrap the startdate/enddate in two functions (stored procedures)
create or replace function get_startdate(p_month in varchar2, p_env in varchar2)
return date
is
l_return date;
begin
... logic goes here ...
return l_return;
end;
Similar for enddate.
Then use those functions in your query:
SELECT
SysDate as RunTime
, get_startdate('Prev', 'Prod')
, get_enddate('Prev', 'Prod')
, TTM_OFF_CONTRIBUTOR
, SUM(NVL(TTM_PER_OFF_FEE,0)) Fees
FROM [Table]
Where
TTM_PROCESSED_DATE >= get_startdate('Prev', 'Prod')
AND TTM_PROCESSED_DATE <= get_enddate('Prev', 'Prod')
group by SysDate, get_startdate('Prev', 'Prod'), get_enddate('Prev', 'Prod'), TTM_OFF_CONTRIBUTOR
Sidenote: please consider a more concise version to determine first and last day of a month. You also can just assign a value to a variable without using SELECT INTO, e.g.
startdate := trunc(sysdate, 'MM'); -- first day of current month
enddate := last_day(trunc(sysdate, 'MM')); -- last day of current month
To calculate with hours, you could add/subtract a number of minutes, that's more readable than all the to_date/to_char conversions you do, e.g.
enddate := trunc(sysdate, 'HH24');
case
when to_char(sysdate, 'mi') between 00 and 20 then enddate := enddate - 2/(24*60);
when to_char(sysdate, 'mi') between 21 and 40 then enddate := enddate + 18/(24*60);
when to_char(sysdate, 'mi') between 41 and 60 then enddate := enddate + 38/(24*60);
end;
Good luck, Martin
I think the issue is with your query and aliases. Try
SELECT
SysDate as RunTime
, v_StartDate AS StartDate
, v_EndDate AS EndDate
, TTM_OFF_CONTRIBUTOR
, SUM(NVL(TTM_PER_OFF_FEE,0)) Fees
FROM [Table]
Where
TTM_PROCESSED_DATE >= v_StartDate
AND TTM_PROCESSED_DATE <= v_EndDate
group by RunTime, StartDate, EndDate, TTM_OFF_CONTRIBUTOR

Problem retrieving time from date field in oracle stored procedure

I'm having problems retrieving the time from a date field in a SP. If I run the query:
select TO_CHAR(HOR_HST_ATN,'YYYY/MM/DD HH24:MI:SS') AS HOR_HST_ATN FROM AAM0_DT_RSTCN ;
It returns the date and time ok.
But in the SP, the code:
SELECT FEC_DSD_ATN, FEC_HST_ATN, NOM_SEM_DSD_ATN, NOM_SEM_HST_ATN,
TO_DATE(HOR_DSD_ATN ,'DD/MM/YYYY HH24:MI:SS') as HOR_DSD_ATN,
TO_DATE(HOR_HST_ATN,'DD/MM/YYYY HH24:MI:SS') as HOR_HST_ATN
returns just the date ok but the time is 00:00
If I try the select in the SP with TO_CHAR as in:
SELECT FEC_DSD_ATN, FEC_HST_ATN, NOM_SEM_DSD_ATN, NOM_SEM_HST_ATN,
TO_CHAR(HOR_DSD_ATN ,'YYYY/MM/DD HH24:MI:SS') as HOR_DSD_ATN,
TO_CHAR(HOR_HST_ATN,'YYYY/MM/DD HH24:MI:SS') as HOR_HST_ATN
I get the error: SQLCODE: -6502 SQLERRM: ORA-06502: PL/SQL: error ...
Any clues ? BTW I must use a SP to retrieve this value.
Update: the column is type date. Here's the full SP:
FUNCTION FN_AAM_EV_RSTCN (
I_NUM_CONTRATO IN AAM0_DT_RSTCN.NUM_CONTRATO%TYPE,
I_COD_PFL IN AAM0_DT_RSTCN.COD_PFL%TYPE,
I_COD_APL_PFM IN AAM0_DT_RSTCN.COD_APL_PFM%TYPE,
I_COD_PTO IN AAM0_DT_RSTCN.COD_PTO%TYPE,
I_COD_FNC IN AAM0_DT_RSTCN.COD_FNC%TYPE
) RETURN BOOLEAN
AS
T_FEC_DSD_ATN AAM0_DT_RSTCN.FEC_DSD_ATN%TYPE;
T_HOR_DSD_ATN AAM0_DT_RSTCN.HOR_DSD_ATN%TYPE;
T_FEC_HST_ATN AAM0_DT_RSTCN.FEC_HST_ATN%TYPE;
T_HOR_HST_ATN AAM0_DT_RSTCN.HOR_HST_ATN%TYPE;
T_NOM_SEM_DSD_ATN AAM0_DT_RSTCN.NOM_SEM_DSD_ATN%TYPE;
T_NOM_SEM_HST_ATN AAM0_DT_RSTCN.NOM_SEM_HST_ATN%TYPE;
O_RESULTSET2 REST_REFCUR;
BEGIN
OPEN O_RESULTSET2 FOR
SELECT FEC_DSD_ATN, FEC_HST_ATN, NOM_SEM_DSD_ATN, NOM_SEM_HST_ATN,
TO_CHAR(HOR_DSD_ATN ,'DD/MM/YYYY HH24:MI:SS') as HOR_DSD_ATN,
TO_CHAR(HOR_HST_ATN,'DD/MM/YYYY HH24:MI:SS') as HOR_HST_ATN
FROM AAM0_DT_RSTCN
WHERE ROWNUM <=1
AND NUM_CONTRATO = NVL ( I_NUM_CONTRATO, NUM_CONTRATO )
AND COD_PFL = NVL ( I_COD_PFL, COD_PFL )
AND COD_APL_PFM = NVL ( I_COD_APL_PFM, COD_APL_PFM )
AND COD_PTO = NVL ( I_COD_PTO, COD_PTO )
AND COD_FNC = NVL ( I_COD_FNC, COD_FNC )
AND FLG_RCS = 'D'
AND COD_TPO_CDC = 'PC'
;
FETCH O_RESULTSET2 INTO T_FEC_DSD_ATN, T_FEC_HST_ATN, T_NOM_SEM_DSD_ATN, T_NOM_SEM_HST_ATN, T_HOR_DSD_ATN, T_HOR_HST_ATN ;
IF (O_RESULTSET2%NOTFOUND) THEN
RETURN TRUE;
ELSE
dbms_output.put_line('EVALUO RESTRICCIONES: T_FEC_DSD_ATN ' || T_FEC_DSD_ATN || ' T_FEC_HST_ATN : ' || T_FEC_HST_ATN || ' T_NOM_SEM_DSD_ATN: ' ||
T_NOM_SEM_DSD_ATN || ' T_NOM_SEM_HST_ATN: ' || T_NOM_SEM_HST_ATN || ' T_HOR_DSD_ATN: ' || TO_CHAR(T_HOR_DSD_ATN ,'DD/MM/YYYY HH24:MI:SS') ||
' T_HOR_HST_ATN: ' || TO_CHAR(T_HOR_HST_ATN ,'DD/MM/YYYY HH24:MI:SS') );
IF (NOT((T_FEC_DSD_ATN IS NULL) and (T_FEC_HST_ATN IS NULL)) ) THEN
IF ( NOT ((T_FEC_DSD_ATN <= SYSDATE) AND (T_FEC_HST_ATN >= SYSDATE ))) THEN
RETURN FALSE;
END IF ;
END IF ;
RETURN TRUE;
END IF ;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN TRUE;
WHEN OTHERS THEN
IF O_RESULTSET2%ISOPEN THEN
CLOSE O_RESULTSET2;
END IF;
DBMS_OUTPUT.PUT_LINE('ERROR en FN_AAM_EV_RSTCN : ');
DBMS_OUTPUT.PUT_LINE(' SQLCODE: ' || SQLCODE );
DBMS_OUTPUT.PUT_LINE(' SQLERRM: ' || SQLERRM );
ROLLBACK;
END FN_AAM_EV_RSTCN;
Your problem lies with the following code:
...
T_HOR_DSD_ATN AAM0_DT_RSTCN.HOR_DSD_ATN%TYPE;
T_HOR_HST_ATN AAM0_DT_RSTCN.HOR_HST_ATN%TYPE;
...
SELECT FEC_DSD_ATN, FEC_HST_ATN, NOM_SEM_DSD_ATN, NOM_SEM_HST_ATN,
TO_CHAR(HOR_DSD_ATN ,'DD/MM/YYYY HH24:MI:SS') as HOR_DSD_ATN,
TO_CHAR(HOR_HST_ATN,'DD/MM/YYYY HH24:MI:SS') as HOR_HST_ATN
FROM AAM0_DT_RSTCN
...
FETCH O_RESULTSET2 INTO T_FEC_DSD_ATN, T_FEC_HST_ATN, T_NOM_SEM_DSD_ATN,
T_NOM_SEM_HST_ATN, T_HOR_DSD_ATN, T_HOR_HST_ATN ;
Since the HOR_DSD_ATN and HOR_HST_ATN columns have the DATE data type, your local variables (T_HOR_DSD_ATN and T_HOR_HST_ATN) are also DATEs. However, in your SELECT you are converting these dates to strings with the TO_CHAR function. Therefore, your fetch is effectively doing this (I've made up some dates here):
T_HOR_DSD_ATN := '03/02/2010 09:33:30';
T_HOR_HST_ATN := '01/01/2010 12:30:00';
Since you are assigning a string to a date variable, Oracle must do an implicit TO_DATE - therefore it is using the session's NLS_DATE_FORMAT to convert them, which probably just gets the date portion (e.g. DD/MM/YYYY) and loses the time values.
To fix it, just remove the TO_CHAR()s from your SELECT statement - that way you will get dates + time values unmodified into your local variables.
Since the TO_DATE function takes a VARCHAR as argument, you wouldn't use this function on a DATE field. If you want to retrieve both the date and the time from a DATE field you would just SELECT the fields without functions:
SELECT FEC_DSD_ATN, FEC_HST_ATN, NOM_SEM_DSD_ATN, NOM_SEM_HST_ATN,
HOR_DSD_ATN, HOR_HST_ATN
...
update
The problem is that you are fetching TO_CHAR(HOR_DSD_ATN, 'DD/MM/YYYY HH24:MI:SS') into a DATE field. There is an implicit conversion that truncates your date. Use to_char on a DATE variable and use to_date on a CHAR variable.
My suggestion:
FUNCTION FN_AAM_EV_RSTCN(I_NUM_CONTRATO IN AAM0_DT_RSTCN.NUM_CONTRATO%TYPE,
I_COD_PFL IN AAM0_DT_RSTCN.COD_PFL%TYPE,
I_COD_APL_PFM IN AAM0_DT_RSTCN.COD_APL_PFM%TYPE,
I_COD_PTO IN AAM0_DT_RSTCN.COD_PTO%TYPE,
I_COD_FNC IN AAM0_DT_RSTCN.COD_FNC%TYPE)
RETURN BOOLEAN AS
BEGIN
FOR cc IN (SELECT FEC_DSD_ATN, FEC_HST_ATN, NOM_SEM_DSD_ATN,
NOM_SEM_HST_ATN, HOR_DSD_ATN, HOR_HST_ATN
FROM AAM0_DT_RSTCN
WHERE ROWNUM <= 1
AND NUM_CONTRATO = NVL(I_NUM_CONTRATO, NUM_CONTRATO)
AND COD_PFL = NVL(I_COD_PFL, COD_PFL)
AND COD_APL_PFM = NVL(I_COD_APL_PFM, COD_APL_PFM)
AND COD_PTO = NVL(I_COD_PTO, COD_PTO)
AND COD_FNC = NVL(I_COD_FNC, COD_FNC)
AND FLG_RCS = 'D'
AND COD_TPO_CDC = 'PC') LOOP
dbms_output.put_line(' T_HOR_DSD_ATN: ' ||
TO_CHAR(cc.HOR_DSD_ATN, 'DD/MM/YYYY HH24:MI:SS') ||
' T_HOR_HST_ATN: ' ||
TO_CHAR(cc.HOR_HST_ATN, 'DD/MM/YYYY HH24:MI:SS'));
-- your logic with return boolean
END LOOP;
-- no rows found
RETURN TRUE;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('ERROR en FN_AAM_EV_RSTCN : ');
DBMS_OUTPUT.PUT_LINE(' SQLCODE: ' || SQLCODE);
DBMS_OUTPUT.PUT_LINE(' SQLERRM: ' || SQLERRM);
RAISE;
END FN_AAM_EV_RSTCN;
If HOR_HST_ATN is a date column then TO_DATE(HOR_HST_ATN,'DD/MM/YYYY HH24:MI:SS') must be a typo. You probably mean TO_CHAR().
If the columns were populated by dates without a time element Oracle defaults the time to midnight...
SQL> create table t23 (d date)
2 /
Table created.
SQL> insert into t23 values (sysdate)
2 /
1 row created.
SQL> insert into t23 values (to_date('03-FEB-2010', 'DD-MON-YYYY'))
2 /
1 row created.
SQL> select to_char(d, 'DD/MM/YYYY HH24:MI:SS') as datetime from t23
2 /
DATETIME
-------------------
02/02/2010 17:59:51
03/02/2010 00:00:00
SQL>

Resources