Casting Varchar2 to interval - oracle

I have a configuration table such that I want to delete a row if the record_tm is over a certain configurable time (config_recordtrim_hrs). In other words, the variable recordtrim_config comes from another table where somebody writes a value (e.g. 24 for 24 hours) after which a record will be deleted.
DECLARE
recordtrim_flag configs_table.value%type;
recordtrim_config configs_table.value%type;
recordtrim_interval interval;
BEGIN
SELECT configs_table.value INTO recordtrim_flag FROM configs_table WHERE configs_table.name = 'enable_recordtrim';
SELECT configs_table.value INTO recordtrim_config FROM configs_table WHERE configs_table.name = 'config_recordtrim_hrs';
SELECT CAST (recordtrim_config AS interval) INTO recordtrim_interval;
IF (recordtrim_flag = 'T') THEN
DELETE FROM main_records WHERE record_tm + recordtrim_interval < current_timestamp;
END IF;
END;
This code throws an error at the line where I try to cast recordtrim_config as an interval. How do I fix this?
EDIT: My error:
Error report: ORA-06550: line 8, column 40: PL/SQL: ORA-30089: missing
or invalid ORA-06550: line 8, column 1: PL/SQL: SQL
Statement ignored
EDIT 2: New Code:
DECLARE
recordtrim_flag configs_table.value%type;
recordtrim_config configs_table.value%type;
recordtrim_interval interval;
BEGIN
SELECT configs_table.value INTO recordtrim_flag FROM configs_table WHERE configs_table.name = 'enable_recordtrim';
SELECT configs_table.value INTO recordtrim_config FROM configs_table WHERE configs_table.name = 'config_recordtrim_hrs';
recordtrim_interval := NUMTODSINTERVAL (recordtrim_config, 'HOUR');
IF (recordtrim_flag = 'T') THEN
DELETE FROM main_records WHERE record_tm + recordtrim_interval < current_timestamp;
END IF;
END;
New Error at line 11, recordtrim_interval is an invalid identifier

The immediate cause of the error is that you've got just interval instead of the type, i.e. interval day to second. You also have to select from something - which can be from dual here, though a straight assignment is cleaner.
You can't cast it directly if it's a single number. Instead you can use the numtodsinterval function:
recordtrim_interval := NUMTODSINTERVAL (recordtrim_config, 'HOUR');
e.g.
set serveroutput on
declare
recordtrim_config varchar2(4) := '24';
recordtrim_interval interval day to second;
begin
recordtrim_interval := NUMTODSINTERVAL (recordtrim_config, 'HOUR');
dbms_output.put_line(recordtrim_interval);
end;
/
anonymous block completed
+01 00:00:00.000000
Or select that straight into the variable from the table.
If you need more complicated values, not a simple value that represents a number of hours - and if that was the case you'd store it as a number I assume - then if you store it in a suitable format, you can cast it, or use the to_dsinterval function instead; any of these give the same result:
declare
recordtrim_config varchar2(10) := '0 2:30:00';
recordtrim_interval interval day to second;
begin
recordtrim_interval := TO_DSINTERVAL (recordtrim_config);
dbms_output.put_line(recordtrim_interval);
SELECT CAST (recordtrim_config AS interval day to second)
INTO recordtrim_interval from dual;
dbms_output.put_line(recordtrim_interval);
recordtrim_interval := CAST (recordtrim_config AS interval day to second);
dbms_output.put_line(recordtrim_interval);
end;
/
anonymous block completed
+00 02:30:00.000000
+00 02:30:00.000000
+00 02:30:00.000000
In your edited question, the ORA-00904: "RECORDTRIM_INTERVAL": invalid identifier is only the last error in the list. Look back a bit and you'll see:
ORA-06550: line 4, column 21:
PLS-00201: identifier 'INTERVAL' must be declared
... followed by some other errors where you try to use the variable. Your declaration of the variable has to be:
recordtrim_interval interval day to second;
There is no 'interval' data type; they are interval year to month and interval day to second.

Related

Oracle SQL to display results from dividing one variable with another

