oracle olap query execution is taking too long - oracle

I have these following tables:
1) date_table_dim
2) clock_table_dim
3) onlinegpspoint : which contains our main information for olap reports
And also there is a sql query like this:
SELECT
date_table_dim.day_id day_id,
clock_table_dim.hour_id hour_id
FROM onlinegpspoint olgps
INNER JOIN date_table_dim
ON (
olgps.occurance_time >= to_date('2014-03-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss')
AND olgps.occurance_time >= date_table_dim.day_id
AND olgps.occurance_time < date_table_dim.day_id + 1
)
INNER JOIN clock_table_dim
ON ( clock_table_dim.hour_id <= TO_NUMBER(TO_CHAR(occurance_time, 'HH24'))
AND clock_table_dim.hour_id > TO_NUMBER(TO_CHAR((occurance_time - 1/24), 'HH24') ))
GROUP BY
date_table_dim.day_id,
clock_table_dim.hour_id ;
My problem is that this query is taking too long to execute.
What actions can be taken to improve the performance of this query execution?
EDIT
There is an index for occurance_time on onlinegpspoint. By this query I want to get some Olap information for 1 hour period of time. (This query is a kind of summary of my fact_table query.)

You can try following query.
SQLFiddle
with t as (select d.day_id, o.occurance_time ot
from onlinegpspoint o
join date_table_dim d on ( o.occurance_time >= date '2014-03-01'
and d.day_id <= o.occurance_time and o.occurance_time < d.day_id + 1) )
select day_id, c.hour_id
from t join clock_table_dim c on (
c.hour_id <= to_char(t.ot, 'HH24') and to_char((t.ot - 1/24), 'HH24') < c.hour_id )
group by day_id, c.hour_id order by day_id, c.hour_id;
In your original query you compare to_char(occurance_time, 'HH24') with hour_id, here index probably does not work.
So idea is to firstly filter data to interesting period and then work only with these filtered data.
There is one more query worth trying, which gave me promising results:
select distinct trunc(occurance_time) day_id, to_char(occurance_time, 'hh24')+0 hour_id
from onlinegpspoint o join (
select to_date(to_char(day_id, 'yyyy-mm-dd ')||' '
||lpad(hour_id, 2, 0), 'yyyy-mm-dd hh24') dt
from date_table_dim, clock_table_dim) d
on (o.occurance_time >= date '2014-03-01'
and d.dt-1/24 <= o.occurance_time and o.occurance_time < d.dt)

Related

Oracle SQL: Return a set without duplicates

