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;
Related
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
I have two int columns:
thedate - for example 20210512
thetime - for example 142342
So, i need to unite them to one column to check if the time difference is lower the 5.
I tried this:
TO_DATE(sysdate) - TO_DATE(thedate, 'YYYY-MM-DD') < 5
But it only for the date not for the time so i will be glad to know how to unite the two int columns + and convert it to date type for time difference.
You can use:
SELECT *
FROM table_name
WHERE TO_DATE( thedate * 1000000 + thetime, 'YYYYMMDDHH24MISS' ) > SYSDATE - 5;
Which, for the sample data:
CREATE TABLE table_name ( thedate INT, thetime INT );
INSERT INTO TABLE_NAME ( thedate, thetime ) VALUES (
TO_NUMBER( TO_CHAR( SYSDATE, 'YYYYMMDD' ) ),
TO_NUMBER( TO_CHAR( SYSDATE, 'HH24MISS' ) )
);
INSERT INTO TABLE_NAME ( thedate, thetime ) VALUES (
TO_NUMBER( TO_CHAR( SYSDATE - INTERVAL '4 23' DAY TO HOUR, 'YYYYMMDD' ) ),
TO_NUMBER( TO_CHAR( SYSDATE - INTERVAL '4 23' DAY TO HOUR, 'HH24MISS' ) )
);
INSERT INTO TABLE_NAME ( thedate, thetime ) VALUES (
TO_NUMBER( TO_CHAR( SYSDATE - INTERVAL '5 1' DAY TO HOUR, 'YYYYMMDD' ) ),
TO_NUMBER( TO_CHAR( SYSDATE - INTERVAL '5 1' DAY TO HOUR, 'HH24MISS' ) )
);
Outputs:
THEDATE
THETIME
20210512
131832
20210507
141832
Or, if you want to use indexes on the thedate and thetime columns (the query above would not use indexes on thedate and thetime columns but would require a function-based index) then:
SELECT *
FROM table_name
WHERE thedate > TO_NUMBER( TO_CHAR( SYSDATE - 5, 'YYYYMMDD' ) )
OR ( thedate = TO_NUMBER( TO_CHAR( SYSDATE - 5, 'YYYYMMDD' ) )
AND thetime >= TO_NUMBER( TO_CHAR( SYSDATE - 5, 'HH24MISS' ) )
)
However, the better solution is to use appropriate data-types for your data; in this case, you should store date values in a DATE data-type (which, in Oracle, contains year-second components) rather than as two INT values for date and time.
db<>fiddle here
You want to convert to a timestamp, not a date. So:
select to_timestamp(cast(20210512 * 1000000 + 142342 as varchar2(255)), 'YYYYMMDDHH24MISS')
from dual;
Here is a db<>fiddle showing that this works.
Or in a where clause:
to_timestamp(cast(thedate * 1000000 + thetime as varchar2(255)), 'YYYYMMDDHH24MISS') > sysdate - interval '5' day
(or whatever you mean by "5").
Note: You may want to add a computed column to the table that has the full timestamp:
alter table t add column timestamp generated always as
( to_timestamp(cast(thedate * 10000 + thetime as varchar2(255)), 'YYYYMMDDHH24MISS') );
I have a column called duration_d which is varchar2 and the data in that table looks like below
duration_d
-----------
12:25
01:35
12:10
04:21
12:18
12:24
I tried below query
SELECT SUM( to_date( duration_d, 'mi:ss' ))
FROM table
GROUP BY calling_number;
When I execute it following error is coming
ORA-00933: SQL command not properly ended
00933. 00000 - "SQL command not properly ended"
can any one tell me how to make sum it?
To get the total as fractions of a day you can use:
SELECT SUM( TO_DATE( duration_d, 'MI:SS' ) - TO_DATE( '00:00', 'MI:SS' ) ) AS total
FROM your_table
Which gives the result:
TOTAL
------------------------------------------
0.0383449074074074074074074074074074074074
To convert this to an interval data type you can use NUMTODSINTERVAL:
SELECT NUMTODSINTERVAL(
SUM( TO_DATE( duration_d, 'MI:SS' ) - TO_DATE( '00:00', 'MI:SS' ) ),
'DAY'
) AS total
FROM your_table
Which gives the result:
TOTAL
-------------------
+00 00:55:13.000000
Please try below:
with x as
(select sum((regexp_substr(YOUR_COLUMN, '[0-9]+', 1, 1)*60) +
regexp_substr(id, '[0-9]+', 1, 2)) seconds
from YOUR_TABLE)
SELECT
TO_CHAR(TRUNC(seconds/3600),'FM9900') || ':' ||
TO_CHAR(TRUNC(MOD(seconds,3600)/60),'FM00') || ':' ||
TO_CHAR(MOD(seconds,60),'FM00')
FROM x
Will work only if the duration is always [MI:SS].
Also you can add the group by as per your requirement.
Converting Seconds to the required duration format Reference.
Group By
with x as
(select calling_number,sum((regexp_substr(YOUR_COLUMN, '[0-9]+', 1, 1)*60) +
regexp_substr(id, '[0-9]+', 1, 2)) seconds
from YOUR_TABLE
group by calling_number)
SELECT calling_number,
TO_CHAR(TRUNC(seconds/3600),'FM9900') || ':' ||
TO_CHAR(TRUNC(MOD(seconds,3600)/60),'FM00') || ':' ||
TO_CHAR(MOD(seconds,60),'FM00')
FROM x
Use a combination of SUBSTR, to_char, to_date, NVL, INSTR, reverse and SUM.
SELECT "calling_number",
to_char(to_date(SUM(NVL(SUBSTR("duration_d", 0, INSTR("duration_d", ':')-1), "duration_d"))*60 +
SUM(substr("duration_d", - instr(reverse("duration_d"), ':') + 1)),'sssss'),'hh24:mi:ss') AS SUM_DURATION_D
FROM yourtable
GROUP BY "calling_number"
Output
calling_number SUM_DURATION_D
1 00:26:10
2 00:29:03
SQL Fiddle: http://sqlfiddle.com/#!4/9b0a81/33/0
Correct spelling as below
SELECT SUM( TO_DATE( duration_d, 'mi:ss' ) )
FROM YOURTABLE Group By calling_number
My Sample Table looks like this ..
Primary_Key || Start_Time || End_Time || Value
1 || 24-FEB-13 18:00:00 || 24-FEB-13 19:00:00 || 6
The data types of these columns are
Primary_Key -- Int
Start_Time --- TimeStamp
End_Time --- TimeStamp
Value --- Int
Now , i want to fetch all the records in the table which have the End_Time value within the 60 mins of the current time ?
Try this...
SELECT * FROM yourTable
WHERE end_time >= CURRENT_TIMESTAMP - NUMToDSInterval(1, 'HOUR')
That should work with DATE and TIMESTAMP and TIMESTAMP WITH TIME ZONE values.
This should work:
select *
from sample s
where CURRENT_TIMESTAMP - end_time <= 1/24.0 and CURRENT_TIMESTAMP >= end_time
Try this
select * from yourtable
WHERE (sysdate - End_Time) * 1440 between 0 and 60
You may try this - select your data between your_start_date and your_start_date + INTERVAL '60' MINUTE:
SELECT SYSDATE start_dt, (SYSDATE + INTERVAL '60' MINUTE) end_dt
FROM dual
/
SELECT * FROM
(
SELECT to_date('24-FEB-13 18:00:00', 'DD-MON-YYYY HH24:MI:SS') your_start_date
,(to_date('24-FEB-13 18:00:00', 'DD-MON-YYYY HH24:MI:SS') + INTERVAL '60' MINUTE) your_end_dt
FROM dual
)
/
Please help me to solve this.
I have a table that contain users check in (checktype = I) and check out (checktype = 0) time everyday, and I would like to get the total amount of check in time per user which occur > 08:00 AM in a specific date range.
I am using the query below, but only handle one day per query not in a range, so I have to loop using javascript to get the amount of delay ( > 08:00 AM) per user for example from 01/06/2012 to 06/06/2012
Please help me to get the amount (count) check in time > 08:00 AM per user (ex: userid 708) from ex:01/06/2012 to 06/06/2012 in a single query.
with tt as
(
select TO_DATE('01/06/2012 08:00:00','dd/mm/yyyy hh24:mi:ss') date1 ,
checktime date2
from
checkinout
where
userid = '708' and
to_char(checktime,'dd/mm/yyyy') = '01/06/2012' and
checktype='I' -- checktype I is check in
) , t2 as
(
select numtodsinterval(date2 - date1,'day') dsinterval from tt
)
select extract(hour from dsinterval) || ' hours ' ||
extract(minute from dsinterval) || ' minutes ' ||
round(extract(second from dsinterval)) || ' seconds' late from t2
I assume you wanted to get how many hours late (i.e. after 08:00) the checkins have been done:
with t2 as (
select userid
,numtodsinterval(sum(checktime - (trunc(checktime)+8/24)),'day') dsinterval
,count(1) cnt
from checkinout
where userid='708'
and checktime > trunc(checktime)+8/24
and trunc(checktime) between to_date('01/06/2012','DD/MM/YYYY') and to_date('06/06/2012','DD/MM/YYYY')
and checktype = 'I'
group by userid
)
select extract(hour from dsinterval) || ' hours ' ||
extract(minute from dsinterval) || ' minutes ' ||
round(extract(second from dsinterval)) || ' seconds' late
,cnt
from t2;
See http://sqlfiddle.com/#!4/c4670/11 for my test case.
edit: added column "cnt" to show how many times
Consider the following example on base of this you can write your own logic
WITH tbl AS
(SELECT SYSDATE dt
FROM DUAL
UNION
SELECT SYSDATE + (1 + (10 / 1440))
FROM DUAL
UNION
SELECT SYSDATE + (2 + (12 / 1440))
FROM DUAL
UNION
SELECT SYSDATE + (3 + (13 / 1440))
FROM DUAL
UNION
SELECT SYSDATE + (6 + (15 / 1440))
FROM DUAL
UNION
SELECT SYSDATE + (8 + (18 / 1440))
FROM DUAL)
SELECT EXTRACT (HOUR FROM dsinterval)
|| ' hours '
|| EXTRACT (MINUTE FROM dsinterval)
|| ' minutes '
|| ROUND (EXTRACT (SECOND FROM dsinterval))
|| ' seconds' late
FROM (SELECT NUMTODSINTERVAL (dt1 - dt2, 'day') dsinterval
FROM (SELECT TO_DATE (TO_CHAR (dt, 'DD/MM/YYYY') || ' 08:00:00',
'DD/MM/YYYY HH24:MI:SS'
) dt1,
TO_DATE (TO_CHAR (dt, 'DD/MM/YYYY HH24:MI:SS'),
'DD/MM/YYYY HH24:MI:SS'
) dt2
FROM tbl
WHERE dt BETWEEN SYSDATE + 2 AND SYSDATE + 5))
As per code you can write like
SELECT EXTRACT (HOUR FROM dsinterval)
|| ' hours '
|| EXTRACT (MINUTE FROM dsinterval)
|| ' minutes '
|| ROUND (EXTRACT (SECOND FROM dsinterval))
|| ' seconds' late
FROM (SELECT NUMTODSINTERVAL (dt1 - dt2, 'day') dsinterval
FROM (SELECT TO_DATE (TO_CHAR (checktime , 'DD/MM/YYYY') || ' 08:00:00',
'DD/MM/YYYY HH24:MI:SS'
) dt1,
TO_DATE (checktime, 'DD/MM/YYYY HH24:MI:SS') dt2
FROM checkinout
WHERE checktime BETWEEN start_date AND end_date
AND checktype='I'))