I have a function below, which works fine and generates a random date.
I'm trying to pair a start_date with and end_date for the same row. Is there a mechanism I can use in SQL where I can add random intervals hours, minutes, seconds to the start_date to ensure the end_date is at least 1 hr greater and less than 10 hours than the start_date?
For example, say I want to populate the table below. I am open to using a function, package type if need be.
Thanks in advance to all who respond.
ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MON-YYYY HH24:MI:SS';
CREATE OR REPLACE FUNCTION random_date(
p_from IN DATE,
p_to IN DATE
) RETURN DATE
IS
BEGIN
RETURN p_from DBMS_RANDOM.VALUE() * (p_to - p_from 1 );
END random_date;
/
CREATE TABLE t1 (
seq_num NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
start_date DATE,
end_date DATE
);
/* here I need help with end_date */
INSERT INTO t1 (start_date, end_date)
SELECT
random_date(DATE '2022-04-01', DATE '2022-04-30'),
FROM dual CONNECT BY level <= 50;
CodePudding user response:
The way I see it, one query won't do because you have to check whether difference of randomly fetched start and end date match criteria. Therefore, a PL/SQL block which - in a loop until counter hits the limit of 50 rows (taken from your connect by
clause) - validates dates and inserts a row only if difference is between 1 and 10 hours:
SQL> DECLARE
2 l_cnt NUMBER := 0;
3 l_start_date DATE;
4 l_end_date DATE;
5 BEGIN
6 WHILE l_cnt < 50
7 LOOP
8 SELECT random_date (DATE '2022-04-01', DATE '2022-04-30') start_date,
9 random_date (DATE '2022-04-01', DATE '2022-04-30') end_date
10 INTO l_start_date, l_end_date
11 FROM DUAL;
12
13 IF l_end_date - l_start_date BETWEEN 1 / 24 AND 10 / 24
14 THEN
15 INSERT INTO test (start_date, end_date)
16 VALUES (l_start_date, l_end_date);
17
18 l_cnt := l_cnt SQL%ROWCOUNT;
19 END IF;
20 END LOOP;
21 END;
22 /
PL/SQL procedure successfully completed.
SQL>
Result:
SQL> SELECT seq_num,
2 start_date,
3 end_date,
4 ROUND ((end_date - start_date) * 24, 1) diff_hours
5 FROM test;
SEQ_NUM START_DATE END_DATE DIFF_HOURS
---------- ---------------- ---------------- ----------
1 08.04.2022 11:14 08.04.2022 18:49 7,6
2 14.04.2022 19:23 14.04.2022 23:06 3,7
3 13.04.2022 12:12 13.04.2022 16:06 3,9
4 24.04.2022 13:04 24.04.2022 16:03 3
5 01.04.2022 12:52 01.04.2022 19:29 6,6
6 08.04.2022 08:24 08.04.2022 12:56 4,5
7 29.04.2022 22:05 29.04.2022 23:22 1,3
8 20.04.2022 05:35 20.04.2022 10:31 4,9
9 23.04.2022 00:52 23.04.2022 08:46 7,9
<snip>
46 05.04.2022 20:06 06.04.2022 01:52 5,8
47 11.04.2022 20:03 12.04.2022 05:11 9,1
48 12.04.2022 17:07 12.04.2022 23:13 6,1
49 30.04.2022 09:32 30.04.2022 13:42 4,2
50 12.04.2022 00:24 12.04.2022 10:19 9,9
50 rows selected.
SQL>
CodePudding user response:
You could add a function to generate a random interval within the 1 and 10 hour limits, and add that to the start date - which would need an inline view or CTE as you need to refer to the random start date twice to do the calculation:
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;
/
and then
INSERT INTO t1 (start_date, end_date)
WITH cte (start_date, duration) AS (
SELECT
random_date(DATE '2022-04-01', DATE '2022-04-30'),
random_interval(1, 10)
FROM dual CONNECT BY level <= 50
)
SELECT start_date, start_date duration
FROM cte;
In recent versions of Oracle you don't need either permanent function, if this is all they will be used for; you can define them within the WITH
clause:
INSERT /* WITH_PLSQL */ INTO t1 (start_date, end_date)
WITH
FUNCTION random_date(
p_from IN DATE,
p_to IN DATE
) RETURN DATE
IS
BEGIN
RETURN p_from DBMS_RANDOM.VALUE() * (p_to - p_from 1 );
END random_date;
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;
cte (start_date, duration) AS (
SELECT
random_date(DATE '2022-04-01', DATE '2022-04-30'),
random_interval(1, 10)
FROM dual CONNECT BY level <= 50
)
SELECT start_date, start_date duration
FROM cte;
My overall goal is to write a function that will RETURN a string denoting of NdaysNHoursNMinutesNSeconds which represents the difference from end_date-start_date
That isn't really anything to do with what you asked, but it's fairly simple if you treat the dates as timestamps, and extract the elements from the interval value you get from subtracting those:
create function diff_as_string (p_from date, p_to date)
return varchar2 is
l_interval interval day(3) to second(0);
begin
l_interval := cast(p_to as timestamp) - cast(p_from as timestamp);
return extract (day from l_interval) || 'Days'
|| extract (hour from l_interval) || 'Hours'
|| extract (minute from l_interval) || 'Minutes'
|| extract (second from l_interval) || 'Seconds';
end diff_as_string;
/
At least as long as the elapsed time is less than 1000 days, which would exceed the range allowed by that interval type.
And again you could define that function in a WITH
clause instead; or it could be done just as a query, maybe with a normal CTE or inline view to avoid repeatedly converting the data:
select seq_num, start_date, end_date,
extract (day from diff_interval) || 'Days'
|| extract (hour from diff_interval) || 'Hours'
|| extract (minute from diff_interval) || 'Minutes'
|| extract (second from diff_interval) || 'Seconds' as diff
from (
select seq_num, start_date, end_date,
cast(end_date as timestamp) - cast(start_date as timestamp) as diff_interval
from t1
)
order by seq_num;
SEQ_NUM | START_DATE | END_DATE | DIFF |
---|---|---|---|
1 | 20-APR-2022 03:46:04 | 20-APR-2022 07:44:30 | 0Days3Hours58Minutes26Seconds |
2 | 12-APR-2022 01:37:07 | 12-APR-2022 10:54:40 | 0Days9Hours17Minutes33Seconds |
3 | 12-APR-2022 16:20:44 | 12-APR-2022 20:36:13 | 0Days4Hours15Minutes29Seconds |
4 | 03-APR-2022 01:24:53 | 03-APR-2022 07:57:52 | 0Days6Hours32Minutes59Seconds |
5 | 01-APR-2022 21:33:20 | 01-APR-2022 23:50:13 | 0Days2Hours16Minutes53Seconds |
... | |||
50 | 22-APR-2022 22:46:46 | 23-APR-2022 08:03:38 | 0Days9Hours16Minutes52Seconds |
9999 | 21-NOV-2019 00:00:01 | 17-AUG-2022 00:00:00 | 999Days23Hours59Minutes59Seconds |
db<>fiddle with and without a function.