Oracle 11gR2 Function based index error - sql

I want to create a simple function based index on a simple table but i get error.
So, first of all I created a function
CREATE OR REPLACE FUNCTION promo_function(p_promo_category VARCHAR2)
RETURN VARCHAR2 DETERMINISTIC IS
BEGIN
RETURN UPPER(p_promo_category);
END promo_function;
Then I would execute this, but fails
CREATE INDEX promotions_fbi
ON SH.PROMOTIONS (promo_function (promo_category));
Why? The error is ORA-00904:"PROMO_FUNCTION": Invalid identifier
But the function works well in a query:
SELECT *
FROM sh.sales s,
sh.promotions p,
sh.times t
WHERE s.promo_id = p.promo_id
AND s.time_id = t.time_id
AND t.time_id BETWEEN DATE '2000-01-01' AND DATE '2000-03-31'
AND promo_function(p.promo_category) = 'AD NEWS';
Many thanks!

There is nothing inherently wrong with your code. I can create a function-based index like this:
SQL> create table promotions (promo_category varchar2(10))
2 /
Table created.
SQL> CREATE OR REPLACE FUNCTION promo_function
2 (p_promo_category in VARCHAR2)
3 RETURN VARCHAR2 DETERMINISTIC
4 IS
5 BEGIN
6 RETURN UPPER(p_promo_category);
7 END promo_function;
8 /
Function created.
SQL> CREATE INDEX promotions_fbi
2 ON PROMOTIONS (promo_function (promo_category));
Index created.
SQL>
The only difference between my code and yours is that I don't prefix the table in the CREATE INDEX statement. Everything is in the same schema, so I don't need to.
So, can I re-create your scenario? Here's one way. I drop the index and function, then give another uses all privileges on teh table...
SQL> drop index promotions_fbi;
Index dropped.
SQL> drop function PROMO_FUNCTION;
Function dropped.
SQL> grant all on promotions to B;
Grant succeeded.
SQL>
As that user I can create a normal index ...
SQL> conn b/b
Connected.
SQL> select * from apc.promotions;
no rows selected
SQL> CREATE INDEX promotions_i
2 ON APC.PROMOTIONS (promo_category);
Index created.
SQL>
However, if I create a function I cannot create a function-based index using it....
SQL> conn b/b
Connected.
SQL> CREATE INDEX promotions_fbi
2 ON APC.PROMOTIONS (promo_function (promo_category));
ON APC.PROMOTIONS (promo_function (promo_category))
*
ERROR at line 2:
ORA-00904: : invalid identifier
SQL>
The invalid identifier fingers the function name. Why? Because although schema B would own the index schema APC owns the table, and needs to be able to execute the function too.
The solution is to grant execute rights on the function to the table owner:
SQL> conn b/b
Connected.
SQL> grant execute on promo_function to APC;
Grant succeeded.
SQL> CREATE INDEX promotions_fbi
2 ON APC.PROMOTIONS (B.promo_function (promo_category));
Index created.
SQL>
Note that we must explicitly reference the function owner as well as the table owner in this statement. It's a bit nasty, and that's why it's generally a bad idea to spread privileges across two schemas in this fashion.
Not sure how #zaratustra gets their findings, as I can definitely create function-based in indexes using the word FUNCTION in the name...
SQL> r
1 select i.table_owner, i.owner as index_owner, i.index_name
2 , i.index_type, e.column_expression
3 from all_indexes i
4 left join all_ind_expressions e
5 on i.owner = e.index_owner
6 and i.index_name = e.index_name
7* where i.table_name = 'PROMOTIONS'
TABLE_OWNER INDEX_OWNER
------------------------------ ------------------------------
INDEX_NAME INDEX_TYPE
------------------------------ ---------------------------
COLUMN_EXPRESSION
--------------------------------------------------------------------------------
APC APC
PROMOTIONS_FBI FUNCTION-BASED NORMAL
"APC"."PROMO_FUNCTION"("PROMO_CATEGORY")
APC A
PROMO_B_I FUNCTION-BASED NORMAL
"A"."B_FUNCTION"("PROMO_CATEGORY")
APC APC
PROMOTIONS_I NORMAL
SQL>
Although I am on a different point release so that might explain it
SQL> select banner from v$version;
BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.2.0 - Production
SQL>

During my experiments I realized that Oracle can't let you create function-based index with the function word in the name of the procedure. Looks like a bug for me:
CREATE OR REPLACE FUNCTION func_function(p_promo_category IN VARCHAR2)
RETURN VARCHAR2 DETERMINISTIC IS
BEGIN
RETURN UPPER(p_promo_category);
END func_function;
create table t1 (
promo_category varchar2(4000)
);
Table created
CREATE INDEX promotions_fbi ON t1 (func_function (promo_category));
ORA-00911: invalid character
Let's create a function without the function word in the name:
CREATE OR REPLACE FUNCTION func_functio(p_promo_category IN VARCHAR2)
RETURN VARCHAR2 DETERMINISTIC IS
BEGIN
RETURN UPPER(p_promo_category);
END func_functio;
CREATE INDEX promotions_fbi ON t1 (func_functio (promo_category));
Index created
select index_name, index_type
from user_indexes
where lower(index_name) = 'promotions_fbi'
INDEX_NAME INDEX_TYPE
-------------------------------------
PROMOTIONS_FBI FUNCTION-BASED NORMAL
My Oracle version:
select banner from v$version
BANNER
----------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production

