Optimizer using an index not present in the current schema - oracle

CONNECT alll/all
SELECT /*+ FIRST_ROWS(25) */ employee_id, department_id
FROM hr.employees
WHERE department_id > 50;
Execution Plan
Plan hash value: 2056577954
| Id | Operation | Name | Rows | Bytes |
| 0 | SELECT STATEMENT | | 25 | 200
| 1 | TABLE ACCESS BY INDEX ROWID| EMPLOYEES | 25 | 200
|* 2 | INDEX RANGE SCAN | **EMP_DEPARTMENT_IX** | |
SQL> select * from user_indexes where index_name = 'EMP_DEPARTMENT_IX';
no rows selected
NOTE: There is an index with the same name on the DEPARTMENT column of the EMPLOYEES table in some other schema. And when that index is dropped a FULL TABLE SCAN on EMPLOYEES is performed.
Can the optimizer use that other index from some other schema over here?

You're connected as user ALLL, but you're querying a table in the HR schema:
SELECT /*+ FIRST_ROWS(25) */ employee_id, department_id
FROM hr.employees
WHERE department_id > 50;
You stressed other schema in the question, but seem to have overlooked that the table you're querying is also in another schema. The employees table won't appear in user_tables either.
The index being used is associated with that table, so it's likely to be in the same HR schema. You can see it in all_indexes or dba_indexes; the optimiser will use it even if you can't see it though. And it doesn't have to be in the same schema as the table, though it usually will be; in those views you might notice separate owner and table owner columns.
The schema model would break down if you could only utilise indexes in your own schema when accessing a table in someone else's. Every user would have to create their own copies of the indexes, which would be untenable.
You don't even necessarily have to be able to see the table - if you query a view that hides the underlying table from you (so you have select privs on the view only) the index will still be used in the background. And you might not always be explicitly using the schema prefix, if there is a synonym for the table, or you change your default schema.

Try looking in SYS.INDEXES:
select * from SYS.INDEXES where IXNAME = 'EMP_DEPARTMENT_IX'
Sounds like you are not the owner of the index, as you have noted. As long as your user can access the table data, then the index should be used by the optimizer.

Related

Oracle 12c - view plans access unnecessary tables

I am hoping for an explanation of some view + optimizer behavior
lets make a view like this
select e.employee_name, e.employee_active, ei.marital_status
from employees e
left join employee_info ei on ei.employee_id = e.employee_id
Then lets access it like this
select distinct employee_name from employee_view where employee_id = 13
In my head, I'm thinking we do not need to access employee_info table at all for this query to execute in a functionally correct way. Yet, when I explain plans on this query, I see it accessing employee_info. Am I missing something that makes it necessary to actually perform that join? If it is functionally equivalent to not hit that table, is there a way to stop that from happening in 12c?
You deduction is basically correct, but you must take in account than not every optimization that is possible is also implemented.
Oracle pragmatically observes the potential and decides where the optimization should be extended.
For example there were times where the predicate where 1=1 and ... could confuse the optimizer, but as this "construction" is used rather often, in recent version the optimizer recognised it and ignore it without side effects.
Your setup of outer join to child, while selecting distinct from the parent columns only is probably not very frequent and therefore not yet covered.
Here a similar example, where Oracle can skip a table from a view (Note the PK and FK definition, which is crutial)
create table employees (
employee_id int primary key,
employee_name varchar2(10),
employee_active varchar2(10));
create table employee_info (
employee_id int,
marital_status varchar2(10));
alter table employee_info
add constraint emp_info_fk1 foreign key (employee_id)
references employees (employee_id);
create or replace view employee_view as
select e.EMPLOYEE_ID, e.employee_name, e.employee_active, ei.marital_status
from employees e
join employee_info ei on ei.employee_id = e.employee_id;
The query select marital_status from employee_view where employee_id = 13 can skip the employees (parent) table and access only the child table
-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 20 | 2 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| EMPLOYEE_INFO | 1 | 20 | 2 (0)| 00:00:01 |
-----------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("EI"."EMPLOYEE_ID"=13)
More information and links in similar answer

Column definition incompatible with clustered column definition

