Generate a random NUMBER of timestamps for each date INTERVAL - sql

Below I am generating rows every 15 minutes from a start time to an end time.
Within the first start and END time
25-JAN-2023 09:00:00.000000
25-JAN-2023 09:15:00.000000
I want to pass these values to the function random_timestamp and generate a random number of timestamps (ie 3-10), which fall between the two timestamps.
I get the next group of times
25-JAN-2023 09:15:00.000000
25-JAN-2023 09:30:00.000000
Call the random_timestamp function again to generate a random number (ie 3-10) of timestamps for that period
I repeat the process until I hit the last set of times
25-JAN-2023 11:45:00.000000
25-JAN-2023 12:00:00.000000
I saw something about a lag function and I think that may help but I'm unsure how to implement it into my example where I need to pass two values to my function.
I know I need to put this logic in my code
SELECT random_timestamp(
TIMESTAMP '2023-01-25 11:45:00',
TIMESTAMP '2023-01-25 12:00:00') from dual connect by level <= ( select dbms_random.value ( 3, 10 ) n from dual )
in place of
select dt from dt;
Below is my test CASE and an example of the desired output. Any help would be appreciated.
ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'DD-MON-YYYY HH24:MI:SS.FF';
with dt (dt, interv) as (
select TIMESTAMP '2023-01-25 09:00:00',
numtodsinterval(15,'MINUTE') from dual
union all
select dt.dt + interv, interv from dt
where dt.dt + interv <= TIMESTAMP '2023-01-25 12:00:00')
select dt from dt;
/
DT
25-JAN-2023 09:00:00.000000
25-JAN-2023 09:15:00.000000
25-JAN-2023 09:30:00.000000
25-JAN-2023 09:45:00.000000
25-JAN-2023 10:00:00.000000
25-JAN-2023 10:15:00.000000
25-JAN-2023 10:30:00.000000
25-JAN-2023 10:45:00.000000
25-JAN-2023 11:00:00.000000
25-JAN-2023 11:15:00.000000
25-JAN-2023 11:30:00.000000
25-JAN-2023 11:45:00.000000
25-JAN-2023 12:00:00.000000
CREATE OR REPLACE FUNCTION random_timestamp(
p_from IN TIMESTAMP,
p_to IN TIMESTAMP,
p_fraction IN VARCHAR2 DEFAULT 'Y'
) RETURN TIMESTAMP
IS
return_val_y TIMESTAMP(9) := p_from + dbms_random.value() * (p_to - p_from);
return_val_n TIMESTAMP(0) := return_val_y;
BEGIN
RETURN CASE
WHEN p_fraction LIKE 'Y%' OR p_fraction LIKE 'y%'
THEN return_val_y
ELSE return_val_n
END;
END random_timestamp;
/
The end result should be something like this below. Note I put an empty line between each call to the function to show a different amount of rows for each call to my function.
25-JAN-2023 09:04:42.917984
25-JAN-2023 09:04:38.082448
25-JAN-2023 09:11:43.368529
25-JAN-2023 09:04:56.513339
25-JAN-2023 09:10:21.592329
25-JAN-2023 09:06:56.241198
25-JAN-2023 09:03:02.853214
25-JAN-2023 09:18:43.151379
25-JAN-2023 09:16:10.342814
25-JAN-2023 09:21:38.186374
…
…
…
25-JAN-2023 11:52:25.095462
25-JAN-2023 11:50:43.687866
25-JAN-2023 11:58:15.107269
25-JAN-2023 11:57:21.549818
25-JAN-2023 11:50:10.750542

You can use:
WITH dt (dt, interv, rnd) AS (
SELECT TIMESTAMP '2023-01-25 09:00:00',
INTERVAL '15' MINUTE,
FLOOR(DBMS_RANDOM.VALUE(3,11))
FROM DUAL
UNION ALL
SELECT dt.dt + interv,
interv,
FLOOR(DBMS_RANDOM.VALUE(3,11))
FROM dt
WHERE dt.dt + interv <= TIMESTAMP '2023-01-25 12:00:00'
)
SELECT random_timestamp(dt, dt + INTERVAL '15' MINUTE) AS dt
FROM dt
CROSS APPLY (
SELECT LEVEL AS lvl
FROM DUAL
CONNECT BY LEVEL <= rnd
)
ORDER BY dt;
fiddle