Related

Trigger on the create table as sql

I want to save the SQL create statement as following:
CREATE TABLE tbl AS
SELECT
*
FROM
tbl_info;
Is there a way to save the previous query in a table like this:
table_name query
tbl CREATE TABLE tbl AS SELECT * FROM tbl_info;
which table_name, query are the columns of this table,
i tried to look up for triggers but there is nothing about trigger before or after on create statement,
please your help.
Indeed there is a way of doing this, but it is not recommendable. Oracle offers out of the box audit capabilities much better than any custom solution you can think of.
Having said that, one option would be to use a DDL TRIGGER. In the example below you have an audit table to store the create event and a trigger to record them.
Keep in mind that I use ON SCHEMA, so it will only affect the CREATE events for the schema which the trigger belongs to.
Base code
CREATE TABLE AUDIT_DDL (
D DATE,
OSUSER VARCHAR2(255),
CURRENT_USER VARCHAR2(255),
SYSEVENT VARCHAR2(30),
STATEMENTS VARCHAR2(1000)
);
CREATE OR REPLACE TRIGGER AUDIT_DDL_TRG
AFTER DDL ON SCHEMA
DECLARE
sql_text ora_name_list_t;
v_stmt VARCHAR2(2000);
n PLS_INTEGER;
BEGIN
n := ora_sql_txt(sql_text);
FOR i IN 1 .. n LOOP
v_stmt := v_stmt || sql_text(i);
END LOOP;
v_stmt :=regexp_replace(v_stmt,
'rename[[:space:]]+.*[[:space:]]+to[[:space:]]+([a-z0-9_]+)',
'\1',
1,
1,
'i');
IF (ORA_SYSEVENT = 'CREATE')
THEN
INSERT INTO AUDIT_DDL
(D,
OSUSER,
CURRENT_USER,
SYSEVENT,
STATEMENTS)
VALUES
(SYSDATE,
SYS_CONTEXT('USERENV', 'OS_USER'),
SYS_CONTEXT('USERENV', 'CURRENT_USER'),
ORA_SYSEVENT,
v_stmt);
END IF;
END;
/
Let's check how it works
sqlplus test1/Oracle_123
SQL*Plus: Release 19.0.0.0.0 - Production on Sun Oct 3 11:13:21 2021
Version 19.6.0.0.0
Copyright (c) 1982, 2019, Oracle. All rights reserved.
Last Successful login time: Wed Sep 22 2021 08:08:57 +02:00
Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.6.0.0.0
SQL> create table t1 as select * from all_objects ;
create table t1 as select * from all_objects
*
ERROR at line 1:
ORA-00955: name is already used by an existing object
SQL> drop table t1 purge;
Table dropped.
SQL> create table t1 as select * from all_objects ;
Table created.
SQL> select d,statements from AUDIT_DDL ;
D
---------
STATEMENTS
--------------------------------------------------------------------------------
03-OCT-21
create table t1 as select * from all_objects
As you can see above ( on purpose ) , I did several statements ( a create failing, a drop table, and finally a CTAS statement ). However, our event trigger is only looking for the sysevent create, and because it is after ddl on schema, it will only store the data after the command is executed successfully.
You can store many other properties of the default context sys_context.
Nevertheless, this has a huge impact in performance and it is not recommendable

Prevent Drop View in Oracle

How to prevent a user from not dropping a view even he/she has previlige to do so? I have a view being used by an application. However, it was dropped by a user a couple times. Is there something like REVOKE DROP ANY VIEW? Do I have to use a trigger for that?
A trigger might help. Here's an example.
First, a few objects to be dropped:
SQL> create table test as select * From emp where 1 = 2;
Table created.
SQL> create or replace view v_dept as select * From dept;
View created.
SQL>
I don't want to allow V_DEPT to be dropped, so:
SQL> create or replace trigger trg_drop
2 before drop on schema
3 declare
4 l_name varchar2(30);
5 begin
6 select ora_dict_obj_name
7 into l_name
8 from dual;
9 if l_name = 'V_DEPT' then
10 raise_application_error(-20001, 'Forbidden');
11 end if;
12 end;
13 /
Trigger created.
Testing:
SQL> drop view v_dept;
drop view v_dept
*
ERROR at line 1:
ORA-00604: error occurred at recursive SQL level 1
ORA-20001: Forbidden
ORA-06512: at line 8
SQL> drop table test;
Table dropped.
SQL>

How do I invalidate a table in Oracle 11g on purpose?

