Date Calculation in Oracle Reports - oracle

I am subtracting DATE_IN (16-NOV-20 06:02 PM) from DATE_OUT (17-NOV-20 03:23 PM). I expect the answer to be 21.21 (hours.minutes), but it gives me 21.34 which is the correct hours but not the correct minutes. What am I doing wrong?
function TIME_CALCULATIONFormula return NUMBER is
begin
RETURN ROUND((((:DATE_OUT - :DATE_IN)*24)*60)/60,2);
end;

The value is correct as 0.34 hours = 20.4 minutes.
If you want to return hours as the units and minutes as the decimal then you could use:
function TIME_CALCULATIONFormula return NUMBER
is
diff INTERVAL DAY TO SECOND := (:DATE_OUT - :DATE_IN) DAY TO SECOND;
begin
RETURN EXTRACT( DAY FROM diff ) * 24
+ EXTRACT( HOUR FROM diff )
+ EXTRACT( MINUTE FROM diff ) / 100;
end;
or
function TIME_CALCULATIONFormula return NUMBER
is
diff INTERVAL DAY TO SECOND := CAST(:DATE_OUT AS TIMESTAMP)
- CAST(:DATE_IN AS TIMESTAMP);
begin
RETURN EXTRACT( DAY FROM diff ) * 24
+ EXTRACT( HOUR FROM diff )
+ EXTRACT( MINUTE FROM diff ) / 100;
end;
or
function TIME_CALCULATIONFormula return NUMBER
is
begin
RETURN TRUNC( ( :date_out - :date_in ) * 24 ) +
MOD( ( :date_out - :date_in ) * 24 * 60, 60 ) / 100;
end;
or, if you are not going to have time differences over 24 hours and want trailing zeroes on the numbers:
function TIME_CALCULATIONFormula return VARCHAR2
is
begin
RETURN TO_CHAR( DATE '1900-01-01' + ( :date_out - :date_in ), 'HH24.MI' );
end;
db<>fiddle here

The result you're getting is not "hours.minutes" - it's a decimal representation of the hours. 0.34 hour is 20 minutes and 24 seconds, which seems to be the result you're gunning for, up to the rounding error of the two decimal places you're forcing.

Related

Generating random INTERVAL