You can use DBMS_RANDOM function to generate a value between 90 and 300 seconds (that give you 3-10 rows per 15 minutes) to be added to your timestamp.
WITH dt (dt) AS (
SELECT TIMESTAMP '2023-01-25 09:00:00'
+ NUMTODSINTERVAL(DBMS_RANDOM.VALUE(90, 300),'SECOND') FROM dual
UNION ALL
SELECT dt.dt + NUMTODSINTERVAL(DBMS_RANDOM.VALUE(90, 300),'SECOND')
FROM dt
WHERE dt.dt + NUMTODSINTERVAL(15,'MINUTE') <= TIMESTAMP '2023-01-25 12:00:00'
)
SELECT dt FROM dt;
Check the demo here.

Get the difference in seconds between the from/to by casting to a date (easier to work with than intervals), then add in any fractional piece. Generate a random number between 0 and that difference, then add it back as seconds to the from date.
CREATE OR REPLACE FUNCTION random_timestamp (
p_from IN TIMESTAMP,
p_to IN TIMESTAMP
) RETURN TIMESTAMP
AS
var_diff number;
BEGIN
var_diff := ((CAST(p_to AS date) - CAST(p_from AS date)) * 86400) + (EXTRACT(SECOND FROM p_to) - EXTRACT(SECOND FROM p_from));
RETURN NUMTODSINTERVAL(dbms_random.value(0,var_diff),'SECOND') + p_from;
END;
To generate 15-minute intervals, just multiply level by 15 and add it as a minute interval to your starting point:
SELECT random_timestamp(TIMESTAMP '2023-01-25 11:45:00' + NUMTODSINTERVAL(level*15,'MINUTE'),
TIMESTAMP '2023-01-25 12:00:00' + NUMTODSINTERVAL(level*15,'MINUTE'))
from dual connect by level <= 5

Related

Create calendar function in Oracle using WITH STATEMENT