I need to calculate the YTD weekly average for the GROSS_AMOUNT
In the below script, the first part of the code gets the number of weeks. The second part of the code gets the YTD sum for that same time period
How do I write the SQL so I can retrieve the actual value of YtdTotal/TotalWeeks?
SELECT YtdTotal/TotalWeeks FROM DUAL; does not work
-- STORES NUMBER OF WEEKS SINCE BEGINING OF YEAR INTO THE VARIABLE, TotalWeeks
DECLARE
TotalWeeks NUMBER;
BEGIN
SELECT to_number(to_char(sysdate, 'WW')) - to_number(to_char(trunc(sysdate, 'year'),'WW'))
INTO TotalWeeks
FROM DUAL
-- (retrieves number of weeks since beginning of year)
END
-- STORES THE SUM OF GROSS)AMOUNT FOR THE WEEKS CALCULATED ABOVE - INTO THE
DECLARE
VARIABLE, YtdTotal
BEGIN
SELECT SUM(GROSS_AMOUNT)
INTO YtdTotal
FROM PARENTS
WHERE process_date BETWEEN
(next_day(TRUNC(sysdate, 'year'),'SUN'))
AND
(next_day(TRUNC(sysdate),'SAT')-7);
END;
Don't use two separate anonymous PL/SQL blocks - combine them into one!
If you want to return the result, then - instead of an anonymous PL/SQL block - create a function.
CREATE OR REPLACE FUNCTION f_test
return NUMBER
IS
totalweeks NUMBER;
ytdtotal NUMBER;
result NUMBER;
BEGIN
-- STORES NUMBER OF WEEKS SINCE BEGINING OF YEAR INTO THE VARIABLE, TotalWeeks
SELECT TO_NUMBER (TO_CHAR (SYSDATE, 'WW'))
- TO_NUMBER (TO_CHAR (TRUNC (SYSDATE, 'year'), 'WW'))
INTO totalweeks
FROM DUAL;
-- (retrieves number of weeks since beginning of year)
-- STORES THE SUM OF GROSS)AMOUNT FOR THE WEEKS CALCULATED ABOVE - INTO THE
SELECT SUM (gross_amount)
INTO ytdtotal
FROM parents
WHERE process_date BETWEEN (NEXT_DAY (TRUNC (SYSDATE, 'year'), 'SUN'))
AND (NEXT_DAY (TRUNC (SYSDATE), 'SAT') - 7);
-- the final result
result := ytdtogal / totalweeks;
RETURN result;
END;
/
Use it as
select f_test from dual;

Get resultset from a declare block

I've managed to incorporate variables into my pl/sql script and I get the correct result but I cannot figure out how to get it back in the same format as if I executed a plain select. I only see examples that use dbms_output.put_line which outputs text. How do I get an ordinary table out of this:
declare
monday date;
sunday date;
c sys_refcursor;
type t is table of MY_TABLE%rowtype;
r t;
begin
monday := (sysdate - 7 - to_char(sysdate, 'd') + 2);
sunday := (sysdate - 7 + to_char(sysdate, 'd'));
DBMS_OUTPUT.PUT_LINE(monday);
DBMS_OUTPUT.PUT_LINE(sunday);
select
*
bulk collect into
r
from
MY_TABLE
where
"Created On" >= monday and
"Created On" <= sunday
fetch next 10 rows only;
DBMS_OUTPUT.PUT_LINE(r.COUNT); -- <-- = 10
-- this might work but doesn't yet...
open c for select * from r;
DBMS_SQL.RETURN_RESULT(c);
close c;
end;
I found a way that it might be possible to use a cursor with DBMS_SQL.RETURN_RESULT but I still have issues with the r, it says ORA-00942 that the table name is invalid.

cannot get year-month interval