Using the following query is working for me except that I need to only return one result for each TPHONE.ID.
What I'm trying to accomplish is to check two tables for three date fields (two in the TPHONE table and one in the TPHONEREQUEST table and return the TPHONE.ID of any place where I find dates within a particular range. However, if more than one entry in either table has one or more of the dates within the date range I'd still only want to return the TPHONE.ID once.
SELECT
TPHONE.ID,
TPHONE.LOCATIONID,
TPHONE.DLASTCHANGED,
TPHONE.D02,
TPHONEREQUEST.D03
FROM
TPHONE, TPHONEREQUEST
WHERE
TPHONE.ID = TPHONEREQUEST.DEVICEID
AND TPHONE.ID IN
(
SELECT
TPHONE.ID
FROM
TPHONE
WHERE
TPHONE.DLASTCHANGED >= '7/1/2019' AND TPHONE.DLASTCHANGED < '10/1/2019'
OR TPHONE.D02 >= '7/1/2019' AND TPHONE.D02 < '10/1/2019'
OR TPHONEREQUEST.D03 >= '7/1/2019' AND TPHONEREQUEST.D03 < '10/1/2019'
)
ORDER BY
TPHONE.LOCATIONID, TPHONE.ID
You can use aggregation and filter with a HAVING clause:
SELECT p.ID,
FROM
TPHONE p
INNER JOIN TPHONEREQUEST t ON p.ID = r.DEVICEID
GROUP BY p.ID
HAVING
MAX(
CASE WHEN
(
p.DLASTCHANGED >= TO_DATE('07/01/2019', 'DD/MM/YYYY')
AND p.DLASTCHANGED < TO_DATE('10/1/2019', 'DD/MM/YYYY')
) OR (
p.D02 >= TO_DATE('07/01/2019', 'DD/MM/YYYY')
AND p.D02 < TO_DATE('10/1/2019', 'DD/MM/YYYY')
) OR (
r.D03 >= TO_DATE('07/01/2019', 'DD/MM/YYYY')
AND r.D03 < TO_DATE('10/1/2019', 'DD/MM/YYYY')
)
THEN 1
END
) = 1
Notes:
this returns only TPHONE.ID, since this seems to be what you are looking for; if you want more columns, then you can add them to the SELECT clause and to the GROUP BY clause (beware that this might change the grouping condition though, if these columns are not functionnaly dependant on TPHONE.ID)
I used table aliases to shorten the query
I used TO_DATE() to generate proper dates instead of relying on the default format of the database (which may change across databses and sessions). There is an assumption here that the format of your dates is DD/MM/YYYY (it could also be MM/DD/YYYY)
You could use ROW_NUMBER to enumerate the rows with the same ID and then select only the first instance:
SELECT TPHONE.ID,
TPHONE.LOCATIONID,
TPHONE.DLASTCHANGED,
TPHONE.D02,
TPHONEREQUEST.D03
FROM (
SELECT TPHONE.ID,
TPHONE.LOCATIONID,
TPHONE.DLASTCHANGED,
TPHONE.D02,
TPHONEREQUEST.D03,
ROW_NUMBER() over (PARTITION BY TPHONE.ID ORDER BY TPHONE.ID) as RN
FROM TPHONE,
TPHONEREQUEST
WHERE TPHONE.ID = TPHONEREQUEST.DEVICEID
AND TPHONE.ID IN
(
SELECT TPHONE.ID
FROM TPHONE
WHERE TPHONE.DLASTCHANGED >= '7/1/2019' AND TPHONE.DLASTCHANGED < '10/1/2019'
OR TPHONE.D02 >= '7/1/2019' AND TPHONE.D02 < '10/1/2019'
OR TPHONEREQUEST.D03 >= '7/1/2019' AND TPHONEREQUEST.D03 < '10/1/2019'
)
ORDER BY TPHONE.LOCATIONID, TPHONE.ID
)
WHERE RN = 1

How to avoid Internal_Function by TO_CHAR IN Oracle

I'm using Oracle 12C and I have the following code:
SELECT *
FROM
(
SELECT
*
FROM
t.tablea
WHERE
name = 'FIS'
) A
LEFT JOIN (
SELECT
*
FROM
t.tableb
WHERE
enabled = 1
) B ON b.id = a.id
AND TO_CHAR(b.createdate, 'dd-mm-yy hh24:mi') = TO_CHAR(a.createdate, 'dd-mm-yy hh24:mi')
Both a and b createdate are timestamp datatype.
Optimizer return an internal_function at TO_CHAR(b.createdate, 'dd-mm-yy hh24:mi') = TO_CHAR(a.createdate, 'dd-mm-yy hh24:mi') in Execution Plan
If I compare like this: 'AND b.createdate = a.createdate', It will lost 1000 rows that look like this '11-JUN-18 04.48.34.269928000 PM'. And If I change 269928000 to 269000000 It will work
Now, I don't want to using to_char to avoid internal_function(must create Function-based-Index)
Anyone can help me?
If I compare like this: AND b.createdate = a.createdate, It will lost 1000 rows that look like this 11-JUN-18 04.48.34.269928000 PM. And If I change 269928000 to 269000000 It will work
Your values appear to have a fractional seconds component and would have the TIMESTAMP data type. If so, you can use TRUNC( timestamp_value, 'MI' ) to truncate to the nearest minute.
SELECT *
FROM t.tablea a
LEFT OUTER JOIN t.tableb b
ON ( a.createdate >= TRUNC( b.createdate, 'MI' )
AND a.createdate < TRUNC( b.createdate, 'MI' ) + INTERVAL '1' MINUTE
AND a.id = b.id
AND b.enabled = 1
)
WHERE a.name = 'FIS'
This will remove the need to apply a function to one of the two tables (a.createdate in this case but you could swap them).
I don't even see the need for the subqueries:
SELECT a.*, b.*
FROM t.tablea a
LEFT JOIN t.tableb b
ON a.id = b.id AND
TRUNC(b.createdate, 'MI') = TRUNC(a.createdate, 'MI') AND
b.enabled = 1
WHERE
a.name = 'FIS'