I have a function below that returns a random INTERVAL between a range of hours, which appears to be working fine but is currently limited to hours only.
I would would like to expand this functionality to also support returning a random INTERVAL for days, minutes by passing in a literal (ie 'DAY', 'MINUTE' or 'SECOND')
For example if I call random_interval (1,4, 'DAY') I would get something like this +000000002 11:24:43.000000000 or if i call random_interval (20,40, 'MINUTE') I would get something like
+000000000 00:24:44.000000000
Thanks in advance to all who answer and for your time and expertise.
CREATE OR REPLACE FUNCTION random_interval(
p_min_hours IN NUMBER,
p_max_hours IN NUMBER
) RETURN INTERVAL DAY TO SECOND
IS
BEGIN
RETURN floor(dbms_random.value(p_min_hours, p_max_hours)) * interval '1' hour
+ floor(dbms_random.value(0, 60)) * interval '1' minute
+ floor(dbms_random.value(0, 60)) * interval '1' second;
END random_interval;
/
SELECT random_interval(1, 10) as random_val FROM dual CONNECT BY level <= 10 order by 1
RANDOM_VAL
+000000000 01:04:03.000000000
+000000000 03:14:52.000000000
+000000000 04:39:42.000000000
+000000000 05:00:39.000000000
+000000000 05:03:28.000000000
+000000000 07:03:19.000000000
+000000000 07:06:13.000000000
+000000000 08:50:55.000000000
+000000000 09:10:02.000000000
+000000000 09:26:44.000000000
Try giving this a shot instead
CREATE OR REPLACE FUNCTION random_interval(
p_min IN NUMBER,
p_max IN NUMBER,
p_period VARCHAR2
) RETURN INTERVAL DAY TO SECOND
IS
BEGIN
IF p_period = 'HOUR' THEN
RETURN floor(dbms_random.value(p_min, p_max)) * interval '1' hour
+ floor(dbms_random.value(0, 60)) * interval '1' minute
+ floor(dbms_random.value(0, 60)) * interval '1' second;
ELSE IF p_period = 'DAY' THEN
RETURN floor(dbms_random.value(p_min, p_max)) * interval '1' day
+ floor(dbms_random.value(0, 24)) * interval '1' hour
+ floor(dbms_random.value(0, 60)) * interval '1' minute
+ floor(dbms_random.value(0, 60)) * interval '1' second;
ELSE IF p_period = 'MINUTE' THEN
RETURN floor(dbms_random.value(p_min, p_max)) * interval '1' minute
+ floor(dbms_random.value(0, 60)) * interval '1' second;
ELSE IF p_period = 'SECOND' THEN
RETURN floor(dbms_random.value(p_min, p_max)) * interval '1' second;
ELSE
RETURN NULL;
END IF;
END random_interval;
/
SELECT random_interval(1, 10, 'DAY') as random_val FROM dual CONNECT BY level <= 10 order by 1
RANDOM_VAL
+000000003 02:46:09.000000000
+000000004 19:19:56.000000000
+000000002 11:24:43.000000000
+000000002 16:20:44.000000000
+000000001 22:24:30.000000000
+000000002 15:14:38.000000000
+000000003 00:48:03.000000000
+000000003 18:08:13.000000000
+000000002 01:05:34.000000000
+000000002 08:12:19.000000000
You don't need a user-defined function as you can use the built-in functions DBMS_RANDOM.VALUE(lower_bound, upper_bound) and NUMTODSINTERVAL(amount, duration):
SELECT NUMTODSINTERVAL(
DBMS_RANDOM.VALUE(1, 3),
'MINUTE'
)
FROM DUAL;
Which will generate a random interval greater than or equal to 1 minute and less than 3 minutes (with a random about of seconds).
If you did want to wrap it into a function then:
CREATE FUNCTION random_interval(
p_min IN NUMBER,
p_max IN NUMBER,
p_duration IN VARCHAR2
) RETURN INTERVAL DAY TO SECOND
IS
BEGIN
RETURN NUMTODSINTERVAL(DBMS_RANDOM.VALUE(p_min, p_max), p_duration);
END;
/
If you want the seconds to be an integer then:
CREATE OR REPLACE FUNCTION random_interval(
p_min IN NUMBER,
p_max IN NUMBER,
p_duration IN VARCHAR2
) RETURN INTERVAL DAY TO SECOND
IS
v_interval INTERVAL DAY TO SECOND := NUMTODSINTERVAL(DBMS_RANDOM.VALUE(p_min, p_max), p_duration);
BEGIN
RETURN ( EXTRACT(DAY FROM v_interval) * 24 * 60 * 60
+ EXTRACT(HOUR FROM v_interval) * 60 * 60
+ EXTRACT(MINUTE FROM v_interval) * 60
+ FLOOR(EXTRACT(SECOND FROM v_interval))
) * INTERVAL '1' SECOND;
END;
/
fiddle

Data grouping for 10 seconds. PLSQL

