Reputation: 145
I have a SQL query the produces a result set being inserted into meter_read_alert
table on daily basis using a procedure.
This process takes around 3 hrs daily. Explain plan for it is shown below.
How can I optimize the query? And what are the problem areas by seeing explain plan?
The table has range interval partition and local indexes. Every day around 9 million of rows inserts happen.
query:
select * from (
SELECT
'SBILL' Scenario,
mfg_serial_num,
device_id,
channel_id,
read_value,
read_time,
date_val,
reading_hourly,
reading_daywise,
CASE
WHEN read_value IS NULL THEN'04'--(Meter goes stale)'
WHEN event_name = 'BACKFLOW' THEN'01'
WHEN event_name = 'TAMPER' THEN'02'
WHEN event_name = 'Tamper' THEN'02'
WHEN event_name = 'EMPTYPIPE' THEN '03' END AS CodeAlert_HOUR,
CASE WHEN reading_daywise BETWEEN CON.min_value AND CON.max_value THEN CON.alert_code END AS CodeAlert_DAY,
CASE
WHEN read_value IS NULL THEN'4'
WHEN event_name = 'BACKFLOW' THEN'4'
WHEN event_name = 'TAMPER' THEN'4'
WHEN event_name = 'Tamper' THEN'4'
WHEN event_name = 'EMPTYPIPE' THEN'4' END AS Priority_HOUR,
CASE WHEN reading_daywise BETWEEN CON.min_value AND CON.max_value THEN CON.priority END AS Priority_DAY,
CASE WHEN val_status='VAL' THEN 'A' WHEN val_status='EST' THEN 'E' ELSE val_status END Val_status,
desc_text,
CASE WHEN reading_hourly BETWEEN CONH.min_value AND CONH.max_value THEN CONH.alert_code END AS ConAlert_per_hour
FROM
(
SELECT d.mfg_serial_num,
d.id device_id,
mdr.channel_id,
mdr.read_value,
mdr.read_time,
To_date(To_char(mdr.read_time, 'dd/mm/yyyy')) date_val,
mdr.read_value - Lag(mdr.read_value, 1, 0) over ( PARTITION BY mdr.channel_id ORDER BY mdr.read_time) reading_hourly,
Max(mdr.read_value) keep (dense_rank last ORDER BY To_char (mdr.read_time, 'HH24:MI:SS')) over ( PARTITION BY mdr.channel_id,To_date(To_char(mdr.read_time, 'dd/mm/yyyy'))) -
Min(mdr.read_value) keep (dense_rank first ORDER BY To_char (mdr.read_time, 'HH24:MI:SS') ) over ( PARTITION BY mdr.channel_id,To_date(To_char(mdr.read_time, 'dd/mm/yyyy'))) as reading_daywise,
det.event_desc desc_text,
det.event_name,
mdr.val_status
FROM (select channel_id,read_value,read_time,val_status,device_id from mudr.register_read
WHERE read_time between (select TO_TIMESTAMP(sysdate-7) from dual) and (select TO_TIMESTAMP(sysdate-1) - INTERVAL '1' SECOND from dual)
--ORDER BY channel_id,read_time
)Mdr
left join (select channel_id, device_id from EIP.device_channel_rel --where eff_end_time is null
) DCR ON dcr.channel_id = mdr.channel_id
left join (select id, mfg_serial_num from EIP.device) d ON d.id = DCR.device_id
left join (select device_id, device_event_type_id,event_time ,
case when to_char(event_time,'mi') between '01' and '59' then trunc(event_time,'HH24') + interVal '60' minute else event_time end New_Event_Time
from MUDR.device_event
where event_time between (select TO_TIMESTAMP(sysdate-7) from dual) and (select TO_TIMESTAMP(sysdate-1) - INTERVAL '1' SECOND from dual)
) de ON mdr.device_id = de.device_id
and mdr.read_time= de.New_Event_Time
left join (select device_event_type_id,event_desc,event_name from MUDR.device_event_type) det ON det.device_event_type_id= de.device_event_type_id
--ORDER BY channel_id,read_time
)t1
left join (SELECT * FROM condition WHERE scenario = 'SBILL' AND unit_of_period_check='DAY' )CON ON t1.reading_daywise BETWEEN CON.min_value AND CON.max_value
left join (SELECT * FROM condition WHERE scenario = 'SBILL' AND unit_of_period_check='HOUR' )CONH ON t1.reading_hourly BETWEEN CONH.min_value AND CONH.max_value
ORDER BY channel_id,read_time) a
where not exists
(
select 1
from meter_read_alert b
WHERE a.scenario=b.scenario
AND a.mfg_serial_num= b.mfg_serial_num
AND a.device_id = b.device_id
and a.channel_id=b.channel_id
AND a.read_time=b.read_time
and b.read_time between (select TO_TIMESTAMP(sysdate-7) from dual) and (select TO_TIMESTAMP(sysdate-1) - INTERVAL '1' SECOND from dual)
);
Plan hash value: 618507408
------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 106K| 46M| | 153K (1)| 00:00:07 | | |
| 1 | SORT ORDER BY | | 106K| 46M| 48M| 153K (1)| 00:00:07 | | |
| 2 | MERGE JOIN OUTER | | 106K| 46M| | 153K (1)| 00:00:07 | | |
| 3 | SORT JOIN | | 106K| 44M| 92M| 153K (1)| 00:00:07 | | |
| 4 | MERGE JOIN OUTER | | 106K| 44M| | 153K (1)| 00:00:07 | | |
| 5 | SORT JOIN | | 106K| 41M| 87M| 153K (1)| 00:00:07 | | |
|* 6 | HASH JOIN RIGHT ANTI | | 106K| 41M| 3432K| 145K (1)| 00:00:06 | | |
| 7 | PARTITION RANGE ITERATOR | | 87775 | 2400K| | 20495 (1)| 00:00:01 | KEY | KEY |
| 8 | VIEW | VW_SQ_1 | 87775 | 2400K| | 20495 (1)| 00:00:01 | | |
|* 9 | TABLE ACCESS BY LOCAL INDEX ROWID BATCHED | METER_READ_ALERT | 87775 | 2914K| | 20495 (1)| 00:00:01 | KEY | KEY |
|* 10 | INDEX RANGE SCAN | READTIME_ALL_IDX | 87775 | | | 81 (0)| 00:00:01 | KEY | KEY |
| 11 | FAST DUAL | | 1 | | | 2 (0)| 00:00:01 | | |
| 12 | FAST DUAL | | 1 | | | 2 (0)| 00:00:01 | | |
| 13 | VIEW | | 155K| 56M| | 124K (1)| 00:00:05 | | |
| 14 | WINDOW SORT | | 155K| 24M| 26M| 124K (1)| 00:00:05 | | |
| 15 | WINDOW SORT | | 155K| 24M| 26M| 124K (1)| 00:00:05 | | |
|* 16 | HASH JOIN RIGHT OUTER | | 155K| 24M| | 119K (1)| 00:00:05 | | |
| 17 | TABLE ACCESS FULL | DEVICE_EVENT_TYPE | 1513 | 124K| | 8 (0)| 00:00:01 | | |
|* 18 | HASH JOIN RIGHT OUTER | | 52840 | 4076K| | 119K (1)| 00:00:05 | | |
| 19 | PARTITION RANGE ITERATOR | | 3 | 54 | | 1 (0)| 00:00:01 | KEY | KEY |
| 20 | PARTITION HASH ALL | | 3 | 54 | | 1 (0)| 00:00:01 | 1 | LAST |
| 21 | VIEW | | 3 | 54 | | 1 (0)| 00:00:01 | | |
| 22 | TABLE ACCESS BY LOCAL INDEX ROWID BATCHED | DEVICE_EVENT | 3 | 48 | | 1 (0)| 00:00:01 | KEY | KEY |
|* 23 | INDEX RANGE SCAN | IX_DEVICE_EVENT_1 | 1 | | | 1 (0)| 00:00:01 | KEY | KEY |
| 24 | FAST DUAL | | 1 | | | 2 (0)| 00:00:01 | | |
| 25 | FAST DUAL | | 1 | | | 2 (0)| 00:00:01 | | |
|* 26 | HASH JOIN OUTER | | 52840 | 3147K| 2944K| 119K (1)| 00:00:05 | | |
|* 27 | HASH JOIN OUTER | | 52840 | 2322K| 2312K| 115K (1)| 00:00:05 | | |
| 28 | VIEW | | 51466 | 1708K| | 113K (1)| 00:00:05 | | |
| 29 | SORT ORDER BY | | 51466 | 1357K| 2240K| 113K (1)| 00:00:05 | | |
| 30 | PARTITION RANGE ITERATOR | | 51466 | 1357K| | 112K (1)| 00:00:05 | KEY | KEY |
| 31 | PARTITION HASH ALL | | 51466 | 1357K| | 112K (1)| 00:00:05 | 1 | LAST |
| 32 | TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| REGISTER_READ | 51466 | 1357K| | 112K (1)| 00:00:05 | KEY | KEY |
|* 33 | INDEX SKIP SCAN | UK_REGISTER_READ | 1 | | | 112K (1)| 00:00:05 | KEY | KEY |
| 34 | FAST DUAL | | 1 | | | 2 (0)| 00:00:01 | | |
| 35 | FAST DUAL | | 1 | | | 2 (0)| 00:00:01 | | |
| 36 | INDEX FAST FULL SCAN | UK_DEVICE_CHANNEL_REL | 1482K| 15M| | 1443 (3)| 00:00:01 | | |
| 37 | TABLE ACCESS FULL | DEVICE | 1096K| 16M| | 4063 (2)| 00:00:01 | | |
|* 38 | FILTER | | | | | | | | |
|* 39 | SORT JOIN | | 1 | 26 | | 3 (34)| 00:00:01 | | |
|* 40 | EXTERNAL TABLE ACCESS FULL | CONDITION | 1 | 26 | | 2 (0)| 00:00:01 | | |
|* 41 | FILTER | | | | | | | | |
|* 42 | SORT JOIN | | 2 | 46 | | 3 (34)| 00:00:01 | | |
|* 43 | EXTERNAL TABLE ACCESS FULL | CONDITION | 2 | 46 | | 2 (0)| 00:00:01 | | |
------------------------------------------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
6 - access("T1"."MFG_SERIAL_NUM"="ITEM_1" AND "T1"."DEVICE_ID"="ITEM_2" AND "T1"."CHANNEL_ID"="ITEM_3" AND "T1"."READ_TIME"="ITEM_4")
9 - filter("B"."SCENARIO"='SBILL')
10 - access("B"."READ_TIME">= (SELECT TO_TIMESTAMP(TO_CHAR(SYSDATE@!-7)) FROM "SYS"."DUAL" "DUAL") AND "B"."READ_TIME"<= (SELECT
TO_TIMESTAMP(TO_CHAR(SYSDATE@!-1))-INTERVAL'+00 00:00:01.000000' DAY(2) TO SECOND(6) FROM "SYS"."DUAL" "DUAL"))
16 - access("DEVICE_EVENT_TYPE_ID"(+)="DE"."DEVICE_EVENT_TYPE_ID")
18 - access("MDR"."DEVICE_ID"="DE"."DEVICE_ID"(+) AND "MDR"."READ_TIME"="DE"."NEW_EVENT_TIME"(+))
23 - access("EVENT_TIME">= (SELECT TO_TIMESTAMP(TO_CHAR(SYSDATE@!-7)) FROM "SYS"."DUAL" "DUAL") AND "EVENT_TIME"<= (SELECT
TO_TIMESTAMP(TO_CHAR(SYSDATE@!-1))-INTERVAL'+00 00:00:01.000000' DAY(2) TO SECOND(6) FROM "SYS"."DUAL" "DUAL"))
26 - access("ID"(+)="DEVICE_ID")
27 - access("CHANNEL_ID"(+)="MDR"."CHANNEL_ID")
33 - access("READ_TIME">= (SELECT TO_TIMESTAMP(TO_CHAR(SYSDATE@!-7)) FROM "SYS"."DUAL" "DUAL") AND "READ_TIME"<= (SELECT
TO_TIMESTAMP(TO_CHAR(SYSDATE@!-1))-INTERVAL'+00 00:00:01.000000' DAY(2) TO SECOND(6) FROM "SYS"."DUAL" "DUAL"))
filter("READ_TIME">= (SELECT TO_TIMESTAMP(TO_CHAR(SYSDATE@!-7)) FROM "SYS"."DUAL" "DUAL") AND "READ_TIME"<= (SELECT
TO_TIMESTAMP(TO_CHAR(SYSDATE@!-1))-INTERVAL'+00 00:00:01.000000' DAY(2) TO SECOND(6) FROM "SYS"."DUAL" "DUAL"))
38 - filter("T1"."READING_DAYWISE"<="CONDITION"."MAX_VALUE"(+))
39 - access(INTERNAL_FUNCTION("T1"."READING_DAYWISE")>=INTERNAL_FUNCTION("CONDITION"."MIN_VALUE"(+)))
filter(INTERNAL_FUNCTION("T1"."READING_DAYWISE")>=INTERNAL_FUNCTION("CONDITION"."MIN_VALUE"(+)))
40 - filter("SCENARIO"(+)='SBILL' AND "UNIT_OF_PERIOD_CHECK"(+)='DAY')
41 - filter("T1"."READING_HOURLY"<="CONDITION"."MAX_VALUE"(+))
42 - access(INTERNAL_FUNCTION("T1"."READING_HOURLY")>=INTERNAL_FUNCTION("CONDITION"."MIN_VALUE"(+)))
filter(INTERNAL_FUNCTION("T1"."READING_HOURLY")>=INTERNAL_FUNCTION("CONDITION"."MIN_VALUE"(+)))
43 - filter("SCENARIO"(+)='SBILL' AND "UNIT_OF_PERIOD_CHECK"(+)='HOUR')
Upvotes: 1
Views: 91
Reputation: 95101
There are only two restrictions on the data you want to select. The main one is the time span. The other one is that you want to exclude data already existing in meter_read_alert
. You could start your query like this, applying the restrictions as early as possible:
with main as
(
select
s.scenario,
mdr.channel_id,
mdr.read_value,
mdr.read_time,
mdr.val_status,
mdr.device_id as mdr_device_id,
dcr.device_id as dcr_device_id,
d.mfg_serial_num as dcr_mfg_serial_num
from (select 'SBILL' as scenario from dual) s
cross join mudr.register_read mdr
left join from eip.device_channel_rel dcr on dcr.channel_id = mdr.channel_id
left join eip.device d on d.id = dcr.device_id
where mdr.read_time >= systimestamp - interval '7' day and mdr.read_time < systimestamp
and not exists
(
select null
from meter_read_alert mra
where mra.scenario = s.scenario
and mra.mfg_serial_num = d.mfg_serial_num
and mra.device_id = mdr.device_id
and mra.channel_id = mdr.channel_id
and mra.read_time = mdr.read_time
)
)
select ...
(Are you sure you want outer joins here by the way? If inner joins suffice, make these inner joins.)
Your time range condition looked complicated. I hope I got it right in my query. If you want it slightly different, change it. With dates/times we usually don't use BETWEEN
, but >=
and <
to be independent on time precision (e.g. somedate < date '2019-12-01'
would get the complete November until the last millisecond, nanosecond or whatever and not include any of December).
Speeding up queries is mostly about providing apropriate indexes. I'd suggest:
create index idx1 on mudr.register_read (read_time, channel_id);
create index idx2 on eip.device_channel_rel (channel_id, device_id);
create index idx3 on eip.device (device_id);
create index idx4 on meter_read_alert (read_time, channel_id, device_id, mfg_serial_num, scenario);
Or even use covering indexes:
create index idx1 on mudr.register_read (read_time, channel_id, read_value, val_status, device_id);
create index idx2 on eip.device_channel_rel (channel_id, device_id);
create index idx3 on eip.device (device_id);
create index idx4 on meter_read_alert (read_time, channel_id, device_id, mfg_serial_num, scenario);
Upvotes: 2