ORACLE Query Count Slow

I'm trying to query my Oracle script on Toad but got slow response, about 4-8 seconds.
The script query is about count, below is mine:
SELECT COUNT(*)
AS TOTALS
FROM(SELECT S.BADGEID_FK, S.SHIFT, S.STATUS, E.BADGEID, E.FIRSTNAME, E.LASTNAME
FROM WA_SEW_TBL_EMP_INFO S, WA_GA_TBL_EMPLOYEES E
WHERE S.BADGEID_FK = E.BADGEID AND S.STATUS = 'Attend' AND S.SHIFT = 'Morning'
AND S.BADGEID_FK NOT IN(SELECT EMPID
FROM WA_SEW_TBL_RESULTS
WHERE TO_CHAR(SYSTEM_DATE, 'YYYY-MM-DD') = '2017-08-30'
AND TO_CHAR(SYSTEM_DATE, 'HH24:MI') >= '07:00'
AND TO_CHAR(SYSTEM_DATE, 'HH24:MI') <= '19:29'))
I tried to add indexing to some column, but there is no effect.
Is there any way to do that query? or any trick?
This part:
WHERE TO_CHAR(SYSTEM_DATE, 'YYYY-MM-DD') = '2017-08-30'
AND TO_CHAR(SYSTEM_DATE, 'HH24:MI') >= '07:00'
AND TO_CHAR(SYSTEM_DATE, 'HH24:MI') <= '19:29'
Would be better rewritten as:
WHERE SYSTEM_DATE between to_date ('2017-08-30 07:00:00', 'YYYY-MM-DD HH24:MI:SS')
and to_date ('2017-08-30 19:29:59', 'YYYY-MM-DD HH24:MI:SS')
That will allow any index on SYSTEM_DATE to be used.
One obvious suspect is your date manipulation in the IN list. You should never, ever use functions around dates - that kills any ability of Oracle to use an index on the date column.
Instead:
where system_date >= to_date('2017-08-30 07:00', 'yyyy-mm-dd hh24:mi')
and system_date < to_date('2017-08-30 19:30', 'yyyy-mm-dd hh24:mi')
(the second inequality is strict, if you want to exclude 7:30pm sharp).
I was able to eliminate most of the subqueries but I'm not sure it will result in the performance gain w/o knowledge of table size and indexes. Posting the execution plan would help us understand where your bottleneck is.
SELECT count(*) as Totals
FROM WA_SEW_TBL_EMP_INFO S
INNER JOIN WA_GA_TBL_EMPLOYEES E
ON S.BADGEID_FK = E.BADGEID
LEFT JOIN WA_SEW_TBL_RESULTS R
ON S.BADGEID_FK =R.EMPID
-- Others already addressed what needs to happen here.
AND TO_CHAR(R.SYSTEM_DATE, 'YYYY-MM-DD') = '2017-08-30'
AND TO_CHAR(R.SYSTEM_DATE,'HH24:MI') >= '07:00'
AND TO_CHAR(R.SYSTEM_DATE,'HH24:MI') <= '19:29'
WHERE S.STATUS = 'Attend'
AND S.SHIFT = 'Morning'
AND R.EmpID is null

Ora-00932 - expected NUMBER got -

