Oracle SQL - Calculate number of concurrent phone calls - sql

First of all, I am not very experienced with SQL. I have found similar questions here before but so far I was not able to develop a working solution for my specific problem.
I have a table that holds phone call records which has the following fields:
END: Holds the timestamp of when a call ended - Data Type: DATE
LINE: Holds the phone line that was used for a call - Data Type: NUMBER
CALLDURATION: Holds the duration of a call in seconds - Data Type: NUMBER
The table has entries like this:
END LINE CALLDURATION
---------------------- ------------------- -----------------------
25/01/2012 14:05:10 6 65
25/01/2012 14:08:51 7 1142
25/01/2012 14:20:36 5 860
I need to create a query that returns the number of concurrent phone calls based on the data from that table. The query should be capable of calculating that number in fixed intervals, such as every 5 minutes.
To make this more clear, here is an example of what the query should return (based on the example entries from the previous table:
TIMESTAMP CURRENTLYUSEDLINES
---------------------- -------------------
25/01/2012 14:05:00 2
25/01/2012 14:10:00 1
25/01/2012 14:15:00 1
How can I do this with a (Oracle) SQL query? There are currently almost 1 million records in the table so the query must be as fast as possible because otherwise it would take forever to execute it.

One solution would be this one:
WITH t AS
(SELECT TIMESTAMP '2012-01-25 14:00:00' + LEVEL * INTERVAL '5' MINUTE AS TS
FROM dual
CONNECT BY TIMESTAMP '2012-01-25 14:00:00' + LEVEL * INTERVAL '5' MINUTE <= TIMESTAMP '2012-01-25 14:15:00'),
calls AS
(SELECT TIMESTAMP '2012-01-25 14:05:10' AS END_TIME, 6 AS LINE, 65 AS duration FROM dual
UNION ALL SELECT TIMESTAMP '2012-01-25 14:08:51', 7, 1142 FROM dual
UNION ALL SELECT TIMESTAMP '2012-01-25 14:20:36', 5, 860 FROM dual)
SELECT TS, count(distinct line)
FROM t
LEFT OUTER JOIN calls ON ts BETWEEN END_TIME - duration * INTERVAL '1' SECOND AND END_TIME
GROUP BY ts
HAVING count(distinct line) > 0
ORDER BY ts;
TS COUNT(DISTINCTLINE)
-------------------- -------------------
25.01.2012 14:05:00 2
25.01.2012 14:10:00 1
25.01.2012 14:15:00 1
3 rows selected.

Related

Oracle SQL, Recursion, Spreadsheet calculation

I want to transfer a calculation from an Excel spreadsheet to an Oracle SQL Query.
There are three predefined columns ID, IncommingDate and ProcessingTime.
Now I want to calculate two additional columns namely Processing Start and Processing End.
The result should look as follows:
With the Formulas:
One can see that the ProcessingStart of one entry should be the maximum of its IncommingDate and the ProcessingEnd of the previous entry.
How can I achieve this using SQL?
I have prepared an example query here:
WITH example AS
(
SELECT
1 AS id,
to_date ('01.01.2018 00:00:00','dd.MM.yyyy HH24:mi:ss') AS IncommingDate,
60 AS "Processing Time [sec.]"
FROM
dual
UNION ALL
SELECT
2,
to_date ('01.01.2018 00:05:00','dd.MM.yyyy HH24:mi:ss'),
60
FROM
dual
UNION ALL
SELECT
3,
to_date ('01.01.2018 00:05:30','dd.MM.yyyy HH24:mi:ss'),
60
FROM
dual
UNION ALL
SELECT
4,
to_date ('01.01.2018 00:10:00','dd.MM.yyyy HH24:mi:ss'),
60
FROM
dual
)
SELECT
*
FROM
example
Does anybody of you knows a way to do this?
It looks like you need to use recursive subquery factoring:
with rcte (id, IncommingDate, ProcessingTime, ProcessingStart, ProcessingEnd) as (
select id,
IncommingDate,
ProcessingTime,
IncommingDate,
IncommingDate + (ProcessingTime/86400)
from example
where id = 1
union all
select e.id,
e.IncommingDate,
e.ProcessingTime,
greatest(e.IncommingDate, r.ProcessingEnd),
greatest(e.IncommingDate, r.ProcessingEnd) + (e.ProcessingTime/86400)
from rcte r
-- assumes IDs are the ordering criteris and are contiguous
join example e on e.id = r.id + 1
)
select * from rcte;
ID INCOMMINGDATE PROCESSINGTIME PROCESSINGSTART PROCESSINGEND
---------- ------------------- -------------- ------------------- -------------------
1 2018-01-01 00:00:00 60 2018-01-01 00:00:00 2018-01-01 00:01:00
2 2018-01-01 00:05:00 60 2018-01-01 00:05:00 2018-01-01 00:06:00
3 2018-01-01 00:05:30 60 2018-01-01 00:06:00 2018-01-01 00:07:00
4 2018-01-01 00:10:00 60 2018-01-01 00:10:00 2018-01-01 00:11:00
The anchor member is ID 1, and can do a simple calculation for that first step to get the start/end times.
The recursive member then find the next original row and uses greatest() to decide whether to do its calculations based on it's incoming time or the previous end time.
This is assuming that the ordering is based on the IDs, and that they are contiguous. If that isn't how you are actually ordering then it's only a bit more complicated.

Getting the days between two dates for multiple IDs

This is a follow up question from How to get a list of months between 2 given dates using a query?
really. (I suspect it's because I don't quite understand the logic behind connect by level clauses !)
What I have is a list of data like so
ID | START_DATE | END_DATE
1 | 01-JAN-2018 | 20-JAN-2018
2 | 13-FEB-2018 | 20-MAR-2018
3 | 01-MAR-2018 | 07-MAR-2018
and what I want to try and get is a list with all the days between the start and end date for each ID.
So for example I want a list which gives
ID | DATE
1 | 01-JAN-2018
1 | 02-JAN-2018
1 | 03-JAN-2018
...
1 | 19-JAN-2018
1 | 20_JAN-2018
2 | 13-FEB-2018
2 | 14-FEB-2018
2 | 15-FEB-2018
...
etc.
What I've tried to do is adapt one of the answers from the above link as follows
select id
, trunc(start_date+((level-1)),'DD')
from (
select id
, start_date
, end_date
from blah
)
connect by level <= ((trunc(end_date,'DD')-trunc(start_date,'DD'))) + 1
which gives me what I want but then a whole host of duplicate dates as if it's like a cartesian join. Is there something simple I need to add to fix this?
I like recursive CTEs:
with cte as (
select id, start_dte as dte, end_dte
from blah
union all
select id, dte + 1, end_dte
from cte
where dte < end_dte
)
select *
from cte
order by id, dte;
This is ANSI standard syntax and works in several other databases.
The hierarchical query you were trying to do needs to include id = prior id in the connect-by clause, but as that causes loops with multiple source rows you also need to include a call to a non-deterministic function, such as dbms_random.value:
select id, start_date + level - 1 as day
from blah
connect by level <= end_date - start_date + 1
and prior id = id
and prior dbms_random.value is not null
With your sample data in a CTE, that gets 63 rows back:
with blah (ID, START_DATE, END_DATE) as (
select 1, date '2018-01-01', date '2018-01-20' from dual
union all select 2, date '2018-02-13', date '2018-03-20' from dual
union all select 3, date '2018-03-01', date '2018-03-07' from dual
)
select id, start_date + level - 1 as day
from blah
connect by level <= end_date - start_date + 1
and prior id = id
and prior dbms_random.value is not null;
ID DAY
---------- ----------
1 2018-01-01
1 2018-01-02
1 2018-01-03
...
1 2018-01-19
1 2018-01-20
2 2018-02-13
2 2018-02-14
...
3 2018-03-05
3 2018-03-06
3 2018-03-07
You don't need to trunc() the dates unless they have non-midnight times, which seems unlikely in this case, and even then it might not be necessary if only the end-date has a later time (like 23:59:59).
A recursive CTE is more intuitive in many ways though, at least once you understand the basic idea of them; so I'd probably use Gordon's approach too. There can be differences in performance and whether they work at all for large amounts of data (or generated rows), but for a lot of data it's worth comparing different approaches to find the most suitable/performant anyway.

How do I extract the time-of-day from a date in Oracle SQL?

I have a SQL database of dates and times, but they are combined into a single value (called time). I would like to determine the number of entries per every hour, but I cannot figure out how to do this.
If I do:
Select time from time_table
The result I get is:
2016-05-13 07:23:23
2016-05-13 07:34:34
2016-05-14 07:21:00
2016-05-14 09:42:43
What I would like is either:
07:23:23
07:34:34
07:21:00
09:42:43
which I can sort to get:
07:21:00
07:23:23
07:34:34
09:42:43
Or, ultimately, I'd like to get counts:
07-08: 3
08-09: 0
09-10: 1
Is there any way to do this?
Time-of-day can be extracted from a value of DATE datatype by subtracting the truncation of that value:
select time - trunc(time) from.....
This will return a fractional number, >= 0 and < 1, representing a fraction of a day. To convert it to hour: multiply by 24.
For example, it is ~ 8:08 pm where I am right now. Then:
select 24 * (sysdate - trunc(sysdate)) as result from dual;
RESULT
----------
20.1566667
So now, if I want to know the hour, I can take trunc() again. Earlier I truncated a date; now the result is a number and I truncate a number.
Putting it all together (the first part generates the whole numbers from 0 to 23):
with h ( hr ) as (select level - 1 from dual connect by level <= 24)
select h.hr, count(tt.time) as ct
from h left outer join time_table tt on h.hr = trunc(24 * (tt.time - trunc(tt.time)))
group by h.hr
order by hr -- optional
;
You can format the "hour" column differently, although I don't see the point; my solution will show 8 where you show 08-09. Both have the same information.
Here is one solution with two sub-queries, one to generate a complete list of hours and one to generate a list of numbers in the target table. An outer join is necessary to produce zero counts for hours with no matches.
Note that bucket labels such as 7-8, 8-9 imply double counting, so I've ignored that in favour of single hours.
SQL> select * from t42
2 /
ID TCOL
---------- -------------------
1 2016-05-13 07:23:23
2 2016-05-13 07:34:34
3 2016-05-14 07:21:00
4 2016-05-14 09:42:43
SQL> with hrs as (select level-1 as hr
2 from dual
3 connect by level <= 24 )
4 , t as (select to_number(to_char(tcol, 'HH24')) as hr
5 from t42)
6 select hrs.hr
7 , count(t.hr)
8 from hrs
9 left outer join t
10 on hrs.hr = t.hr
11 where hrs.hr between 7 and 9
12 group by hrs.hr
13 order by hrs.hr
14 /
HR COUNT(T.HR)
---------- -----------
7 3
8 0
9 1
SQL>
This solution assumes the time column is an Oracle date datatype. If it's a string then either casting it to a date first or applying a substr() would work.
this will help you to solve you problem taken from
Counting number of records hour by hour between two dates in oracle
SELECT
trunc(time ,'HH'),
count(*)
FROM
time_table
WHERE
time > trunc(SYSDATE -2)
group by trunc(time ,'HH');
Result
2016-11-29 09:00:00.0 | 8 |

SQL getting datediff from same field

I have a problem. I need to get the date difference in terms of hours in my table but the problem is it is saved in the same field. This is my table would look like.
RecNo. Employeeno recorddate recordtime recordval
1 001 8/22/2014 8:15 AM 1
2 001 8/22/2014 5:00 PM 2
3 001 8/24/2014 8:01 AM 1
4 001 8/24/2014 5:01 PM 2
1 indicates time in and 2 indicates time out. Now, How will i get the number of hours worked for each day? What i want to get is something like this.
Date hoursworked
8/22/2014 8
8/24/2014 8
I am using VS 2010 and SQL server 2005
You could self-join each "in" record with its corresponding "out" record and use datediff to subtract them:
SELECT time_in.employeeno AS "Employee No",
time_in.recorddate AS "Date",
DATEDIFF (hour, time_in.recordtime, time_out.recordtime)
AS "Hours Worked"
FROM (SELECT *
FROM my_table
WHERE recordval = 1) time_in
INNER JOIN (SELECT *
FROM my_table
WHERE recordval = 2) time_out
ON time_in.employeeno = time_out.employeeno AND
time_in.recorddate = time_out.recorddate
If you always record time in and time out for every employee, and just one per day, using a self-join should work:
SELECT
t1.Employeeno,
t1.recorddate,
t1.recordtime AS [TimeIn],
t2.recordtime AS [TimeOut],
DATEDIFF(HOUR,t1.recordtime, t2.recordtime) AS [HoursWorked]
FROM Table1 t1
INNER JOIN Table1 t2 ON
t1.Employeeno = t2.Employeeno
AND t1.recorddate = t2.recorddate
WHERE t1.recordval = 1 AND t2.recordval = 2
I included the recordtime fields as time in, time out, if you don't want them just remove them.
Note that this datediff calculation gives 9 hours, and not 8 as you suggested.
Sample SQL Fiddle
Using this sample data:
with table1 as (
select * from ( values
(1,'001', cast('20140822' as datetime),cast('08:15:00 am' as time),1)
,(2,'001', cast('20140822' as datetime),cast('05:00:00 pm' as time),2)
,(3,'001', cast('20140824' as datetime),cast('08:01:00 am' as time),1)
,(4,'001', cast('20140824' as datetime),cast('04:59:00 pm' as time),2)
,(5,'001', cast('20140825' as datetime),cast('10:01:00 pm' as time),1)
,(6,'001', cast('20140826' as datetime),cast('05:59:00 am' as time),2)
)data(RecNo,EmployeeNo,recordDate,recordTime,recordVal)
)
this query
SELECT
Employeeno
,convert(char(10),recorddate,120) as DateStart
,convert(char(5),cast(TimeIn as time)) as TimeIn
,convert(char(5),cast(TimeOut as time)) as TimeOut
,DATEDIFF(minute,timeIn, timeOut) / 60 AS [HoursWorked]
,DATEDIFF(minute,timeIn, timeOut) % 60 AS [MinutesWorked]
FROM (
SELECT
tIn.Employeeno,
tIn.recorddate,
dateadd(minute, datediff(minute,0,tIn.recordTime), tIn.recordDate)
as TimeIn,
( SELECT TOP 1
dateadd(minute, datediff(minute,0,tOut.recordTime), tOut.recordDate)
as TimeOut
FROM Table1 tOut
WHERE tOut.RecordVal = 2
AND tOut.EmployeeNo = tIn.EmployeeNo
AND tOut.RecNo > tIn.RecNo
ORDER BY tOut.EmployeeNo, tOut.RecNo
) as TimeOut
FROM Table1 tIn
WHERE tIn.recordval = 1
) T
yields (as desired)
Employeeno DateStart TimeIn TimeOut HoursWorked MinutesWorked
---------- ---------- ------ ------- ----------- -------------
001 2014-08-22 08:15 17:00 8 45
001 2014-08-24 08:01 16:59 8 58
001 2014-08-25 22:01 05:59 7 58
No assumptions are made about shifts not running across midnight (see case 3).
This particular implementation may not be the most performant way to construct this correlated subquery, so if there is a performance problem come back and we can look at it again. However running those tests requires a large dataset which I don't feel like constructing just now.

How to build a daily history from a set of rows in SQL?

I have a simple SQLite table that records energy consumption all day long. It looks like that:
rowid amrid timestamp value
---------- ---------- ---------- ----------
1 1 1372434068 5720
2 2 1372434075 0
3 3 1372434075 90
4 1 1372434078 5800
5 2 1372434085 0
6 3 1372434085 95
I would like to build a simplified history of the consumption of the last day by getting the closest value for every 10 minutes to build a CSV which would look like:
date value
---------------- ----------
2013-07-01 00:00 90
2013-07-01 00:10 100
2013-07-01 00:20 145
As for now I have a request that allows me to get the closest value for one timestamp:
SELECT *
FROM indexes
WHERE amrid=3
ORDER BY ABS(timestamp - strftime('%s','2013-07-01 00:20:00'))
LIMIT 1;
How can I build a request that would do the trick to get it for the whole day?
Thanks,
Let me define "closest value" as the first value after each 10-minute interval begins. You can generalize the idea to other definitions, but I think this is easiest to explain the idea.
Convert the timestamp to a string value of the form "yyyy-mm-dd hhMM". This string is 15 characters long. The 10-minute interval would be the first 14 characters. So, using this as an aggregation key, calculate the min(id) and use this to join back to the original data.
Here is the idea:
select isum.*
from indexes i join
(select substr(strftime('%Y-%m-%d %H%M', timestamp), 1, 14) as yyyymmddhhm,
min(id) as whichid
from indexes
group by substr(strftime('%Y-%m-%d %H%M', timestamp), 1, 14)
) isum
on i.id = isum.whichid