I have created a cluster in Oracle
CREATE CLUSTER myLovelyCluster (clust_id NUMBER(38,0))
SIZE 1024 SINGLE TABLE HASHKEYS 11;
Than a table for the cluster
CREATE TABLE Table_cluster
CLUSTER myLovelyCluster (columnRandom)
AS SELECT * FROM myTable ;
the columnRandom is well defined as NUMBER(38,0) but why I am getting an error assuming incompatible column definition?
Thanks
Are you sure that columnRandom is number(38,0)? In oracle NUMBER != NUMBER(38,0)
Let's create two table.
create table src_table ( a number);
create table src_table2( a number(38,0));
select column_name,data_precision,Data_scale from user_tab_cols where table_name like 'SRC_TABLE%';
Result of query is. Definitions of column are different.
+-------------+----------------+------------+
| Column_name | Data_Precision | Data_scale |
+-------------+----------------+------------+
| A | | |
| A | 38 | 0 |
+-------------+----------------+------------+
And if i try creat cluster for first table.
CREATE TABLE Table_cluster
CLUSTER myLovelyCluster (a)
AS SELECT * FROM src_table ;
ORA-01753: column definition incompatible with clustered column definition
For 2-nd every thing is ok.
CREATE TABLE Table_cluster
CLUSTER myLovelyCluster (a)
AS SELECT * FROM src_table2 ;
If you add cast into select. Execution also is correct.
CREATE TABLE Table_cluster CLUSTER myLovelyCluster (a)
AS SELECT cast(a as number(38,0)) FROM src_table;

Oracle Select * returns rows but Select count(1) return 0

So, this is bizarre and it's something I have never seen before. I'm hoping someone has the magic answer that can shed some light on this problem...
SELECT * FROM TABLE -- returns rows... a lot of rows
however,
SELECT COUNT(1) FROM TABLE -- returns zero (0), as in the number zero (0) as the result
Here's the table structure:
CREATE TABLE TRACKING (
A_ID NUMBER,
D_CODE NUMBER,
HOD NUMBER,
ADR_CNT NUMBER,
TTL_CNT NUMBER,
CREATED DATE,
MODIFIED DATE
);
CREATE INDEX HOD_D_CODE_IDX ON TRACKING (HOD, D_CODE);
CREATE UNIQUE INDEX TRACKING_PK ON TRACKING (A_ID, D_CODE, HOD);
CREATE INDEX MOD_DATE_IDX ON TRACKING (MODIFIED);
ALTER TABLE TRACKING ADD CONSTRAINT TRACKING_PK PRIMARY KEY (A_ID, D_CODE, HOD);
How can an Oracle table have rows but count(1) return zero? I've done some searching on the web but found nothing. The only other post I found was in relation to MS SQL Server. This is happening in Oracle.
Any idea? Anyone?
Thank you in advance for any help you can provide.
Another thing I might add in hopes that it will help answer the puzzle, this table was used by an Oracle Job to aggregate and populate another table. However, it's been done for a few days now. The other table is fully populated and shows expected record counts. I checked the Oracle Job Log and it shows all success and not a single error.
Wrong results can be caused by corruption, bugs, and features that silently change SQL statements.
Corrupt index. Very rarely an index gets corrupt and the data from an index does not match the data from a table. This causes unexpected results when the query plan changes and an index is used, but everything looks normal for different queries that use table access. Sometimes simply re-building objects can fix this. If it doesn't, you'll need to create a fully reproducible test case (including data); either post it here or submit it to Oracle Support. It can take many hours to track this down.
Bug. Very rarely a bug can cause queries to fail when returning or changing data. Again, a fully reproducible test case is required to
diagnose this, and it can take a while.
Feature that switches SQL There are a few ways to transparently alter SQL statements. Look into Virtual Private Database (VPD), DBMS_ADVANCED_REWRITE, and the SQL Translation Framework.
To rule out #3, the code below shows you one of the evil ways to do this, and how to detect it. First, create the schema and some data:
CREATE TABLE TRACKING (
A_ID NUMBER,
D_CODE NUMBER,
HOD NUMBER,
ADR_CNT NUMBER,
TTL_CNT NUMBER,
CREATED DATE,
MODIFIED DATE
);
CREATE INDEX HOD_D_CODE_IDX ON TRACKING (HOD, D_CODE);
CREATE UNIQUE INDEX TRACKING_PK ON TRACKING (A_ID, D_CODE, HOD);
CREATE INDEX MOD_DATE_IDX ON TRACKING (MODIFIED);
ALTER TABLE TRACKING ADD CONSTRAINT TRACKING_PK PRIMARY KEY (A_ID, D_CODE, HOD);
insert into tracking values (1,2,3,4,5,sysdate,sysdate);
commit;
At first, everything works as expected:
SQL> SELECT * FROM TRACKING;
A_ID D_CODE HOD ADR_CNT TTL_CNT CREATED MODIFIED
---------- ---------- ---------- ---------- ---------- --------- ---------
1 2 3 4 5 17-JUN-16 17-JUN-16
SQL> SELECT COUNT(1) FROM TRACKING;
COUNT(1)
----------
1
Then someone does this:
begin
sys.dbms_advanced_rewrite.declare_rewrite_equivalence(
'april_fools',
'SELECT COUNT(1) FROM TRACKING',
'SELECT 0 FROM TRACKING WHERE ROWNUM = 1',
false);
end;
/
Now the results are "wrong":
SQL> ALTER SESSION SET query_rewrite_integrity = trusted;
Session altered.
SQL> SELECT COUNT(1) FROM TRACKING;
COUNT(1)
----------
0
This can be probably be detected by looking at the explain plan. In the example below, the Predicate 2 - filter(ROWNUM=1) is a clue that something is wrong, since that predicate is not in the original query. Sometimes the "Notes" section of the explain plan will tell you exactly why it was transformed, but sometimes it only gives clues.
SQL> explain plan for SELECT COUNT(1) FROM TRACKING;
Explained.
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------
Plan hash value: 1761840423
------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2 | 1 (0)| 00:00:01 |
| 1 | VIEW | | 1 | 2 | 1 (0)| 00:00:01 |
|* 2 | COUNT STOPKEY | | | | | |
| 3 | INDEX FULL SCAN| HOD_D_CODE_IDX | 1 | | 1 (0)| 00:00:01 |
------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter(ROWNUM=1)
15 rows selected.
(On an unrelated note - always use COUNT(*) instead of COUNT(1). COUNT(1) is an old myth that looks like cargo cult programming.)
COUNT(SomeColumn) will only return the count of rows that contain non-null values for SomeColumn. COUNT(*) and COUNT('Foo') will return the total number of rows in the table.
Source: Count(*) vs Count(1)
Is it possible that your column 1 has NULL for all registers? If it has no NULL's at all it should work as a Count(*) which returns all rows in that table, and if this is the case we need more information in order to help you.