I'm writing a small query to find invalid tables in Oracle :
select * from user_tables where status != 'VALID'
For testing, I thought it would be good to create a table and invalidate it on purpose. Is there a way to do this?
Invalidating a view is easy, just drop one of the underlying tables.
Any hint welcome.
You won't see status INVALID in user_tables, however, you would see that in [USER|ALL|DBA]_OBJECTS view.
One simple way is to create the table using an object type, and force the object type attribute to invalidate.
For example,
SQL> CREATE OR REPLACE TYPE mytype AS OBJECT(col1 VARCHAR2(10))
2 /
Type created.
SQL>
SQL> CREATE TABLE t(col1 NUMBER,col2 mytype)
2 /
Table created.
SQL>
SQL> SELECT object_name, object_type, status FROM user_objects WHERE object_name='T'
2 /
OBJECT_NAME OBJECT_TYPE STATUS
----------- ----------------------- -----------
T TABLE VALID
SQL>
So, the table is now in VALID status. Let's make it INVALID:
SQL> ALTER TYPE mytype ADD ATTRIBUTE col2 NUMBER INVALIDATE
2 /
Type altered.
SQL>
SQL> SELECT object_name, object_type, status FROM user_objects WHERE object_name='T'
2 /
OBJECT_NAME OBJECT_TYPE STATUS
----------- ----------------------- -----------
T TABLE INVALID
SQL>
Its an old question but I want to point on something for future readers, the opposit of valid in the status user_tables is UNUSABLE so if you want to make a table UNUSABLE,A DROP TABLE operation failed from oracle doc
If a previous DROP TABLE operation failed, indicates whether the table
is unusable (UNUSABLE) or valid (VALID)
As for the type INVALID in user_objects its mainly related to views/package/procedure however lalit kumar gave a good example where a table is invalid.

How to truncate any table using its synonym in oracle?

How to truncate any table using its synonym in oracle?
-- in Server_A
Create Table table_a ( col int);
-- in server_B
CREATE SYNONYM syn_table_a FOR table_a#dblink_server_a;
--insert into
INSERT INTO syn_table_a values (1);
--Truncate
How to truncate table using synonym only?.
A truncate statement cannot be used on a synonym.
Synonyms cannot be used in a drop table, drop view or truncate
table/cluster statements. If this is tried, it results in a ORA-00942:
table or view does not exist
For example,
SQL> CREATE TABLE t(col NUMBER);
Table created.
SQL>
SQL> CREATE SYNONYM t_syn FOR t;
Synonym created.
SQL>
SQL> TRUNCATE TABLE t_syn;
TRUNCATE TABLE t_syn
*
ERROR at line 1:
ORA-00942: table or view does not exist
SQL>
You could use dynamic SQL to do it, e.g.:
declare
d varchar2(1000);
begin
select 'TRUNCATE TABLE "' || table_owner || '"."' || table_name || '"'
into d
from all_synonyms
where synonym_name = 'MYSYNONYM';
execute immediate d;
end;
If the table is accessed via a database link, this will not work. In that case, you could create a procedure on the remote instance that does the truncate, then call that procedure across the database link, e.g.
begin
truncate_my_table#dblinkname;
end;
In Oracle, you can also get ORA-14410 while trying to drop/truncate a table using synonym.
The alert log:
ORA-00604: error occurred at recursive SQL level 1
ORA-14410: RPI LOCK TABLE issued to table referenced through synonym
Follow the above dynamic SQl to drop/truncate it.

Change table name with sysdate

I want to change a table name by appending SYSDATE to it. For example, I want to change table EXAMPLE_TABLE to EXAMPLE_TABLE_05_01_2015, but I want to get the date from SYSDATE.
I prepared the following but it is not working:
ALTER TABLE "MYDB"."EXAMPLE_TABLE" rename to (SELECT 'EXAMPLE_TABLE' || TO_CHAR(SYSDATE, '_dd_MM_yyyy') FROM DUAL);
How can I make it work?
Here is the error:
SQL Error: ORA-14047: ALTER TABLE|INDEX RENAME may not be combined with other operations
14047. 00000 - "ALTER TABLE|INDEX RENAME may not be combined with other operations"
*Cause: ALTER TABLE or ALTER INDEX statement attempted to combine
a RENAME operation with some other operation which is illegal
*Action: Ensure that RENAME operation is the sole operation specified in
ALTER TABLE or ALTER INDEX statement;
Use execute immediate.
begin
execute immediate
'alter table mydb.example_table rename to ' ||
'example_table_' || to_char(sysdate, 'dd_mm_yyyy');
end;
/
That said, I have the hunch that you'd be better off using partitioned tables.
In SQL*Plus, you could use the variable substitution.
Just another way :
SQL> CREATE TABLE t(ID NUMBER)
2 /
Table created.
SQL>
SQL> COLUMN new_tablename NEW_VALUE new_tablename
SQL> SELECT 't_' || to_char(sysdate, 'dd_mm_yyyy') AS new_tablename from dual
2 /
NEW_TABLENAM
------------
t_05_01_2015
SQL>
SQL> RENAME t TO &new_tablename
2 /
old 1: RENAME t TO &new_tablename
new 1: RENAME t TO t_05_01_2015
Table renamed.
SQL>
SQL> select * from t_05_01_2015;
no rows selected
SQL>
So, now the table T is renamed to T_05_01_2015.