I have been running the below query without issue:
with Nums (NN) as
(
select 0 as NN
from dual
union all
select NN+1 -- (1)
from Nums
where NN < 30
)
select null as errormsg, trunc(sysdate)-NN as the_date, count(id) as the_count
from Nums
left join
(
SELECT c1.id, trunc(c1.c_date) as c_date
FROM table1 c1
where c1.c_date > trunc(sysdate) - 30
UNION
SELECT c2.id, trunc(c2.c_date)
FROM table2 c2
where c2.c_date > trunc(sysdate) -30
) x1
on x1.c_date = trunc(sysdate)-Nums.NN
group by trunc(sysdate)-Nums.NN
However, when I try to pop this in a proc for SSRS use:
procedure pr_do_the_thing (RefCur out sys_refcursor)
is
oops varchar2(100);
begin
open RefCur for
-- see above query --
;
end pr_do_the_thing;
I get
Error(): PL/SQL: ORA-00932: inconsistent datatypes: expected NUMBER got -
Any thoughts? Like I said above, as a query, there is no issue. As a proc, the error appears at note (1) int eh query.
This seems to be bug 18139621 (see MOS Doc ID 2003626.1). There is a patch available, but if this is the only place you encounter this, it might be simpler to switch to a hierarchical query:
with Nums (NN) as
(
select level - 1
from dual
connect by level <= 31
)
...
You could also calculate the dates inside the CTE (which also fails with a recursive CTE):
with Dates (DD) as
(
select trunc(sysdate) - level + 1
from dual
connect by level <= 31
)
select null as errormsg, DD as the_date, count(id) as the_count
from Dates
left join
(
SELECT c1.id, trunc(c1.c_date) as c_date
FROM table1 c1
where c1.c_date > trunc(sysdate) - 30
UNION
SELECT c2.id, trunc(c2.c_date)
FROM table2 c2
where c2.c_date > trunc(sysdate) -30
) x1
on x1.c_date = DD
group by DD;
I'd probably organise it slightly differently, so the subquery doesn't limit the date range directly:
with dates (dd) as
(
select trunc(sysdate) - level + 1
from dual
connect by level <= 31
)
select errormsg, the_date, count(id) as the_count
from (
select null as errormsg, d.dd as the_date, c1.id
from dates d
left join table1 c1 on c1.c_date >= d.dd and c1.c_date < d.dd + 1
union all
select null as errormsg, d.dd as the_date, c2.id
from dates d
left join table2 c2 on c2.c_date >= d.dd and c2.c_date < d.dd + 1
)
group by errormsg, the_date;
but as always with these things, check the performance of each approach...
Also notice that I've switched from union to union all. If an ID could appear more than once on the same day, in the same table or across both tables, then the counts will be different - you need to decide whether you want to count them once or as many times as they appear. That applies to your original query too.

Query to get the data at particular time

I need to write a query for attendance, which gives the attendance for current time and previous 10 minutes. In total 20 records.
I have the below query. it gives me the correct data for current time. But the previous attendance goes on reducing.
For example : at 12:30 PM the attendance is 2000. But when the time reaches 1:30 PM the attendnce corresponding to 12:30 shows very less.
SELECT TO_CHAR (each_sec, 'HH24:MI') p_time, COUNT (each_sec) cnt
FROM (SELECT *
FROM (SELECT d.userid, d.logdate
FROM atendance_data d
JOIN
(SELECT userid, MAX (logdate) logdate
FROM atendance_data
WHERE TRUNC (logdate) = TRUNC (SYSDATE)
GROUP BY userid) m
ON d.userid = m.userid AND d.logdate = m.logdate
WHERE d.c1 NOT IN ('Check-Out', 'Break-Out')) c
JOIN
(SELECT SYSDATE + (1 - LEVEL) / 24 / 3600 * 600 each_sec
FROM DUAL
CONNECT BY LEVEL <= 20) s ON c.logdate <= s.each_sec
)
GROUP BY each_sec
ORDER BY each_sec
I have to write a where condition to take maximum of logdate, saying less than each_sec. I guess that is where i am going wrong. But I dont know how to do it.
Can anyone help me doing this?
Thanks in advance.
I have got it done like this. Thank you all for your responses
SELECT COUNT (*), each_sec
FROM (SELECT a.userid, a.logdate, a.c1, each_sec
FROM atendance_data a,
(SELECT userid, MAX (logdate) logdate, each_sec
FROM (SELECT userid, logdate, c1, each_sec
FROM (SELECT a.USERID,A.LOGDATE,A.C1, b.each_sec
FROM atendance_data a
JOIN
(SELECT SYSDATE
+ (1 - LEVEL)
/ 24
/ 3600
* 600 each_sec
FROM DUAL
CONNECT BY LEVEL <= 20) b
ON a.logdate <= b.each_sec
AND trunc(a.logdate) =trunc(sysdate)
))
GROUP BY userid, each_sec) b
WHERE a.userid = b.userid
AND a.logdate = b.logdate
AND Trunc (a.logdate) = trunc(sysdate))
WHERE c1 NOT IN ('Check-Out', 'Break-Out')
GROUP BY each_sec
ORDER BY each_sec;

Resources