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
Related
I need help with PVA calculation in PL/SQL .I have formula:
Annuity = r * PVA Ordinary / [1 – (1 + r)-n]
Where:
PVA Ordinary = Present value of an ordinary annuity
r = Effective interest rate
n = Number of periods.
enter image description here
You can use:
DECLARE
principal NUMBER := 9000;
r NUMBER := 0.015;
n NUMBER := 5;
start_dt DATE := DATE '2022-07-14';
payment NUMBER := r * principal / (1 - POWER(1 + r, -n));
amt NUMBER := principal;
interest NUMBER;
pmt_dt DATE;
BEGIN
FOR i IN 1 .. n LOOP
pmt_dt := ADD_MONTHS(start_dt, i);
pmt_dt := pmt_dt + CASE pmt_dt - TRUNC(pmt_dt, 'IW')
WHEN 5 THEN 2 -- Saturday
WHEN 6 THEN 1 -- Sunday
ELSE 0 -- Weekday
END;
interest := amt * r;
amt := amt - payment + interest;
DBMS_OUTPUT.PUT_LINE(
TO_CHAR(i, 'fm0')
|| ', ' || TO_CHAR(pmt_dt, 'YYYY-MM-DD (DY)')
|| ', ' || TO_CHAR(payment, '9990.00')
|| ', ' || TO_CHAR(interest, '990.00')
|| ', ' || TO_CHAR(payment - interest, '9990.00')
|| ', ' || TO_CHAR(amt, '9990.00')
);
END LOOP;
END;
/
Which outputs:
1, 2022-08-15 (MON), 1881.80, 135.00, 1746.80, 7253.20
2, 2022-09-14 (WED), 1881.80, 108.80, 1773.01, 5480.19
3, 2022-10-14 (FRI), 1881.80, 82.20, 1799.60, 3680.59
4, 2022-11-14 (MON), 1881.80, 55.21, 1826.60, 1853.99
5, 2022-12-14 (WED), 1881.80, 27.81, 1853.99, -0.00
Or, in SQL using a MODEL clause:
WITH data (id, start_date, principal, rate, period ) AS (
SELECT 1, DATE '2022-07-14', 9000, 0.015, 5 FROM DUAL
)
SELECT pmt_dt + CASE pmt_dt - TRUNC(pmt_dt, 'IW')
WHEN 5 THEN 2
WHEN 6 THEN 1
ELSE 0
END AS pmt_dt,
ROUND(payment, 2) AS payment,
ROUND(interest, 2) AS interest,
ROUND(payment - interest, 2) AS reduction,
ROUND(balance, 2) AS balance
FROM data
MODEL
PARTITION BY (id)
DIMENSION BY (1 AS key)
MEASURES (
start_date,
principal,
rate,
period,
DATE '1900-01-01' AS pmt_dt,
rate * principal / (1 - POWER(1 + rate, -period)) AS payment,
0 AS interest,
0 AS balance
)
RULES SEQUENTIAL ORDER ITERATE (100) UNTIL (balance[ITERATION_NUMBER+1] <= 0) (
payment[ITERATION_NUMBER+1] = payment[1],
pmt_dt[ITERATION_NUMBER+1] = ADD_MONTHS(start_date[1], ITERATION_NUMBER+1),
interest[ITERATION_NUMBER + 1]
= COALESCE(balance[ITERATION_NUMBER],principal[1]) * rate[1],
balance[ITERATION_NUMBER+1]
= COALESCE(balance[ITERATION_NUMBER],principal[1])
- payment[1] + interest[ITERATION_NUMBER+1]
)
ORDER BY id, key
Which outputs:
PMT_DT
PAYMENT
INTEREST
REDUCTION
BALANCE
15-AUG-22
1881.8
135
1746.8
7253.2
14-SEP-22
1881.8
108.8
1773.01
5480.19
14-OCT-22
1881.8
82.2
1799.6
3680.59
14-NOV-22
1881.8
55.21
1826.6
1853.99
14-DEC-22
1881.8
27.81
1853.99
0
db<>fiddle here
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.
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
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
I don't know how to convert integer into percentage, please help me. Thank you
Here's the query:
SELECT 'Data' || ',' ||
TO_CHAR(D.DTIME_DAY,'MM/dd/yyyy') || ',' ||
NVL(o.CNT_OPENED,0) || ',' || --as cnt_opened
NVL(c.CNT_CLOSED,0) --as cnt_closed
FROM OWNER_DWH.DC_DATE d
LEFT JOIN (SELECT TRUNC(t.CREATE_TIME, 'MM') AS report_date,
count(*) AS cnt_opened
FROM APP_ACCOUNT.OTRS_TICKET t
WHERE t.CREATE_TIME BETWEEN SYSDATE -120 AND SYSDATE
GROUP BY TRUNC(t.CREATE_TIME, 'MM')) o
ON d.DTIME_DAY=o.REPORT_DATE
LEFT JOIN (SELECT TRUNC(t.CLOSE_TIME, 'MM') as report_date,
count(*) AS cnt_closed
FROM APP_ACCOUNT.OTRS_TICKET t
WHERE t.CLOSE_TIME BETWEEN SYSDATE -120 AND SYSDATE
GROUP BY TRUNC(t.CLOSE_TIME, 'MM')) c
ON D.DTIME_DAY=c.REPORT_DATE
WHERE d.DTIME_DAY BETWEEN SYSDATE -120 AND TRUNC(SYSDATE) -1 AND
d.DTIME_DAY = TRUNC(d.DTIME_DAY, 'MM') AND
TRUNC(d.DTIME_DAY,'MM')= d.DTIME_DAY
ORDER BY D.DTIME_DAY;
The output of that query:
Data,10/01/2013,219,201
Data,11/01/2013,249,234
Data,12/01/2013,228,224
Data,01/01/2014,269,256
example output that I need is like this:
Data,10/01/2013,219, 52%, 201, 45%
Data,11/01/2013,249, 75%, 234, 60%
.......
........
Formula:
create_time + close time = total / create_time (for cnt_opened each column) = percentage
create_time + close time = total / close_time (for cnt_closed each column) = percentage
Try this:
Basically just add the total of CNT_OPENED and CNT_CLOSED, then whichever you want to take the percentage of, multiply that by 100 and divide by the sum.
For instance, CNT_OPENED = 219 and CNT_CLOSED = 201 so the total is 420. Multiply CNT_OPENED by 100 and then divide by 420 -> (219 * 100) / 420 = 52. Do the same thing with CNT_CLOSED.
Note that this WILL result in an exception if both CNT_OPENED and CNT_CLOSED are 0.
SELECT 'Data'
||','||TO_CHAR(D.DTIME_DAY,'MM/dd/yyyy')
||','||NVL(o.CNT_OPENED,0) --as cnt_opened
||','||(NVL(o.CNT_OPENED,0) * 100) / (NVL(o.CNT_OPENED,0) + NVL(o.CNT_CLOSED,0)) || '%'
||','||NVL(c.CNT_CLOSED,0) --as cnt_closed
||','||(NVL(o.CNT_CLOSED,0) * 100) / (NVL(o.CNT_OPENED,0) + NVL(o.CNT_CLOSED,0)) || '%'
That will also potentially give you a million decimal places, so if you only want to take it out to a couple, simply use the TRUNC function and specify your precision (2 decimal places in this case):
TRUNC((NVL(o.CNT_OPENED,0) * 100) / (NVL(o.CNT_OPENED,0) + NVL(o.CNT_CLOSED,0)), 2)