It is necessary to find the average number of processed messages by the subscriber in 10 seconds within an hour. PLSQL
There are columns: subscriber, date ('dd.mm.yyyy hh.mm.ss') and messages, they are related (each message has its own time and the subscriber who processed it).
the idea was this:
SELECT subscriber, HH24, AVG(CNT) FROM (
SELECT subscriber,
trunc(date, 'HH24') HH24,
trunc(date - INTERVAL '10' SECOND) SS,
count(messages) CNT
FROM tables
where date IS NOT NULL
GROUP BY subscriber,
trunc(date, 'HH24'),
trunc(date - INTERVAL '10' SECOND)
order by subscriber)
GROUP BY subscriber, HH24
Expected Result:
subscriber HH24 CNT
subscriber 1 01.01.01 21:00:00 8,88
subscriber 1 01.01.01 22:00:00 7,88
subscriber 2 01.01.01 21:00:00 6,66
subscriber 3 01.01.01 22:00:00 5,54
My query produces something like the following:
subscriber HH24 CNT
subscriber 1 01.01.01 21:00:00 400
subscriber 1 01.01.01 22:00:00 500
subscriber 2 01.01.01 21:00:00 300
subscriber 3 01.01.01 22:00:00 500
Most likely my request does not group the data for 10 seconds, tell me where I made a mistake? Thank!
Please try this:
select subscriber, ss, count(сообщения) cnt from (
select subscriber,
trunc(date) + (floor((date-trunc(date))*24*60*60/10)*10)/24/60/60 as ss,
сообщения
from tables
where date is not null)
group by subscriber, ss;
I'd better explain the big expression:
(date-trunc(date))*24*60*60 -- Time expressed as number of seconds since midnight (T1)
(floor(T1/10)*10) -- ... rounded down to nearest 10 seconds (T2)
T2/24/60/60 -- ... converted to fraction of a day (T3)
trunc(date)+T3 -- ... added back on to date
Not clear what you are looking for. For this kind of task I use this generic function:
CREATE OR REPLACE FUNCTION MakeInterval(ts IN TIMESTAMP, roundInterval IN INTERVAL DAY TO SECOND) RETURN TIMESTAMP DETERMINISTIC IS
denom INTEGER;
BEGIN
IF roundInterval >= INTERVAL '1' HOUR THEN
denom := EXTRACT(HOUR FROM roundInterval);
IF MOD(24, denom) <> 0 THEN
RAISE VALUE_ERROR;
END IF;
RETURN TRUNC(ts) + TRUNC(EXTRACT(HOUR FROM ts) / denom) * denom * INTERVAL '1' HOUR;
ELSIF roundInterval >= INTERVAL '1' MINUTE THEN
denom := EXTRACT(MINUTE FROM roundInterval);
IF MOD(60, denom) <> 0 THEN
RAISE VALUE_ERROR;
END IF;
RETURN TRUNC(ts, 'hh') + TRUNC(EXTRACT(MINUTE FROM ts) / denom) * denom * INTERVAL '1' MINUTE;
ELSE
denom := EXTRACT(SECOND FROM roundInterval);
IF MOD(60, denom) <> 0 THEN
RAISE VALUE_ERROR;
END IF;
RETURN TRUNC(ts, 'mi') + TRUNC(EXTRACT(SECOND FROM ts) / denom) * denom * INTERVAL '1' SECOND;
END IF;
END MakeInterval;
Then you can use it simple as MakeInterval("date", INTERVAL '10' SECOND)
However, maybe you are looking for windowing_clause, for example
AVG(CNT) OVER (PARTITION BY subscriber ORDER BY "date" RANGE BETWEEN CURRENT_ROW AND INTERVAL '10' SECOND FOLLOWING)
I explained poorly, but the problem was solved as follows:
SELECT subscriber, HH24, AVG(cnt / 3600) * 100 / 10 cnt
AVG - average value, CNT / 3600 - divide by the number of seconds per hour, * 100 - translate in percent, / 10 - we get the answer for 10 seconds

Getting compilation errors with this function