I come from SQL Server and some times I'm not familiar to Oracle syntax, I want to create a function that takes a date and number of dates as a parameters and create a table function.
My original query is:
VAR TREND = 1;
VAR OBS_DATE = 20221109;
VAR N_DAYS = 21;
WITH CAL AS
(
SELECT
TO_DATE(:OBS_DATE, 'YYYYMMDD') + (LEVEL - 1 * :TREND) DT, ROW_NUMBER() OVER(ORDER BY NULL) - 1 IX
FROM
DUAL
WHERE
TO_CHAR(TO_DATE(:OBS_DATE, 'YYYYMMDD') + (LEVEL - 1 * :TREND) , 'D') NOT IN (1,7)
CONNECT BY LEVEL <= :N_DAYS + :N_DAYS/5*2+1
)
SELECT DT
FROM CAL
WHERE IX <= :N_DAYS;
But when I try to convert as a function it sends me an error and I don't know what the correct syntax is.
My attempt is:
CREATE OR REPLACE FUNCTION FUN_BUS_CALENDAR(
OBS_DATE IN DATE := SYSDATE
, NDAYS IN NUMBER
, TREND IN NUMBER
)
RETURN OBS_DATE DATE;
BEGIN
WITH CAL AS(
SELECT
TO_DATE(:OBS_DATE, 'YYYYMMDD') + (LEVEL - 1 * :TREND) OBS_DATE, ROW_NUMBER() OVER(ORDER BY NULL) - 1 IX
FROM DUAL
WHERE TO_CHAR(TO_DATE(:OBS_DATE, 'YYYYMMDD') + (LEVEL - 1 * :TREND) , 'D') NOT IN (1,7)
CONNECT BY LEVEL <= :N_DAYS + :N_DAYS/5.*2.+1.
)
SELECT OBS_DATE FROM CAL WHERE IX <= :N_DAYS
RETURN OBS_DATE
END
/
You should probably just use the initial query.
However, if you did want a function then you can use a pipelined function:
CREATE FUNCTION BARRRAF.FUN_BUS_CALENDAR(
OBS_DATE IN DATE := SYSDATE,
NDAYS IN NUMBER,
TREND IN NUMBER
) RETURN SYS.ODCIDATELIST PIPELINED
IS
BEGIN
FOR i IN 1 .. ndays LOOP
PIPE ROW( obs_date + i - trend );
END LOOP;
END;
/
Then if you want to generate a row number then just use a sub-query:
SELECT column_value AS obs_date,
ROWNUM - 1 AS rn
FROM TABLE(BARRRAF.FUN_BUS_CALENDAR(ndays => 3, trend=>1))
Which outputs:
OBS_DATE
RN
2022-11-17 23:40:22
0
2022-11-18 23:40:22
1
2022-11-19 23:40:22
2
fiddle
Here is a generic function that can be used to create a calendar for the following INTERVALs seconds, minutes, hours or days.
You can pass it any start and END date/time you like. The lower or higher date can go in any position as there is logic to figure out which is what least/greatest command
CREATE OR REPLACE FUNCTION generate_dates(i_from_dat IN TIMESTAMP, i_to_dat IN TIMESTAMP, i_interval IN NUMBER, i_interval_type IN VARCHAR2)
RETURN VARCHAR2
SQL_MACRO
IS
BEGIN
RETURN q'~SELECT LEAST(i_from_dat,i_to_dat) + NUMTODSINTERVAL( (LEVEL-1)*i_interval, i_interval_type ) AS dt
FROM DUAL
CONNECT BY LEAST(i_from_dat,i_to_dat) + NUMTODSINTERVAL( (LEVEL-1)*i_interval, i_interval_type) < GREATEST(i_from_dat, i_to_dat)~';
END ;
SELECT * FROM generate_dates(
TIMESTAMP '2022-11-03 09:47:31',
TIMESTAMP '2022-11-03 12:37:11',
30, 'MINUTE') ;
DT
03-NOV-22 09.47.31.000000 AM
03-NOV-22 10.17.31.000000 AM
03-NOV-22 10.47.31.000000 AM
03-NOV-22 11.17.31.000000 AM
03-NOV-22 11.47.31.000000 AM
03-NOV-22 12.17.31.000000 PM
SELECT * FROM generate_dates(
TIMESTAMP '2022-11-03 00:00:00',
TIMESTAMP '2022-11-08 00:00:00',
1, 'DAY') ;
DT
03-NOV-22 12.00.00.000000 AM
04-NOV-22 12.00.00.000000 AM
05-NOV-22 12.00.00.000000 AM
06-NOV-22 12.00.00.000000 AM
07-NOV-22 12.00.00.000000 AM

Is there a way to create a date or timestamp output column from a varchar

I have a data set with the following columns
time date
1310 2020-06-01
1425 2020-06-22
1640 2020-06-29
My desired final output is a column in datetime format that looks as such
datetime
2020-06-01 13:10:00.000
2020-06-22 14:25:00.000
2020-06-29 16:40:00.000
I have used the following command thus far to format the output the way I would like
CONCAT(date_string, ' ', substring(barstarttime, 1, 2), ':', substring(barstarttime, 3, 2), ':00.000'))
However, I have not had success in changing this to a datetime or timestamp.
Is there a function that can help me do so?
Thanks
Get the hours and minutes from the time string with SUBSTR. Then add the according intervals to the date.
I don't know whether Presto allows functions for the interval values. Please try:
select
date +
interval substr(time, 1, 2) hour +
interval substr(time, 3, 2) minute
from mytable;
If this doesn't work, try this instead:
select
date +
((interval '1' hour) * cast(substr(time, 1, 2) as integer)) +
((interval '1' minute) * cast(substr(time, 3, 2) as integer))
from mytable;
As mentioned, I don't know Presto. You may even have to cast the date to a timestamp first:
cast(date as timestamp) +
...
You can use date_parse function.
select date_parse(cast(date as varchar) || ' ' || time, '%Y-%m-%d %H%i') from
(values ('1300', date '2020-06-01')) as t(time, date)
You can use below in order to convert to timestamp
select *,
cast(date_col as timestamp) as ts
from T

