Thought it's helpful to read again and again.
Be on fire. You have to want to learn everything, do everything, consume everything. So you got the DBA job, now is not the time to rest in your new chair and enjoy your success. List out your goals and what you want to learn. A five year plan is a great idea.
Listen! Ask Questions! Be involved! Don't just sit back waiting for the create table requests.
When it comes to theory, don't believe anything you hear or read until you have tried it yourself. Database rule number one, in my opinion, is that no rule of thumb applies all the time.
If you are solely responsible for a database, make darned sure, before you leave your job on day one, that your database can be recovered. Nothing else matters if you can't get that database back.
Document everything.
And, a baker's half-dozen...
Learn to use your voice of authority. You are the DBA, and this database is your responsibility. As you learn the right way, and the wrong way to do things (like, say, database design), you need to be an advocate for best practices and for good design. If you succeed, you can enjoy the rapture of success. If they don't listen, you will get the joy of "I told you so."
http://searchoracle.techtarget.com/news/1077095/So-you-want-to-be-a-DBA
Thursday, April 14, 2011
Friday, October 29, 2010
effective index selectivity 2
File name...: A:\SQL\CBO\index_selectivity2.sql
Usage.......: @file_name
Description.: Why we need to avoid implicit data conversions on index columns in predicate.
Notes.......: index on VarChar2 columns
Parameters..:
Package.....: ._pkg
Modification History:
Date Who What
29-Oct-2010: Charlie(Yi): Create the file,
Goal
----
Show how implicit data conversions impact Effective Index Selectivity.
Prove that developers rely on implicit conversions is a bad practice.
Solution
--------
Create index on VarChar2 column(s), put number value in the predicate that use this index,
cause implicitly data type conversion.
E.g. TO_NUMBER(index_column) = n
Spec
----
cost = blevel +
ceiling(leaf_blocks * effective index selectivity) +
ceiling(clustering_factor * effective table selectivity)
Logical Reads(LIO) of index access = BLevel + leaf_blocks * effective index selectivity
Buffers is LIO in function dbms_xplan.display_cursor output.
See Page 62(91) of Book: [Cost-Based Oracle Fundamentals]
Result
------
cost = 1 , LIO = 2 , No data conversion
cost = 26, LIO = 28, data conversion, from char to number
Data flow
---------
Test case
---------
* ,
* ,
Setup
-----
See below SQL code.
Reference
---
QA..: You can send feedbacks or questions about this script to charlie.zhu1 gmail.com
blog: http://mujiang.blogspot.com/
Usage.......: @file_name
Description.: Why we need to avoid implicit data conversions on index columns in predicate.
Notes.......: index on VarChar2 columns
Parameters..:
Package.....: ._pkg
Modification History:
Date Who What
29-Oct-2010: Charlie(Yi): Create the file,
Goal
----
Show how implicit data conversions impact Effective Index Selectivity.
Prove that developers rely on implicit conversions is a bad practice.
Solution
--------
Create index on VarChar2 column(s), put number value in the predicate that use this index,
cause implicitly data type conversion.
E.g. TO_NUMBER(index_column) = n
Spec
----
cost = blevel +
ceiling(leaf_blocks * effective index selectivity) +
ceiling(clustering_factor * effective table selectivity)
Logical Reads(LIO) of index access = BLevel + leaf_blocks * effective index selectivity
Buffers is LIO in function dbms_xplan.display_cursor output.
See Page 62(91) of Book: [Cost-Based Oracle Fundamentals]
Result
------
cost = 1 , LIO = 2 , No data conversion
cost = 26, LIO = 28, data conversion, from char to number
Data flow
---------
Test case
---------
* ,
* ,
Setup
-----
See below SQL code.
Reference
---
QA..: You can send feedbacks or questions about this script to charlie.zhu1 gmail.com
blog: http://mujiang.blogspot.com/
ALTER SESSION SET STATISTICS_LEVEL=TYPICAL;
create table t
(
c1 VarChar2(5),
c2 VarChar2(7)
)
nologging;
create table t1
(
c1 varchar2(5),
n1 number(5)
)
nologging;
insert into t1(c1,n1) values('501',501);
insert /*+ append */ into t(c1,c2)
select mod(rownum,5), rownum
from dual
connect by level <=50000;
commit;
create unique index t_u1 on t(c1,c2) nologging;
exec dbms_stats.gather_table_stats(user,'t');
exec dbms_stats.gather_table_stats(user,'t1');
set serveroutput off
set linesize 200
ALTER SESSION SET STATISTICS_LEVEL=ALL;
REM -- use number value on VarChar2 index columns,
select * from t
where c1='1' and c2=(select n1 from t1);
SELECT * FROM table(dbms_xplan.display_cursor(NULL,NULL, '+cost iostats memstats last partition'));
----------------------------------------------------------
| Id | Operation | Name | Cost (%CPU)| Buffers |
----------------------------------------------------------
| 0 | SELECT STATEMENT | | 29 (100)| 35 |
|* 1 | INDEX RANGE SCAN | T_U1 | 26 (0)| 35 |
| 2 | TABLE ACCESS FULL| T1 | 3 (0)| 7 |
----------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("C1"='1')
filter(TO_NUMBER("C2")=)
REM -- use string value on VarChar2 index columns,
select * from t
where c1='1' and c2=(select c1 from t1);
SELECT * FROM table(dbms_xplan.display_cursor(NULL,NULL, 'cost iostats memstats last partition'));
----------------------------------------------------------
| Id | Operation | Name | Cost (%CPU)| Buffers |
----------------------------------------------------------
| 0 | SELECT STATEMENT | | 4 (100)| 9 |
|* 1 | INDEX UNIQUE SCAN | T_U1 | 1 (0)| 9 |
| 2 | TABLE ACCESS FULL| T1 | 3 (0)| 7 |
----------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("C1"='1' AND "C2"=)
.
Thursday, September 30, 2010
Throttle big result set
Goal
To process a big query result set from database, many a time the application server is limited by memory footprint, so we need to throttle the output chunk by chunk.Solution
Q: How do you eat an elephant? A: One piece at a time.
The staging table will put into a tablespace with EXTENT MANAGEMENT LOCAL UNIFORM SIZE 4M. you may choose other uniform size based on your result size limit.
And then we will read data one extent at a time, after finish one extent, mark it as processed.
When client application crashed or failed, we can restart at failed extent.
We may slow make the chunk across 2 or many extents to make it more flexible.
Reference
On the use of DBMS_ROWID.rowid_create
[http://bit.ly/bphHJb]
Setup
drop table rowid_range_job purge;
create table rowid_range_job
tablespace data_auto nologging
as
select e.extent_id, e.block_id, e.block_id+blocks-1 block_id_end,
cast(dbms_rowid.rowid_create( 1, o.data_object_id, e.file_id, e.block_id, 0 ) as urowid) min_rowid,
cast(dbms_rowid.rowid_create( 1, o.data_object_id, e.file_id, e.block_id+e.blocks-1, 10000 ) as urowid) max_rowid,
cast(o.object_name as varchar2(30)) table_name,
cast(null as varchar2(30)) partition_name,
cast('isbn_extract' as varchar2(30)) job_name,
cast(null as date) process_date,
cast(null as number(1)) is_processed
from dba_extents e, user_objects o
where o.object_name = 'T'
and e.segment_name = 'T'
and e.owner = user
and e.segment_type = 'TABLE'
order by e.extent_id
;
drop table t purge;
create table t
(
n1 number(10),
d1 date,
c1 varchar2(2000)
);
insert --+ append
into t(n1,d1,c1)
select rownum, sysdate, rpad('a',1200)
from dual
connect by level <= 10000;
commit;
Generate and check rowid range split,
with data as
(
select e.extent_id, e.block_id, e.block_id+blocks-1,
dbms_rowid.rowid_create( 1, o.data_object_id, e.file_id, e.block_id, 0 ) min_rowid,
dbms_rowid.rowid_create( 1, o.data_object_id, e.file_id, e.block_id+e.blocks-1, 10000 ) max_rowid
from dba_extents e,
user_objects o
where o.object_name = 'T'
and e.segment_name = o.object_name
and e.owner = user
and e.segment_type = 'TABLE'
)
select extent_id, count(*) cnt
from data, T t
where t.rowid between data.min_rowid and data.max_rowid
group by rollup (extent_id)
;
delete rowid_range_job where job_name = 'test_output_job';
INSERT INTO rowid_range_job
SELECT e.extent_id, e.block_id, e.block_id + blocks - 1 block_id_end,
CAST
(DBMS_ROWID.rowid_create (1,
o.data_object_id,
e.file_id,
e.block_id,
0
) AS UROWID
) min_rowid,
CAST
(DBMS_ROWID.rowid_create (1,
o.data_object_id,
e.file_id,
e.block_id + e.blocks - 1,
32000
) AS UROWID
) max_rowid,
CAST (o.object_name AS VARCHAR2 (30)) table_name,
CAST (NULL AS VARCHAR2 (30)) partition_name,
CAST ('test_output_job' AS VARCHAR2 (30)) job_name,
CAST (NULL AS DATE) process_date, 0 is_processed
FROM dba_extents e, user_objects o
WHERE o.object_name LIKE 'T'
AND e.segment_name = o.object_name
AND e.owner = USER
AND e.segment_type = 'TABLE'
ORDER BY o.object_name, e.extent_id;
commit;
Query rowid extent range split metadata
select EXTENT_ID,
BLOCK_ID,
BLOCK_ID_END,
MIN_ROWID,
MAX_ROWID
--,TABLE_NAME
--,PARTITION_NAME
--,JOB_NAME
--,PROCESS_DATE
--,IS_PROCESSED
from invdb.rowid_range_job where job_name = 'test_output_job';
with data as
(
select extent_id, block_id, block_id_end, min_rowid, max_rowid, table_name, job_name, process_date, is_processed
from rowid_range_job
where job_name = 'test_output_job'
and table_name = 'T'
)
select extent_id, count(*) cnt
from data, T t
where t.rowid between data.min_rowid and data.max_rowid
group by rollup (extent_id)
/
Monday, August 16, 2010
IN_EXISTS Correlated Subquery
Goal
To show how IN and EXISTS Correlated Subquery works, and how they changed in Oracle 11g CBO SQL Engine. Also covered the NOT IN and NOT EXISTS difference.Setup
drop table t1 cascade constraints purge; drop table t2 purge; create table t1(x number); create table t2(y number); insert into t1(x) values(1); insert into t1(x) values(2); insert into t1(x) values(Null); insert into t2(y) values(1); insert into t2(y) values(Null); commit; create index t1_x on t1(x); create index t2_y on t2(y);
IN
t2 is small, index on t1.x, the subquery ( select y from T2 ) is small, probable full scan t2Select * from T1 where x in ( select y from T2 ); = select * from t1, ( select distinct y from t2 ) t2 where t1.x = t2.y;
- Small t2,
set autot trace exp Select /*+ cardinality(t1,50000) */ * from T1 where x in ( select /*+ cardinality(t2, 200) */ y from T2 ); ------------------------------------------ | Id | Operation | Name | Rows | ------------------------------------------ | 0 | SELECT STATEMENT | | 224 | | 1 | NESTED LOOPS | | 224 | | 2 | SORT UNIQUE | | 200 | | 3 | INDEX FULL SCAN| T2_Y | 200 | |* 4 | INDEX RANGE SCAN| T1_X | 1 | ------------------------------------------
- Small t1
Select /*+ cardinality(t1,500) */ * from T1 where x in ( select /*+ cardinality(t2, 20000) */ y from T2 ); ------------------------------------------ | Id | Operation | Name | Rows | ------------------------------------------ | 0 | SELECT STATEMENT | | 500 | | 1 | NESTED LOOPS SEMI| | 500 | | 2 | INDEX FULL SCAN | T1_X | 500 | |* 3 | INDEX RANGE SCAN| T2_Y | 20000 | ------------------------------------------
Exists
t1 is small, index on t2(y), full scan t1,select * from t1
where exists ( select null from t2 where y = x );
=
for x in ( select * from t1 )
loop
if ( exists ( select null from t2 where y = x.x )
then
OUTPUT THE RECORD
end if
end loop
- Small t2, big t1.
select /*+ cardinality(t1,50000) */ * from t1
where exists ( select /*+ cardinality(t2, 700) */ null from t2 where y = x );
------------------------------------------
| Id | Operation | Name | Rows |
------------------------------------------
| 0 | SELECT STATEMENT | | 782 |
| 1 | NESTED LOOPS | | 782 |
| 2 | SORT UNIQUE | | 700 |
| 3 | INDEX FULL SCAN| T2_Y | 700 |
|* 4 | INDEX RANGE SCAN| T1_X | 1 |
------------------------------------------
- Small t1, big t2.
select /*+ cardinality(t1,500) */ * from t1
where exists ( select /*+ cardinality(t2, 70000) */ null from t2 where y = x );
------------------------------------------
| Id | Operation | Name | Rows |
------------------------------------------
| 0 | SELECT STATEMENT | | 500 |
| 1 | NESTED LOOPS SEMI| | 500 |
| 2 | INDEX FULL SCAN | T1_X | 500 |
|* 3 | INDEX RANGE SCAN| T2_Y | 70000 |
------------------------------------------
If both the subquery and the outer table are huge — either might work as well as the
other — depends on the indexes and other factors.
Not in and Not exists are different.
select * from t1 outer
where outer.x not in (select y from t2);
is NOT the same asselect * from t1 outer
where not exists (select null from t2 where t2.y = outer.x);
UNLESS the expression "y" is not null. That said:select * from t1 outer
where outer.x not in (select y from t2 where y is not null);
is the same asselect * from t1 outer
where not exists (select null from t2 where t2.y = outer.x);
If t2.y is null-able,
select * from t1 outer
where outer.x not in (select y from t2 where y is null);
== equals ==
select * from t1 outer
where outer.x not in (2,3,Null);
return no rows.
select * from t1 outer
where outer.x not in (2,3);
Returns some rows.
IN & EXISTS, AskTom, http://bit.ly/aLvOeS
Set Stats
declare
m_distcnt number;
m_density number;
m_nullcnt number;
m_avgclen number;
begin
m_distcnt := 200100;
m_density := 0.002;
m_nullcnt := 0;
m_avgclen := 5;
dbms_stats.set_column_stats(
ownname => user,
tabname => 't1',
colname => 'x',
distcnt => m_distcnt,
density => m_density,
nullcnt => m_nullcnt,
avgclen => m_avgclen
);
dbms_stats.set_column_stats(
ownname => user,
tabname => 't2',
colname => 'y',
distcnt => m_distcnt,
density => m_density,
nullcnt => m_nullcnt,
avgclen => m_avgclen
);
dbms_stats.set_table_stats( user, 't1', numrows => 2000000, numblks => 1000000);
dbms_stats.set_table_stats( user, 't2', numrows => 2000000, numblks => 1000000);
end;
/
select table_name,
num_rows,
blocks
from
user_tables
where
table_name in ('T1','T2')
;
select
num_distinct,
low_value,
high_value,
density,
num_nulls,
num_buckets,
histogram
from
user_tab_columns
where
table_name in ('T1','T2')
and column_name in ('X','Y')
;
Tuesday, May 18, 2010
Collection_Array
PL/SQL has three collection types, Tom often demos with Nested Table and Associative Array.
Associative Array is most flexible on element indexing. Nested Table is good enough for Bulk Fetching.
But Bryn Llewellyn, Oracle PL/SQL Product Manager likes to use VArray in his demo.
"the collection is best declared as a varray with a maximum size equal to Batchsize."
See http://www.oracle.com/technology/tech/pl_sql/pdf/doing_sql_from_plsql.pdf
Probably he knows that VARRAY implemented with the most efficient internal storage, and delivery the best performance, when correctly used; it may meet most of the requirements for loop batch bulk fetching.
To find out the details, you may do a simple RunStats benchmark.
collection(elements), e.g.
record_name.field_name
Associative Array is most flexible on element indexing. Nested Table is good enough for Bulk Fetching.
But Bryn Llewellyn, Oracle PL/SQL Product Manager likes to use VArray in his demo.
"the collection is best declared as a varray with a maximum size equal to Batchsize."
See http://www.oracle.com/technology/tech/pl_sql/pdf/doing_sql_from_plsql.pdf
Probably he knows that VARRAY implemented with the most efficient internal storage, and delivery the best performance, when correctly used; it may meet most of the requirements for loop batch bulk fetching.
To find out the details, you may do a simple RunStats benchmark.
- Associative Array type(or index-by table)
TYPE population IS TABLE OF NUMBER INDEX BY VARCHAR2(64);- Nested Tables
TYPE nested_type IS TABLE OF VARCHAR2(30);- invoke EXTEND method to add elements later
- Collection of ADT = UDT, Abstract datatype, User defined datatype:
CREATE OR REPLACE TYPE INVDB.NUMBER_TAB_TYPE is table of number; / select ... from TABLE(ADT_Table_Instance);
Comments, Bulk fetch into ADT is not efficient, you may see the workaround in paper doing_sql_from_plsql.pdf
- Variable-size array (varray)
-- Code_30 Many_Row_Select.sql Batchsize constant pls_integer := 1000; type Result_t is record(PK t.PK%type, v1 t.v1%type); type Results_t is varray(1000) of Result_t; Results Results_t;
Concept
- Associative Array: sparse array.
- Nested Table or ADT/UDT : dense array
REM Associative Array declare type date_aat is table of date index by binary_integer; l_data date_aat; begin l_data(-200) := sysdate; l_data(+200) := sysdate+1; end; /
collection(elements), e.g.
collection_instance(element_unique_subscript_index_number)record_name.field_name
Monday, May 03, 2010
Benchmark with RunStats
There are many approaches and tools to benchmark Oracle application.
E.g.
* mystat.sql and mystat2.sql
* SQL session trace and tkprof
* SET AUTOT[RACE] {OFF | ON | TRACE[ONLY]} [EXP[LAIN]] [STAT[ISTICS]]
* ASH/AWR
* SQL hint /*+ gather_plan_statistics */ and dbms_xplan.display_cursor(NULL,NULL, 'iostats memstats last partition');
* ALTER SESSION SET STATISTICS_LEVEL=ALL;
* Real-Time SQL Monitoring
My favorite one is Tom's RunStats. Here is the one I enhanced from Tom's original version.
/*
Goal
----
Persistent the benchmark stats, and then developer can query the report later.
Solution
--------
Add an IP column to utility.run_stats_save table, get computer IP by SYS_CONTEXT function.
Reference
---------
file://a:/Tuning/Trace/RunStatsSave.sql
http://tkyte.blogspot.com/2009/10/httpasktomoraclecomtkyte.html
How to build a simple test harness (RUNSTATS) (HOWTO) to test two different approaches from a performance perspective.
Runstats.sql
This is the test harness I use to try out different ideas. It shows two vital sets of statistics for me
The elapsed time difference between two approaches. It very simply shows me which approach is faster by the wall clock
How many resources each approach takes. This can be more meaningful then even the wall clock timings. For example, if one approach is faster then the other but it takes thousands of latches (locks), I might avoid it simply because it will not scale as well.
The way this test harness works is by saving the system statistics and latch information into a temporary table. We then run a test and take another snapshot. We run the second test and take yet another snapshot. Now we can show the amount of resources used by approach 1 and approach 2.
Requirements
In order to run this test harness you must at a minimum have:
Access to V$STATNAME, V$MYSTAT, v$TIMER and V$LATCH
You must be granted select DIRECTLY on SYS.V_$STATNAME, SYS.V_$MYSTAT, SYS.V_$TIMER and SYS.V_$LATCH. It will not work to have select on these via a ROLE.
The ability to create a table -- run_stats -- to hold the before, during and after information.
The ability to create a package -- rs_pkg -- the statistics collection/reporting piece
You should note also that the LATCH information is collected on a SYSTEM WIDE basis. If you run this on a multi-user system, the latch information may be technically "incorrect" as you will count the latching information for other sessions - not just your session. This test harness works best in a simple, controlled test environment.
*/
E.g.
* mystat.sql and mystat2.sql
* SQL session trace and tkprof
* SET AUTOT[RACE] {OFF | ON | TRACE[ONLY]} [EXP[LAIN]] [STAT[ISTICS]]
* ASH/AWR
* SQL hint /*+ gather_plan_statistics */ and dbms_xplan.display_cursor(NULL,NULL, 'iostats memstats last partition');
* ALTER SESSION SET STATISTICS_LEVEL=ALL;
* Real-Time SQL Monitoring
My favorite one is Tom's RunStats. Here is the one I enhanced from Tom's original version.
/*
Goal
----
Persistent the benchmark stats, and then developer can query the report later.
Solution
--------
Add an IP column to utility.run_stats_save table, get computer IP by SYS_CONTEXT function.
Reference
---------
file://a:/Tuning/Trace/RunStatsSave.sql
http://tkyte.blogspot.com/2009/10/httpasktomoraclecomtkyte.html
How to build a simple test harness (RUNSTATS) (HOWTO) to test two different approaches from a performance perspective.
Runstats.sql
This is the test harness I use to try out different ideas. It shows two vital sets of statistics for me
The elapsed time difference between two approaches. It very simply shows me which approach is faster by the wall clock
How many resources each approach takes. This can be more meaningful then even the wall clock timings. For example, if one approach is faster then the other but it takes thousands of latches (locks), I might avoid it simply because it will not scale as well.
The way this test harness works is by saving the system statistics and latch information into a temporary table. We then run a test and take another snapshot. We run the second test and take yet another snapshot. Now we can show the amount of resources used by approach 1 and approach 2.
Requirements
In order to run this test harness you must at a minimum have:
Access to V$STATNAME, V$MYSTAT, v$TIMER and V$LATCH
You must be granted select DIRECTLY on SYS.V_$STATNAME, SYS.V_$MYSTAT, SYS.V_$TIMER and SYS.V_$LATCH. It will not work to have select on these via a ROLE.
The ability to create a table -- run_stats -- to hold the before, during and after information.
The ability to create a package -- rs_pkg -- the statistics collection/reporting piece
You should note also that the LATCH information is collected on a SYSTEM WIDE basis. If you run this on a multi-user system, the latch information may be technically "incorrect" as you will count the latching information for other sessions - not just your session. This test harness works best in a simple, controlled test environment.
*/
CREATE USER utility
IDENTIFIED BY ?
DEFAULT TABLESPACE USERS quota unlimited on users
ACCOUNT UNLOCK;
grant unlimited tablespace to utility;
create role schema_admin;
grant create session, create table, create view,
create procedure, create trigger, create any directory,
CREATE SEQUENCE, CREATE TYPE, CREATE SYNONYM,
create materialized view, create dimension,
SELECT_CATALOG_ROLE,
create database link,create public database link, drop public database link,
create job
to schema_admin;
grant schema_admin to utility;
-- grant create procedure, create table to utility;
grant select on SYS.V_$STATNAME to utility;
grant select on SYS.V_$MYSTAT to utility;
grant select on SYS.V_$TIMER to utility;
grant select on SYS.V_$LATCH to utility;
drop table utility.run_stats;
drop table utility.run_stats_save;
create global temporary table utility.run_stats
( runid varchar2(15),
name varchar2(80),
value int )
on commit preserve rows;
-- Store the stats for later reporting
create table utility.run_stats_save
(
runid varchar2(15),
name varchar2(80),
value int,
IP varchar2(30),
hostname varchar2(30)
) tablespace users;
create or replace view utility.stats
as select 'STAT...' || a.name name, b.value
from v$statname a, v$mystat b
where a.statistic# = b.statistic#
union all
select 'LATCH.' || name, gets
from v$latch
union all
select 'STAT...Elapsed Time', hsecs from v$timer;
/*
SYS_CONTEXT
The SYS_CONTEXT function is able to return the following host and IP address information for the current session:
TERMINAL - An operating system identifier for the current session. This is often the client machine name.
HOST - The host name of the client machine.
IP_ADDRESS - The IP address of the client machine.
SERVER_HOST - The host name of the server running the database instance.
SELECT SYS_CONTEXT('USERENV','HOST') FROM dual;
----------
GATES2\SKY
SELECT terminal, machine FROM v$session
where sid = (select sid from v$mystat where rownum <= 1);
*/
CREATE OR REPLACE package utility.runstats_pkg
as
TYPE print_tab IS TABLE OF varchar2(200);
--l_print dbms_sql.VARCHAR2_TABLE;
procedure rs_start;
procedure rs_middle;
procedure rs_stop( p_difference_threshold in number default 0 );
procedure rs_report( p_difference_threshold in number default 0, p_host in varchar2 default Null );
function rs_report( p_difference_threshold in number default 0, p_host in varchar2 default Null )
return print_tab PIPELINED DETERMINISTIC;
end;
/
CREATE OR REPLACE package body utility.runstats_pkg
as
g_start number;
g_run1 number;
g_run2 number;
g_host varchar2(30);
g_ip varchar2(30);
procedure rs_start
is
begin
g_host := substr(SYS_CONTEXT('USERENV','TERMINAL'),1,30);
g_ip := substr(SYS_CONTEXT('USERENV','IP_ADDRESS'),1,30);
delete from run_stats_save where hostname = g_host;
execute immediate 'truncate table run_stats';
delete from run_stats;
insert into run_stats
select 'before', stats.* from stats;
g_start := dbms_utility.get_time;
end;
procedure rs_middle
is
begin
g_run1 := (dbms_utility.get_time-g_start);
insert into run_stats
select 'after 1', stats.* from stats;
g_start := dbms_utility.get_time;
end;
procedure rs_stop(p_difference_threshold in number default 0)
is
begin
g_run2 := (dbms_utility.get_time-g_start);
dbms_output.put_line
( 'Run1 ran in ' || g_run1 || ' hsecs' );
dbms_output.put_line
( 'Run2 ran in ' || g_run2 || ' hsecs' );
dbms_output.put_line
( 'run 1 ran in ' || round(g_run1/g_run2*100,2) ||
'% of the time' );
dbms_output.put_line( chr(9) );
insert into run_stats
select 'after 2', stats.* from stats;
insert into run_stats_save (RUNID,NAME,VALUE,IP,HOSTNAME)
select RUNID,NAME,VALUE, g_ip, g_host from run_stats;
commit;
dbms_output.put_line
( rpad( 'Name', 40 ) || lpad( 'Run1', 14 ) ||
lpad( 'Run2', 14 ) || lpad( 'Diff', 14 ) );
for x in
( select rpad( a.name, 40 ) ||
to_char( b.value-a.value, '99999,999,999' ) ||
to_char( c.value-b.value, '99999,999,999' ) ||
to_char( ( (c.value-b.value)-(b.value-a.value)), '99999,999,999' ) data
from run_stats a, run_stats b, run_stats c
where a.name = b.name
and b.name = c.name
and a.runid = 'before'
and b.runid = 'after 1'
and c.runid = 'after 2'
-- and (c.value-a.value) > 0
and abs( (c.value-b.value) - (b.value-a.value) )
> p_difference_threshold
order by abs( (c.value-b.value)-(b.value-a.value)), data
) loop
dbms_output.put_line( x.data );
end loop;
dbms_output.put_line( chr(9) );
dbms_output.put_line
( 'Run1 latches total versus run2 -- difference and pct' );
dbms_output.put_line
('.'|| lpad( 'Run1', 14 ) || lpad( 'Run2', 14 ) ||
lpad( 'Diff', 14 ) || lpad( 'Pct', 11 ) );
for x in
( select '.'||
to_char( run1, '99999,999,999' ) ||
to_char( run2, '99999,999,999' ) ||
to_char( diff, '99999,999,999' ) ||
to_char( round( run1/run2*100,2 ), '99,999.99' ) || '%' data
from ( select sum(b.value-a.value) run1, sum(c.value-b.value) run2,
sum( (c.value-b.value)-(b.value-a.value)) diff
from run_stats a, run_stats b, run_stats c
where a.name = b.name
and b.name = c.name
and a.runid = 'before'
and b.runid = 'after 1'
and c.runid = 'after 2'
and a.name like 'LATCH%'
)
) loop
dbms_output.put_line( x.data );
end loop;
end;
procedure rs_report( p_difference_threshold in number default 0, p_host in varchar2 default Null )
is
begin
g_host := substr(SYS_CONTEXT('USERENV','TERMINAL'),1,30);
g_ip := substr(SYS_CONTEXT('USERENV','IP_ADDRESS'),1,30);
g_run2 := (dbms_utility.get_time-g_start);
dbms_output.put_line
( 'Run1 ran in ' || g_run1 || ' hsecs' );
dbms_output.put_line
( 'Run2 ran in ' || g_run2 || ' hsecs' );
dbms_output.put_line
( 'run 1 ran in ' || round(g_run1/g_run2*100,2) ||
'% of the time' );
dbms_output.put_line( chr(9) );
dbms_output.put_line
( rpad( 'Name', 40 ) || lpad( 'Run1', 14 ) ||
lpad( 'Run2', 14 ) || lpad( 'Diff', 14 ) );
for x in
( select rpad( a.name, 40 ) ||
to_char( b.value-a.value, '99999,999,999' ) ||
to_char( c.value-b.value, '99999,999,999' ) ||
to_char( ( (c.value-b.value)-(b.value-a.value)), '99999,999,999' ) data
from run_stats_save a, run_stats_save b, run_stats_save c
where a.name = b.name
and b.name = c.name
and a.runid = 'before'
and b.runid = 'after 1'
and c.runid = 'after 2'
-- and (c.value-a.value) > 0
and abs( (c.value-b.value) - (b.value-a.value) )
> p_difference_threshold
and a.hostname = g_host
and a.hostname = b.hostname
and a.hostname = c.hostname
order by abs( (c.value-b.value)-(b.value-a.value))
) loop
dbms_output.put_line( x.data );
end loop;
dbms_output.put_line( chr(9) );
dbms_output.put_line
( 'Run1 latches total versus run2 -- difference and pct' );
dbms_output.put_line
( lpad( 'Run1', 14 ) || lpad( 'Run2', 14 ) ||
lpad( 'Diff', 14 ) || lpad( 'Pct', 11 ) );
for x in
( select to_char( run1, '99999,999,999' ) ||
to_char( run2, '99999,999,999' ) ||
to_char( diff, '99999,999,999' ) ||
to_char( round( run1/run2*100,2 ), '99,999.99' ) || '%' data
from ( select sum(b.value-a.value) run1, sum(c.value-b.value) run2,
sum( (c.value-b.value)-(b.value-a.value)) diff
from run_stats_save a, run_stats_save b, run_stats_save c
where a.name = b.name
and b.name = c.name
and a.runid = 'before'
and b.runid = 'after 1'
and c.runid = 'after 2'
and a.name like 'LATCH%'
and a.hostname = g_host
and a.hostname = b.hostname
and a.hostname = c.hostname
)
) loop
dbms_output.put_line( x.data );
end loop;
end;
-- select * from TABLE(runstats_pkg.rs_report);
function rs_report( p_difference_threshold in number default 0, p_host in varchar2 default Null )
return print_tab PIPELINED DETERMINISTIC
as
begin
g_host := substr(SYS_CONTEXT('USERENV','TERMINAL'),1,30);
g_ip := substr(SYS_CONTEXT('USERENV','IP_ADDRESS'),1,30);
g_run2 := (dbms_utility.get_time-g_start);
PIPE ROW
( 'Run1 ran in ' || g_run1 || ' hsecs' );
PIPE ROW
( 'Run2 ran in ' || g_run2 || ' hsecs' );
PIPE ROW
( 'run 1 ran in ' || round(g_run1/g_run2*100,2) ||
'% of the time' );
PIPE ROW( chr(9) );
PIPE ROW
( rpad( 'Name', 40 ) || lpad( 'Run1', 14 ) ||
lpad( 'Run2', 14 ) || lpad( 'Diff', 14 ) );
for x in
( select rpad( a.name, 40 ) ||
to_char( b.value-a.value, '99999,999,999' ) ||
to_char( c.value-b.value, '99999,999,999' ) ||
to_char( ( (c.value-b.value)-(b.value-a.value)), '99999,999,999' ) data
from run_stats_save a, run_stats_save b, run_stats_save c
where a.name = b.name
and b.name = c.name
and a.runid = 'before'
and b.runid = 'after 1'
and c.runid = 'after 2'
-- and (c.value-a.value) > 0
and abs( (c.value-b.value) - (b.value-a.value) )
> p_difference_threshold
and a.hostname = g_host
and a.hostname = b.hostname
and a.hostname = c.hostname
order by abs( (c.value-b.value)-(b.value-a.value)), abs(c.value-b.value)
) loop
PIPE ROW( x.data );
end loop;
PIPE ROW( chr(9) );
PIPE ROW
( 'Run1 latches total versus run2 -- difference and pct' );
PIPE ROW
( lpad( 'Run1', 14 ) || lpad( 'Run2', 14 ) ||
lpad( 'Diff', 14 ) || lpad( 'Pct', 11 ) );
for x in
( select to_char( run1, '99999,999,999' ) ||
to_char( run2, '99999,999,999' ) ||
to_char( diff, '99999,999,999' ) ||
to_char( round( run1/run2*100,2 ), '99,999.99' ) || '%' data
from ( select sum(b.value-a.value) run1, sum(c.value-b.value) run2,
sum( (c.value-b.value)-(b.value-a.value)) diff
from run_stats_save a, run_stats_save b, run_stats_save c
where a.name = b.name
and b.name = c.name
and a.runid = 'before'
and b.runid = 'after 1'
and c.runid = 'after 2'
and a.name like 'LATCH%'
and a.hostname = g_host
and a.hostname = b.hostname
and a.hostname = c.hostname
)
) loop
PIPE ROW( x.data );
end loop;
end;
end;
/
grant execute on utility.runstats_pkg to public;
create or replace public synonym runstats_pkg for utility.runstats_pkg;
/*
--Usage: to benchmark two approaches
--you may just leave approach 2 code part empty, to get resources of code 1 take.
set serveroutput on
execute runStats_pkg.rs_start;
execute runStats_pkg.rs_middle;
execute runStats_pkg.rs_stop;
begin
runStats_pkg.rs_start;
for c in ()
loop
Null;
end loop;
runStats_pkg.rs_middle;
for c in ()
loop
Null;
end loop;
runStats_pkg.rs_stop;
end;
--To get the report after benchmark:
select * from TABLE(runstats_pkg.rs_report);
OR
exec runStats_pkg.rs_report(10);
*/
Monday, April 26, 2010
Find and delete duplicate rows by Analytic Function
The intuitive way will be create a temp table, with Min(RowID) and Count(*)>1, then join it back to target table to do the delete.
You can get duplicate rows by Analytic SQL:
Get duplicate row count with Count(*) > 0.
To delete them:
You can get duplicate rows by Analytic SQL:
SELECT rid, deptno, job, rn
FROM
(SELECT /*x parallel(a) */
ROWID rid, deptno, job,
ROW_NUMBER () OVER (PARTITION BY deptno, job ORDER BY empno) rn
FROM scott.emp a
)
WHERE rn <> 1;
Get duplicate row count with Count(*) > 0.
SELECT /*x parallel(a,8) */ MAX(ROWID) rid, deptno, job, COUNT(*) FROM scott.emp a GROUP BY deptno, job HAVING COUNT(*) > 1;
To delete them:
DELETE FROM scott.emp
WHERE ROWID IN
(
SELECT rid
FROM (SELECT /*x parallel(a) */
ROWID rid, deptno, job,
ROW_NUMBER () OVER (PARTITION BY deptno, job ORDER BY empno) rn
FROM scott.emp a)
WHERE rn <> 1
);
Subscribe to:
Posts (Atom)