Oracle partition key

I have many tables with large amount of data. The PK is the column (TAB_ID) which has data type RAW(16). I created the hash partitions with partition key having the TAB_ID column.
My issue is: the SQL statement (select * from my_table where tab_id = 'aas1df') does not use partition pruning. If I change the column datatype to varchar2(32), partition pruning works.
Why does not partition pruning work with partition key which have datatype RAW(16)?
I'm just guessing: try select * from my_table where 'aas1df' = tab_id.
Probably the datatype conversion works other way that expected. Anyway you should use the function UTL_RAW.CAST_TO_RAW.
Edited:
Is your table partitioned by TAB_ID? If yes, then there is something wrong with your design, you usually partition table by some useful business value, NOT by surrogate key.
If you know the PK value you do not need partition pruning at all. When Oracle traverses the PK index it gets ROWID value. This ROWID contains file-number, block ID and also row number within the block. So Oracle can access directly the row.
HEXTORAW enables partition pruning.
In the sample below the Pstart and Pstop are literal numbers, implying partition pruning occurs.
create table my_table
(
TAB_ID raw(16),
a number,
constraint my_table_pk primary key (tab_id)
)
partition by hash(tab_id) partitions 16;
explain plan for
select *
from my_table
where tab_id = hextoraw('1BCDB0E06E7C498CBE42B72A1758B432');
select * from table(dbms_xplan.display(format => 'basic +partition'));
Plan hash value: 1204448714
--------------------------------------------------------------------------
| Id | Operation | Name | Pstart| Pstop |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | |
| 1 | TABLE ACCESS BY GLOBAL INDEX ROWID| MY_TABLE | 2 | 2 |
| 2 | INDEX UNIQUE SCAN | MY_TABLE_PK | | |
--------------------------------------------------------------------------

Why is the NLSSORT index not used for this query?

