In an Oracle database, how can I see the SQL that is really executed?
Let's say I have a query that looks like this:
WITH numsA
AS (SELECT 1 num FROM DUAL
UNION
SELECT 2 FROM DUAL
UNION
SELECT 3 FROM DUAL)
SELECT *
FROM numsA
FULL OUTER JOIN (SELECT 3 num FROM DUAL
UNION
SELECT 4 FROM DUAL
UNION
SELECT 5 FROM DUAL) numsB
ON numsA.num = numsB.num
I suppose that the SQL engine will rewrite this SQL into something different before executing it.
Can some tell me how can I see that rewritten query (with tkprof maybe)?
As #Gordon already commented that query does not execute in the oracle. Oracle creates the execution plan and further processing is done using the best plan chosen by the optimizer.
If you are keen to see how the query is executed then you must go for the execution plan.
Many tools provide the feature to directly see the execution plan and if you want to see the execution plan by yourself then you can achieve it using the following technique(taking the simplest example with query SELECT 1 FROM DUAL):
SQL> explain plan for
2 select 1 from dual;
Explained.
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 1388734953
-----------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
-----------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2 (0)| 00:00:01 |
| 1 | FAST DUAL | | 1 | 2 (0)| 00:00:01 |
-----------------------------------------------------------------
8 rows selected.
SQL>
To understand the explain plan you must have to go through all the details related to it.
I advise you to refer to Oracle documentation for Reading Execution Plans
Cheers!!
Related
can anybody explain me why the following Query returns two Rows and not only one?
SELECT *
FROM (SELECT 'ASDF' c1, MAX (SUM (1)) c2
FROM DUAL
GROUP BY dummy
UNION
SELECT 'JKLÖ' c1, 1 c2
FROM DUAL)
WHERE c1 != 'ASDF';
--another Version with the same wrong result:
SELECT *
FROM (SELECT 1 c1, MAX (SUM (1)) c2
FROM DUAL
GROUP BY dummy
UNION all
SELECT 2 c1, 1 c2
FROM DUAL)
WHERE c1 != 1;
Is it correct that Oracle delivers two rows? In my opinion the Row with c1 = ASDF should not be in the result.
Here is a Screenshot of the result from the first query:
I have tested it on the following Versions, always with the same result:
Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit Production
Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production
No this is not a bug. Aggregate functions are the reason why you see this unexpected result. Here is how it works. SUM() function as well as MAX() function will return NULL(producing 1 row) if there is no rows returned by the query. When your query is executed optimizer applies predicate pushing transformation and your original query becomes(will not post the entire trace, only transformed query):
SELECT "from$_subquery$_001"."C1" "C1",
"from$_subquery$_001"."C2" "C2"
FROM (
(SELECT 'ASDF' "C1",MAX(SUM(1)) "C2"
FROM "SYS"."DUAL" "DUAL"
WHERE 'ASDF'<>'ASDF' [1]-- predicate pushed into the view
GROUP BY "DUAL"."DUMMY" )
UNION
(SELECT 'JKLÖ' "C1",
1 "C2"
FROM "SYS"."DUAL" "DUAL"
WHERE 'JKLÖ'<>'ASDF')) "from$_subquery$_001"
[1] Because of predicate pushing your fist sub-query returns no rows and when an aggregate function(except count and few others), MAX or SUM or even both as in this case used on empty result set NULL will be returned - 1 row + 1 row return by the second sub-query thus producing 2 rows result set you are looking at.
Here is simple demonstration:
create table empty_table (c1 varchar2(1));
select 'aa' literal, nvl(max(c1), 'NULL') as res
from empty_table
LITERAL RES
------- ----
aa NULL
1 row selected.
It definitely looks like a bug.
I don't really know how to read explain plans, but here it is. It seems to me the predicate has been pushed to only one of the UNION members and it has been transformed into "NULL IS NOT NULL" which is totally weird.
Note that the strings could be changed to 'a' and 'b' (so we don't use special characters), UNION and UNION ALL produce the same bug, and the bug seems to be triggered by the MAX(SUM(1)) in the first branch; simply replacing that with NULL or anything else that's "simple", or even with SUM(1) (without the MAX) causes the query to work correctly.
ADDED: Strangely, if I change MAX(SUM(1)) to either MAX(1) or SUM(1), or if I simply change it to the literal number 1, the query works correctly - but the Explain Plan still shows the same weird predicate, "NULL IS NOT NULL". So, it seems the problem is that the predicate is not pushed to both branches of the union, not the predicate transformation. (And even that doesn't explain why c2 appears as NULL in the extra row in the result set.) MORE ADDED (see Comments below) - as it turns out, the predicate IS pushed to both branches of the UNION, and this is exactly what causes the problem (as Nicholas explains in his answer).
Plan hash value: 1682090214
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 32 | 2 (0)| 00:00:01 |
| 1 | VIEW | | 2 | 32 | 2 (0)| 00:00:01 |
| 2 | UNION-ALL | | | | | |
| 3 | SORT AGGREGATE | | 1 | 2 | | |
| 4 | HASH GROUP BY | | 1 | 2 | | |
|* 5 | FILTER | | | | | |
| 6 | TABLE ACCESS FULL| DUAL | 1 | 2 | 2 (0)| 00:00:01 |
| 7 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
-------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
5 - filter(NULL IS NOT NULL)
A much simpler example results in the same error:
SELECT 'ASDF' c1, MAX (SUM (1)) c2
FROM DUAL where 'ASDF' <> 'ASDF'
GROUP BY dummy
I must confess that I am totally confused. Why doesn't the filter eliminate the record, thereby eliminating any result set?
all,
I am learning to tune query now, when I ran the following:
select /*+ gather_plan_statistics */ * from emp;
select * from table(dbms_xplan.display(FORMAT=>'ALLSTATS LAST'));
The result always says:
Warning: basic plan statistics not available. These are only collected when:
hint 'gather_plan_statistics' is used for the statement or
parameter 'statistics_level' is set to 'ALL', at session or system level
I tried the alter session set statistics_level = ALL; too in sqlplus, but that did not change anything in the result.
Could anyone please let me know what I might have missed?
Thanks so much.
DISPLAY Function displays a content of PLAN_TABLE generated (filled) by EXPLAIN PLAN FOR command. So you can use it to generate and display an (theoretical) plan using EXPLAIN PLAN FOR command, for example in this way:
create table emp as select * from all_objects;
explain plan for
select /*+ gather_plan_statistics */ count(*) from emp where object_id between 100 and 150;
select * from table(dbms_xplan.display );
Plan hash value: 2083865914
---------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 5 | 351 (1)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 5 | | |
|* 2 | TABLE ACCESS FULL| EMP | 12 | 60 | 351 (1)| 00:00:01 |
---------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("OBJECT_ID"<=150 AND "OBJECT_ID">=100)
/*+ gather_plan_statistics */ hint does not save data into PLAN_TABLE, but it stores execution statistics in V$SQL_PLAN performance view.
To display these data you can use a method described here: http://www.dba-oracle.com/t_gather_plan_statistics.htm, but this not always work, because you must execute the second command immediately after the SQL query.
The better method is to query V$SQL view to obtain SQL_ID of the query, and then use DISPLAY_CURSOR function, for example in this way:
select /*+ gather_plan_statistics */ count(*) from emp where object_id between 100 and 150;
select sql_id, plan_hash_value, child_number, executions, fetches, cpu_time, elapsed_time, physical_read_requests, physical_read_bytes
from v$sql s
where sql_fulltext like 'select /*+ gather_plan_statistics */ count(*)%from emp%'
and sql_fulltext not like '%from v$sql' ;
SQL_ID PLAN_HASH_VALUE CHILD_NUMBER EXECUTIONS FETCHES CPU_TIME ELAPSED_TIME PHYSICAL_READ_REQUESTS PHYSICAL_READ_BYTES
------------- --------------- ------------ ---------- ---------- ---------- ------------ ---------------------- -------------------
9jjm288hx7buz 2083865914 0 1 1 15625 46984 26 10305536
The above query returns SQL_ID=9jjm288hx7buz and CHILD_NUMBER=0(child number is just a cursor number). Use these values to query the colledted plan:
SELECT * FROM table(DBMS_XPLAN.DISPLAY_CURSOR('9jjm288hx7buz', 0, 'ALLSTATS'));
SQL_ID 9jjm288hx7buz, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ count(*) from emp where object_id
between 100 and 150
Plan hash value: 2083865914
-------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | | 2 |00:00:00.05 | 10080 |
| 1 | SORT AGGREGATE | | 2 | 1 | 2 |00:00:00.05 | 10080 |
|* 2 | TABLE ACCESS FULL| EMP | 2 | 47 | 24 |00:00:00.05 | 10080 |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter(("OBJECT_ID"<=150 AND "OBJECT_ID">=100))
If all you ran were the two statements in your question:
select /*+ gather_plan_statistics */ * from emp;
select * from table(dbms_xplan.display(FORMAT=>'ALLSTATS LAST'));
Then I think your problem is your use of DBMS_XPLAN.DISPLAY. The way you are using it, you are printing the plan of the last statement you explained, not the last statement you executed. And "explain" will not execute the query, so it will not benefit from a gather_plan_statistics hint.
This works for me in 12c:
select /*+ gather_plan_statistics */ count(*) from dba_objects;
SELECT *
FROM TABLE (DBMS_XPLAN.display_cursor (null, null, 'ALLSTATS LAST'));
i.e., display_cursor instead of just display.
What I leared from the answers so far:
When a query is parsed, the optimizer estimates how many rows are produced during each step of the query plan. Sometimes it is neccessary to check how good the prediction was. If the estimates are off by more than a order of magnitude, this might lead to the wrong plan being used.
To compare estimated and actual numbers, the following steps are necessary:
You need read access to V$SQL_PLAN, V$SESSION and V$SQL_PLAN_STATISTICS_ALL. These privileges are included in the SELECT_CATALOG role. (source)
Switch on statistics gathering, either by
ALTER SESSION SET STATISTICS_LEVEL = ALL;
or by using the hint /*+ gather_plan_statistics */ in the query.
There seems to be a certain performance overhead.
See for instance Jonathan's blog.
Run the query. You'll need to find it later, so it's best to include an arbitrary hint:
SELECT /*+ gather_plan_statistics HelloAgain */ * FROM scott.emp;
EXPLAIN PLAN FOR SELECT ... is not sufficient, as it will only create the estimates without running the actual query.
Furthermore, as #Matthew suggested (thanks!), it is important to actually fetch all rows. Most GUIs will show only the first 50 rows or so. In SQL Developer, you can use the shortcut ctrl+End in the query result window.
Find the query in the cursor cache and note it's SQL_ID:
SELECT sql_id, child_number, sql_text
FROM V$SQL
WHERE sql_text LIKE '%HelloAgain%';
dbqbqxp9srftn 0 SELECT /*+ gather_plan...
Format the result:
SELECT *
FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('dbqbqxp9srftn',0,'ALLSTATS LAST'));
Steps 4. and 5. can be combined:
SELECT x.*
FROM v$sql s,
TABLE(DBMS_XPLAN.DISPLAY_CURSOR(s.sql_id, s.child_number)) x
WHERE s.sql_text LIKE '%HelloAgain%';
The result shows the estimated rows (E-Rows) and the actual rows (A-Rows):
SQL_ID dbqbqxp9srftn, child number 0
-------------------------------------
SELECT /*+ gather_plan_statistics HelloAgain */ * FROM scott.emp
Plan hash value: 3956160932
------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 14 |00:00:00.01 | 6 |
| 1 | TABLE ACCESS FULL| EMP | 1 | 14 | 14 |00:00:00.01 | 6 |
------------------------------------------------------------------------------------
ALLSTATS LAST starts working after you ran the statement twice.
This question already has answers here:
How to optimize an Oracle query that has to_char in where clause for date
(6 answers)
Closed 9 years ago.
I have simple SQL query.. on Oracle 10g. I want to know the difference between these queries:
select * from employee where id = 123 and
to_char(start_date, 'yyyyMMdd') >= '2013101' and
to_char(end_date, 'yyyyMMdd') <= '20121231';
select * from employee where id = 123 and
start_date >= to_date('2013101', 'yyyyMMdd') and
end_date <= to_date('20121231', 'yyyyMMdd');
Questions:
1. Are these queries the same? start_date, end_date are indexed date columns.
2. Does one work better over the other?
Please let me know. thanks.
The latter is almost certain to be faster.
It avoids data type conversions on a column value.
Oracle will estimate better the number of possible values between two dates, rather than two strings that are representations of dates.
Note that neither will return any rows as the lower limit is probably intended to be higher than the upper limit according to the numbers you've given. Also you've missed a numeral in 2013101.
One of the biggest flaw when you converting, casting or transforming to expression (i.e. "NVL", "COALESCE" etc.) columns in WHERE clause is that CBO will not be able to use index on that column. I slightly modified your example to show the difference:
SQL> create table t_test as
2 select * from all_objects;
Table created
SQL> create index T_TEST_INDX1 on T_TEST(CREATED, LAST_DDL_TIME);
Index created
Created table and index for our experiment.
SQL> execute dbms_stats.set_table_stats(ownname => 'SCOTT',
tabname => 'T_TEST',
numrows => 100000,
numblks => 10000);
PL/SQL procedure successfully completed
We are making CBO think that our table kind of big one.
SQL> explain plan for
2 select *
3 from t_test tt
4 where tt.owner = 'SCOTT'
5 and to_char(tt.last_ddl_time, 'yyyyMMdd') >= '20130101'
6 and to_char(tt.created, 'yyyyMMdd') <= '20121231';
Explained
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 2796558804
----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 3 | 300 | 2713 (1)| 00:00:33 |
|* 1 | TABLE ACCESS FULL| T_TEST | 3 | 300 | 2713 (1)| 00:00:33 |
----------------------------------------------------------------------------
Full table scan is used which would be costly on big table.
SQL> explain plan for
2 select *
3 from t_test tt
4 where tt.owner = 'SCOTT'
5 and tt.last_ddl_time >= to_date('20130101', 'yyyyMMdd')
6 and tt.created <= to_date('20121231', 'yyyyMMdd');
Explained
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 1868991173
-------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 3 | 300 | 4 (0)| 00:00:01 |
|* 1 | TABLE ACCESS BY INDEX ROWID| T_TEST | 3 | 300 | 4 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | T_TEST_INDX1 | 8 | | 3 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------
See, now it's index range scan and the cost is significantly lower.
SQL> drop table t_test;
Table dropped
Finally cleaning.
for output (displaying) purpose use to_char
for "date" handling (insert, update, compare etc) use to_date
I don't have any performance link to share, but using to_date in above Query should run faster!
While the to_char it will first cast the date and then for making the compare it will need to resolve it as date type. There will be a small performance loss.
As using to_date it will not need to cast first, it will use date type directly.
Similar to this question.
I'd like to get a detailed query plan and actual execution in Oracle (10g) similar to EXPLAIN ANALYZE in PostgreSQL. Is there an equivalent?
The easiest way is autotrace in sql*plus.
SQL> set autotrace on exp
SQL> select count(*) from users ;
COUNT(*)
----------
137553
Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=66 Card=1)
1 0 SORT (AGGREGATE)
2 1 INDEX (FAST FULL SCAN) OF 'SYS_C0062362' (INDEX (UNIQUE)
) (Cost=66 Card=137553)
Alternately, oracle does have an explain plan statement, that you can execute and then query the various plan tables. Easiest way is using the DBMS_XPLAN package:
SQL> explain plan for select count(*) from users ;
Explained.
SQL> SELECT * FROM table(DBMS_XPLAN.DISPLAY);
--------------------------------------------------------------
| Id | Operation | Name | Rows | Cost |
--------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 66 |
| 1 | SORT AGGREGATE | | 1 | |
| 2 | INDEX FAST FULL SCAN| SYS_C0062362 | 137K| 66 |
--------------------------------------------------------------
If you're old-school, you can query the plan table yourself:
SQL> explain plan set statement_id = 'my_statement' for select count(*) from users;
Explained.
SQL> column "query plan" format a50
SQL> column object_name format a25
SQL> select lpad(' ',2*(level-1))||operation||' '||options "query plan", object_name
from plan_table
start with id=0 and statement_id = '&statement_id'
connect by prior id=parent_id
and prior statement_id=statement_id
Enter value for statement_id: my_statement
old 3: start with id=0 and statement_id = '&statement_id'
new 3: start with id=0 and statement_id = 'my_statement'
SELECT STATEMENT
SORT AGGREGATE
INDEX FAST FULL SCAN SYS_C0062362
Oracle used to ship with a utility file utlxpls.sql that had a more complete version of that query. Check under $ORACLE_HOME/rdbms/admin.
For any of these methods, your DBA must have set up the appropriate plan tables already.
I've just discovered that Oracle lets you do the following:
SELECT foo.a, (SELECT c
FROM bar
WHERE foo.a = bar.a)
from foo
As long as only one row in bar matches any row in foo.
The explain plan I get from PL/SQL developer is this:
SELECT STATEMENT, GOAL = ALL_ROWS
TABLE ACCESS FULL BAR
TABLE ACCESS FULL FOO
This doesn't actually specify how the tables are joined. A colleague asserted that this is more efficient than doing a regular join. Is that true? What is the join strategy on such a select statement, and why doesn't it show up in the explain plan?
Thanks.
The plan you have there does not provide much information at all.
Use SQL*Plus and use dbms_xplan to get a more detailed plan. Look for a script called utlxpls.sql.
This gives a bit more information:-
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1837 | 23881 | 3 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| BAR | 18 | 468 | 2 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL| FOO | 1837 | 23881 | 3 (0)| 00:00:01 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("BAR"."A"=:B1)
Note
-----
- dynamic sampling used for this statement
18 rows selected.
I didn't create any indexes or foreign keys or collect statistics on the tables, which would change the plan and the join mechanism choosen. Oracle is actually doing a NESTED LOOPS type join here. Step 1, your inline sub-select, is performed for every row returned from FOO.
This way of performing a SELECT is not quicker. It could be the same or slower. In general try and join everything in the main WHERE clause unless it becomes horribly unreadable.
If you create a normal index on bar(a) the CBO should be able to use, but I'm pretty sure that it won't be able to do hash joins. These kind of queries only make sense if you're using an aggregate function and you got multiple single-row subqueries in your top SELECT. Even so, you can always rewrite the query as:
SELECT foo.a, bar1.c, pub1.d
FROM foo
JOIN (SELECT a, MIN(c) as c
FROM bar
GROUP BY a) bar1
ON foo.a = bar1.a
JOIN (SELECT a, MAX(d) as d
FROM pub
GROUP BY a) pub1
ON foo.a = pub1.a
This would enable the CBO to use more options, while at the same time it would enable you to easily retrieve multiple columns from the child tables without having to scan the same tables multiple times.