Query date and specific time in the where clause

I'm looking for some help in Oracle SQL. I need to query date and time in the where clause to find shift data based on current date. There are 3 shifts, 5am to 1pm, 1pm to 9pm and 9pm to 5am(next day morning. For example
SELECT 'T1' AS SHIFT, WORK_CENTER, SUM(CASE WHEN AQL='PASS' THEN 1 ELSE 0) END AS AQL_PASSED
FROM Z_INSPECTION_DEFECTS
WHERE DATE_TIME >= TO_DATE((SELECT CONCAT(TO_CHAR(CURRENT_DATE, ''DD-MON-YYYY''), '' 5:00:00 '') FROM DUAL) , ''DD-MON-YYYY HH:MI:SS '') AND
DATE_TIME < TO_DATE((SELECT CONCAT(TO_CHAR(CURRENT_DATE, ''DD-MON-YYYY''), '' 1:00:00 '') FROM DUAL) , ''DD-MON-YYYY HH:MI:SS '')
I do not get any results from this query. The date time field is a timestamp on local Los Angeles time.
The immediate problem is that you're looking dor times that are after 5am and before 1am, which logically means nothing matches - as no time can fulfil both at once. You can use 24-hour times instead:
WHERE DATE_TIME >= TO_DATE((SELECT CONCAT(TO_CHAR(CURRENT_DATE, ''DD-MON-YYYY''), '' 5:00:00 '') FROM DUAL) , ''DD-MON-YYYY HH24:MI:SS '') AND
DATE_TIME < TO_DATE((SELECT CONCAT(TO_CHAR(CURRENT_DATE, ''DD-MON-YYYY''), ''13:00:00 '') FROM DUAL) , ''DD-MON-YYYY HH24:MI:SS '')
But there are other ways to get those, e.g. just without the queries against dual:
WHERE DATE_TIME >= TO_DATE(TO_CHAR(CURRENT_DATE, ''DD-MON-YYYY'') || '' 5:00:00 '', ''DD-MON-YYYY HH24:MI:SS '') AND
DATE_TIME < TO_DATE(TO_CHAR(CURRENT_DATE, ''DD-MON-YYYY'') || ''13:00:00 '', ''DD-MON-YYYY HH24:MI:SS '')
or with truncation and date arithmetic:
WHERE DATE_TIME >= TRUNC(CURRENT_DATE) + (5/24) AND
DATE_TIME < TRUNC(CURRENT_DATE) + (13/24)
You really need to be getting those times in the target time zone though, e.g.:
WHERE DATE_TIME >= FROM_TZ(CAST(TRUNC(CURRENT_DATE) + (5/24) AS TIMESTAMP), ''America/Los_Angeles'') AND
DATE_TIME < FROM_TZ(CAST(TRUNC(CURRENT_DATE) + (13/24) AS TIMESTAMP), ''America/Los_Angeles'')
You need to be careful with current_date, which is in you current session time zone, and sysdate which is in the server time zone. If your session is UTC then current_date might not give you the day you expect.
(I've stuck with escaped single quotes as that is mostly what you have in the question, implying you're probably running this with dynamic SQL; whether you need to is another matter. If you're only doing that to provide the period offsets at runtime then that wouldn't need to be dynamic...)
You can see the generated times from those calculations with:
select FROM_TZ(CAST(TRUNC(CURRENT_DATE) + (5/24) AS TIMESTAMP), 'America/Los_Angeles'),
FROM_TZ(CAST(TRUNC(CURRENT_DATE) + (13/24) AS TIMESTAMP), 'America/Los_Angeles')
from dual;
FROM_TZ(CAST(TRUNC(CURRENT_DATE)+(5/24)ASTIMESTAM FROM_TZ(CAST(TRUNC(CURRENT_DATE)+(13/24)ASTIMESTA
------------------------------------------------- -------------------------------------------------
2018-10-22 05:00:00.000000000 AMERICA/LOS_ANGELES 2018-10-22 13:00:00.000000000 AMERICA/LOS_ANGELES

Generate a table from period of time and each entry spans one hour

For example I have 8am to 8pm.
I want the table where it can generate the table where it has entry one by one starttime and endtime (two columns):
8:00-9:00
9:00-10:00
....
19:00-20:00
Like this way. No date before the time.
You can use the generate_series function to do that:
psql# select generate_series('2014-01-01 16:00'::timestamp, '2014-01-01 20:00'::timestamp, '1 hour');
generate_series
---------------------
2014-01-01 16:00:00
2014-01-01 17:00:00
2014-01-01 18:00:00
2014-01-01 19:00:00
2014-01-01 20:00:00
(5 rows)
And then you can use:
SELECT
t AS starttime,
t + INTERVAL '1 hour' as endtime
FROM
GENERATE_SERIES(
'2014-01-01 16:00'::TIMESTAMP,
'2014-01-01 20:00'::TIMESTAMP,
'1 hour'
) AS t
To get the start and end times.
Alternatively, to just get the times, you can use:
SELECT
'08:00'::time + (t || ' hours')::interval as starttime,
'08:00'::time + ((t + 1)::text || ' hours')::interval as endtime
FROM
GENERATE_SERIES(0, 12) AS t
Simpler, use a full timestamp with a dummy date component and cast to time:
SELECT t::time AS starttime
, t::time + interval '1h' AS endtime
FROM generate_series('2000-1-1 08:00'::timestamp
, '2000-1-1 19:00'::timestamp
, interval '1h') t;
The same for text columns:
SELECT to_char(t, 'HH24:MI') AS starttime
, to_char(t + interval '1h', 'HH24:MI') AS endtime
FROM generate_series('2000-1-1 08:00'::timestamp
, '2000-1-1 19:00'::timestamp
, interval '1h') t;

Help with Sql query for comparing string(HH24MI) to date

We have a configuration table as shown below that stores the start time and the duration.
If the start time is 9:20 pm (3rd one ) add the duration then the time becomes 9:35.
I have to find out if the current time is in between any of the values.
I have to return the output based on the start_time and duration. i.e current time should be between start_time and the start_time + duration. (between 09:20 and and 09:35)
Can you please help me with the sql query or is it better if we go with sql function?
Start_time, duration(minutes) output
1108 5 2
1054 100 5
2120 15 8
I'm not a fan of storing dates and times in VARCHAR2 columns. START_TIME should really be a DATE or a TIMESTAMP column.
That said, you can do something like
with x as (
select '1108' start_time, 5 duration, 2 output from dual
union all
select '1054', 100, 5 from dual
union all
select '2120', 15, 8 from dual
)
select *
from (
select to_date(
to_char(sysdate,'YYYY-MM-DD') || ' ' ||
start_time,
'YYYY-MM-DD HH24MI' ) start_date,
to_date(
to_char(sysdate,'YYYY-MM-DD') || ' ' ||
start_time,
'YYYY-MM-DD HH24MI' ) + duration/24/60 end_date
from x)
where sysdate between start_date and end_date
The following selects all rows where sysdate is within the Start_Time and Start_Time + duration (EDITed as per comment from OP):
SELECT (TRUNC ( SYSDATE ) + TO_NUMBER ( SUBSTR ( Start_Time, 0, 2 ) ) / 24.0 + TO_NUMBER ( SUBSTR ( Start_Time, 3 ) ) / (24.0 * 60.0)) start_date, (TRUNC ( SYSDATE ) + TO_NUMBER ( SUBSTR ( Start_Time, 0, 2 ) ) / 24.0 + TO_NUMBER ( SUBSTR ( Start_Time, 3 ) ) / (24.0 * 60.0) + TO_NUMBER (duration)) end_date FROM configtable;