In our application we have case-insensitive semantics configured at session level:
alter session set NLS_COMP=LINGUISTIC;
alter session set NLS_SORT=BINARY_AI;
but then I want to have a table with a NAME column with binary semantics, so I defined an function-based index accordingly:
create table RAW_SCREEN (
ID number(10) constraint RSCR_PK primary key,
NAME nvarchar2(256) not null
);
create unique index RSCR_IDX on RAW_SCREEN (nlssort(NAME, 'NLS_SORT=BINARY'));
I would have expected the query below to take advantage of the function-based index:
select * from RAW_SCREEN where
nlssort(NAME, 'NLS_SORT=BINARY') = nlssort(N'raw_screen1', 'NLS_SORT=BINARY');
but it doesn't. The query plan shows a table scan. While experimenting, I've discovered that a simple index on NAME does the trick:
create unique index RSCR_IDX2 on RAW_SCREEN (NAME);
When running the query again, the RSCR_IDX2 index was used successfully.
Now, that is not very surprising, but I can't understand why the first function-based index was not used by the optimizer. The indexed expression matches exactly the expression used in the WHERE condition. Do you have any idea why it wasn't used?
NOTE: This was run on Oracle 10.2
Here's a full test script if you want to try it out:
alter session set NLS_COMP=LINGUISTIC;
alter session set NLS_SORT=BINARY_AI;
create table RAW_SCREEN (
ID number(10) constraint RSCR_PK primary key,
NAME nvarchar2(256) not null
);
create unique index RSCR_IDX on RAW_SCREEN (nlssort(NAME, 'NLS_SORT=BINARY'));
--create unique index RSCR_IDX2 on RAW_SCREEN (NAME);
begin
for i in 1..10000
loop
insert into RAW_SCREEN values (i, 'raw_screen' || i);
end loop;
end;
/
commit;
select * from RAW_SCREEN where nlssort(NAME, 'NLS_SORT=BINARY') = nlssort(N'raw_screen1000', 'NLS_SORT=BINARY');
Expressions are converted to NLS session settings in DML, but not in DDL.
This is arguably a bug with the behavior of NLSSORT(char, 'NLS_SORT=BINARY').
From the manual: "If you specify BINARY, then this function returns char."
But that is not true for the index. Normally it is very convenient that the index expression does not undergo any transformation; if it depended on session settings
than tools like DBMS_METADATA.GET_DDL would have to return many alter session statements. But in this case it means that you can create an index that will never
be used.
The explain plan shows the real expression. Here's how Oracle uses nlssort in a session without it being explicitly used:
alter session set nls_comp=linguistic;
alter session set nls_sort=binary_ai;
drop table raw_screen;
create table raw_screen (
id number(10) constraint rscr_pk primary key,
name nvarchar2(256) not null
);
create unique index idx_binary_ai
on raw_screen (nlssort(name, 'nls_sort=binary_ai'));
explain plan for select * from raw_screen where name = n'raw_screen1000';
select * from table(dbms_xplan.display(format=>'basic predicate'));
Plan hash value: 2639454581
-----------------------------------------------------
| Id | Operation | Name |
-----------------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | TABLE ACCESS BY INDEX ROWID| RAW_SCREEN |
|* 2 | INDEX UNIQUE SCAN | IDX_BINARY_AI |
-----------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access(NLSSORT("NAME",'nls_sort=''BINARY_AI''')=HEXTORAW('0072006
10077005F00730063007200650065006E003100300030003000'))
This example shows that nlssort(char, 'nls_sort=binary') is dropped by the DML:
alter session set nls_comp=linguistic;
alter session set nls_sort=binary_ai;
drop table raw_screen;
create table raw_screen (
id number(10) constraint rscr_pk primary key,
name nvarchar2(256) not null
);
create unique index idx_binary_ai on
raw_screen (nlssort(name, 'nls_sort=binary_ai'));
explain plan for select * from raw_screen where
nlssort(name,'nls_sort=binary') = nlssort(N'raw_screen1000','nls_sort=binary');
select * from table(dbms_xplan.display(format=>'basic predicate'));
Plan hash value: 237065300
----------------------------------------
| Id | Operation | Name |
----------------------------------------
| 0 | SELECT STATEMENT | |
|* 1 | TABLE ACCESS FULL| RAW_SCREEN |
----------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("NAME"=U'raw_screen1000')
In summary - index DDL needs to exactly match the transformed expressions, which can depend on session settings and the unusual behavior of binary.
When you apply a function to a column in the where clause of a query, any corresponding indexes on this column must also include the function for Oracle to make use of them when executing the query. The NLSSORT function can be applied to strings in your where clause automatically be Oracle if you set NLS_COMP and NLS_SORT appropriately.
To enable case-insensitive searching, we must convert the strings stored in the table by a applying a function such as upper(), lower(), etc. We must then also create a function-based index on the column with the same function we use in our queries.
By changing the NLS_COMP parameter to ANSI and the NLS_SORT parameter to BINARY_CI for a session, Oracle will automatically place the NLSSORT function to strings in your query however! In this case, you don't have to change your query, as Oracle does this for you behind the scenes.

Resources