I am learning PL-SQL and the exercise is to find the year-month interval difference between two dates. I wrote the following code:
DECLARE
t1 TIMESTAMP (2) WITH TIME ZONE := to_timestamp_tz('2019-01-21 21:05:53.46 +02:00',
'YYYY-MM-DD HH24:MI:SS.FF TZH:TZM');
t3 TIMESTAMP WITH TIME ZONE := to_timestamp_tz('2020-01-21 21:05:53.46 +02:00',
'YYYY-MM-DD HH24:MI:SS.FF TZH:TZM');
ym INTERVAL YEAR(2) to MONTH;
BEGIN
-- ym := '10-2';
ym := t3-t1;
DBMS_OUTPUT.PUT_LINE(ym);
END;
I would expect the ym variable to give '01-0' (1 year difference), but I get an error:
Error report -
ORA-06550: line 9, column 9:
PLS-00382: expression is of wrong type
ORA-06550: line 9, column 3:
PL/SQL: Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
I am really confused why this is happening, I tried changing the precision of the YEAR(_), but that doesn't help.
If ym is of datatype INTERVAL DAY(2) to SECOND(2), I get correct result. If I replace ym to ym := '10-2'; it also works. But with ym INTERVAL YEAR(2) to MONTH it is not working :(
I found in the psoug.org examples that you could use this syntax:
ym := (t3-t1) year to month;
dbfiddle demo
t3-t1 does not work as the resulting value is of the INTERVAL DAY TO SECOND data type and there is no implicit cast to INTERVAL YEAR TO MONTH.
Instead, you can use NUMTOYMINTERVAL( MONTHS_BETWEEN( t3, t1 ), 'MONTH' ); to dynamically create the correct data type:
DECLARE
t1 TIMESTAMP (2) WITH TIME ZONE := to_timestamp_tz('2019-01-21 21:05:53.46 +02:00',
'YYYY-MM-DD HH24:MI:SS.FF TZH:TZM');
t3 TIMESTAMP WITH TIME ZONE := to_timestamp_tz('2020-01-21 21:05:53.46 +02:00',
'YYYY-MM-DD HH24:MI:SS.FF TZH:TZM');
ym INTERVAL YEAR(2) to MONTH;
BEGIN
ym := NUMTOYMINTERVAL( MONTHS_BETWEEN( t3, t1 ), 'MONTH' );
DBMS_OUTPUT.PUT_LINE(ym);
END;
/
outputs:
+01-00
db<>fiddle here

pl/sql block, weird errors with simple script

I'm writing very simple block code in pl/sql:
DECLARE
dateof21 DATE;
dzien number;
dzien_tyg number;
BEGIN
dateof21:= '2001-01-01';
WHILE dateof21 != '2101-01-01' LOOP
SELECT EXTRACT(day from date dateof21) INTO dzien from dual;
select to_char(date dateof21,'D') INTO dzien_tyg from dual;
if ((dzien=13) AND (dzien_tyg=5)) THEN
dbms_output.put_line(to_char(dateof21));
end if;
dateof21:= dateof21+1;
END LOOP;
END;
but i'm getting very annoying errors:
ORA-06550: linia 8, kolumna 26:
PL/SQL: ORA-00936: brak wyrażenia
ORA-06550: linia 8, kolumna 2:
PL/SQL: SQL Statement ignored
ORA-06550: linia 9, kolumna 17:
PL/SQL: ORA-00936: brak wyrażenia
ORA-06550: linia 9, kolumna 2:
PL/SQL: SQL Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
I really tried to find whats wrong, but everything seems just fine. Can anybody help? it should write on output all fridays which are 13th day of month btw.
There are many things wrong in your code:
First: you should use proper date literals. I prefer ANSI literals, like DATE '2001-01-01' but you can also use the to_date()function: `to_date('2001-01-01', 'yyyy-mm-dd')
Second: to_char() returns a varchar value, not a number. So you can't assign the result of that to a number variable, you need to use to_number(to_char(dateof21,'D')).
You also don't need to use select ... into to call a function.
And finally: the extract() method does not require the use of a date prefix: EXTRACT(day from dateof21)
Putting that all together, gives us:
DECLARE
dateof21 DATE;
dzien number;
dzien_tyg number;
BEGIN
dateof21 := date '2001-01-01';
-- alternatively: dateof21 := to_date('2001-01-01', 'yyyy-mm-dd');
WHILE dateof21 <> DATE '2101-01-01'
LOOP
dzien := EXTRACT(day from dateof21);
dzien_tyg := to_number(to_char(dateof21,'D'));
if ((dzien=13) AND (dzien_tyg=5)) THEN
dbms_output.put_line(to_char(dateof21, 'yyyy-mm-dd'));
end if;
dateof21 := dateof21+1;
END LOOP;
END;
/
Note that the return value of to_char(dateof21,'D') depends on your NLS settings. You can't rely on it to always return 5 for a friday in all configurations (e.g. on my computer it returns 6).

Timestamp subtraction

I am getting the error
PL/SQL: ORA-00932: inconsistent datatypes: expected NUMBER got INTERVAL DAY TO SECOND
When I try to run this procedure. The column EXPIRES in the code below is of type TIMESTAMP(6) in the SESSIONS table. As far code is concerned I am having two consistent datatypes being subtracted from each other. So why this error?
CREATE OR REPLACE PROCEDURE demo_restrict_multlogin (p_login varchar2,
p_out OUT NUMBER,
p_msg OUT VARCHAR2)
IS
vl_val NUMBER;
vl_diff TIMESTAMP(6);
BEGIN
p_out := 0;
SELECT SYStimestamp-EXPIRES
INTO vl_diff
FROM sessions
WHERE LOGIN_DETAILS = p_login;
SELECT CASE WHEN COUNT (1) > 0 THEN 1 ELSE 2 END
INTO vl_val
FROM sessions
WHERE LOGIN_DETAILS = p_login AND TO_CHAR (vl_diff, 'mi') > 30;
IF vl_val = 1
THEN
p_msg := 'Pass expired';
ELSE
p_msg := 'Pass not expired ';
END IF;
EXCEPTION
WHEN OTHERS
THEN
p_out := 1;
END;
When you subtract one value that is of timestamp data type from another one, which is also of timestamp data type, result of that subtraction will be of interval data type, not timestamp, so when you are trying to select/assign a value of interval data type into/to a variable that is of timestamp data type, you will inevitably receive the PL/SQL: ORA-00932:/PLS-00382 error message. A simple solution is to assign the result of timestamp subtraction to a variable of interval data type. To that end you:
Re-declare your vl_diff variable as a variable of interval data type:
vl_diff interval day to second;
use extract() function to extract minutes from the value assigned to vl_diff variable:
extract(minute from vl_diff)
Your second query might look like this:
select case when count(1) > 0 then 1 else 2 end
into vl_val
from sessions
where login_details = p_login
and extract(minute from vl_diff) > 30
Have you tried:
SELECT TRUNC(SYStimestamp - EXPIRES)
INTO vl_diff
FROM sessions
WHERE LOGIN_DETAILS = p_login;

Resources