I am getting the following error:
Warning: compiled but with compilation errors
when I try to create the following function:
Create or replace function Time_Gap (v_date1, v_date2, return varchar2 )
is
difrence_In_Hours varchar2;
difrence_In_minutes varchar2;
difrence_In_seconds varchar2;
begin
difrence_In_Hours := floor(((v_date2-v_date1)*24*60*60)/3600);
difrence_In_minutes := floor((((v_date2-v_date1)*24*60*60) -
floor(((v_date2-v_date1)*24*60*60)/3600)*3600)/60);
difrence_In_seconds := round((((v_date2-v_date1)*24*60*60) -
floor(((v_date2-v_date1)*24*60*60)/3600)*3600 -
(floor((((v_date2-v_date1)*24*60*60) -
floor(((v_date2-v_date1)*24*60*60)/3600)*3600)/60)*60) ));
return difrence_In_Hours || '' HRS '' || difrence_In_minutes || '' MINS '' || difrence_In_seconds
|| '' SECS '';
end ;
/
what am I doing wrong???
Thanks,
a few syntax errors, return type should be outside of input parameters and define type of input:
create or replace function time_gap (v_date1 in date, v_date2 in date)
return varchar2 is
difrence_in_hours varchar2(500);
difrence_in_minutes varchar2(500);
difrence_in_seconds varchar2(500);
begin
difrence_in_hours := floor ( ( (v_date2 - v_date1) * 24 * 60 * 60) / 3600);
difrence_in_minutes :=
floor ( ( ( (v_date2 - v_date1) * 24 * 60 * 60) - floor ( ( (v_date2 - v_date1) * 24 * 60 * 60) / 3600) * 3600) / 60);
difrence_in_seconds :=
round (
( ( (v_date2 - v_date1) * 24 * 60 * 60)
- floor ( ( (v_date2 - v_date1) * 24 * 60 * 60) / 3600) * 3600
- ( floor (
( ( (v_date2 - v_date1) * 24 * 60 * 60) - floor ( ( (v_date2 - v_date1) * 24 * 60 * 60) / 3600) * 3600)
/ 60)
* 60)));
return difrence_in_hours || ' HRS ' || difrence_in_minutes || ' MINS ' || difrence_in_seconds || ' SECS';
end;
/
EDIT:
You have to give us more of the error message. It's not saying anything. This works fine for me:
declare
function time_gap (v_date1 in date, v_date2 in date)
return varchar2 is
difrence_in_hours number;
difrence_in_minutes number;
difrence_in_seconds number;
begin
difrence_in_hours := floor ( ( (v_date2 - v_date1) * 24 * 60 * 60) / 3600);
difrence_in_minutes :=
floor ( ( ( (v_date2 - v_date1) * 24 * 60 * 60) - floor ( ( (v_date2 - v_date1) * 24 * 60 * 60) / 3600) * 3600) / 60);
difrence_in_seconds :=
round (
( ( (v_date2 - v_date1) * 24 * 60 * 60)
- floor ( ( (v_date2 - v_date1) * 24 * 60 * 60) / 3600) * 3600
- ( floor (
( ( (v_date2 - v_date1) * 24 * 60 * 60)
- floor ( ( (v_date2 - v_date1) * 24 * 60 * 60) / 3600) * 3600)
/ 60)
* 60)));
return trim (to_char (difrence_in_hours)) || ' HRS ' || trim (to_char (difrence_in_minutes)) || ' MINS ' || trim (
to_char (
difrence_in_seconds)) || ' SECS';
end;
begin
dbms_output.put_line (time_gap (sysdate, sysdate - 2));
end;
==>
PL/SQL block executed
-48 HRS 0 MINS 0 SECS
Have you created it without checking the syntax? Always check the syntax before doing stuff like that.
As per https://docs.oracle.com/cd/B12037_01/server.101/b10759/create_function.gif just in the first line:
you lack one of [IN][OUT][IN OUT] at every argument
you lack datatypes for every argument
return statement should be outside the parentheses.
At that point I stopped reading.

Oracle performance tuning order by is taking time

Am having query,in which two fields and getting as output pps_id and total_weight. Here pps_id is the column from the table and total_weight we are calculating from inner query. after doing all process in query we are order by the query by total weight. Its taking more cost and response.Is there any way to improve this query performance.
SELECT PPS_ID, TOTAL_WEIGHT
FROM ( SELECT PPS_ID, TOTAL_WEIGHT
FROM (SELECT pps_id,
ROUND (
( ( (60 * name_pct_match / 100)
+ prs_weight
+ year_weight
+ dt_weight)
/ 90)
* 100)
total_weight
FROM (SELECT pps_id,
ROUND (func_compare_name ('aaaa',
UPPER (name_en),
' ',
60))
name_pct_match,
DECODE (prs_nationality_id, 99, 15, 0)
prs_weight,
10 mother_weight,
100 total_attrib_weight,
CASE
WHEN TO_NUMBER (
TO_CHAR (birth_date, 'yyyy')) =
1986
THEN
5
ELSE
0
END
year_weight,
CASE
WHEN TO_CHAR (
TO_DATE ('12-JAN-86',
'DD-MON-RRRR'),
'dd') =
TO_CHAR (birth_date, 'dd')
AND TO_CHAR (
TO_DATE ('12-JAN-86',
'DD-MON-RRRR'),
'mm') =
TO_CHAR (birth_date, 'mm')
THEN
10
WHEN TO_DATE ('12-JAN-86', 'DD-MON-RRRR') BETWEEN birth_date
- 6
AND birth_date
+ 6
THEN
8
WHEN TO_DATE ('12-JAN-86', 'DD-MON-RRRR') BETWEEN birth_date
- 28
AND birth_date
+ 28
THEN
5
WHEN TO_DATE ('12-JAN-86', 'DD-MON-RRRR') BETWEEN birth_date
- 90
AND birth_date
+ 90
THEN
3
ELSE
0
END
dt_weight
FROM individual_profile
WHERE birth_date = '12-JAN-86'
AND IS_ACTIVE = 1
AND gender_id = 1
AND ROUND (func_compare_name ('aaa',
UPPER (name_en),
' ',
60)) > 20))
WHERE TOTAL_WEIGHT >= 100
ORDER BY total_weight DESC)
WHERE ROWNUM <= 10
i have tried by splitting the query and put values in temp tables and tried but it also taking time. I want to improve the performance of the query

Error while converting date string to date

In my project in my_table time of any event is stored in number format. Now I have to convert it to oracle datetime format.
Here is an explanation below:
Example1 :
•Sched_Arr_Tm = 0450, will equal 04:30 am (the first 2 is HH (24 hour clock. Since 04 < 12, then just use that number as the hour)) and the next 2 are the fractional equivalent of an hour (.50 * 60 min = 30 min)
•Sched_Arr_Tm = 2100, will equal 9:00 PM (since 21> 12, then take 21-12=9)
•Sched_Arr_Tm = 1475, will equal 02:45 Pm (the first 2 is HH (24 hour clock. Since 14 > 12. Then take 14-12=2), then just use that number as the hour)) and the next 2 are the fractional equivalent of an hour (.75 * 60 min = 45 min)
•Sched_Arr_Tm = 0075, will equal 12:45 AM (since the hour = 00, then the hour= 12) and the next 2 are the fractional equivalent of an hour (.75 * 60 min = 45 min)
I am able to extract data according to above login but getting error while converting it to date.
select sched_arr_tm,
LPAD(substr(tn.sched_arr_tm, 1,length(tn.sched_arr_tm) - 2),2,'0') as HH,
RPAD(TRUNC(TO_NUMBER(substr(tn.sched_arr_tm,3,length(tn.sched_arr_tm) - 2)) * .60,0),2,'0') as MM,
'00' AS SS,
LPAD(substr(tn.sched_arr_tm,1,length(tn.sched_arr_tm) - 2),2,'0')
||':' ||
RPAD(TRUNC(TO_NUMBER(substr(tn.sched_arr_tm,3,length(tn.sched_arr_tm) - 2)) * .60,0),2,'0')
||':'||
LPAD(0,2,0) AS DTTM,
TO_DATE(LPAD(substr(tn.sched_arr_tm,1,length(tn.sched_arr_tm) - 2),2,'0')
||':' ||
RPAD(TRUNC(TO_NUMBER(substr(tn.sched_arr_tm,3,length(tn.sched_arr_tm) - 2)) * .60,0),2,'0')
||':'||
LPAD(00,2,0),'HH24:MI:SS') AS DTTM,
tn.sched_slip_arr_tm
from MY_TABLE;
I am getting this error:
ORA-01858: a non-numeric character was found where a numeric was expected.
you can do this with:
SQL> with data as (select 450 Sched_Arr_Tm from dual
2 union all
3 select 1475 from dual
4 union all
5 select 2100 from dual)
6 select Sched_Arr_Tm, to_date(hours||':'||(60*(mins/100)), 'hh24:mi')
7 from (select Sched_Arr_Tm, substr(Sched_Arr_Tm, -2) mins,
8 substr(Sched_Arr_Tm, 1, length(Sched_Arr_Tm)-2) hours
9 from data)
10 /
SCHED_ARR_TM TO_DATE(HOURS||':
------------ -----------------
450 01-jan-2013 04:30
1475 01-jan-2013 14:45
2100 01-jan-2013 21:00
SQL>

Resources