Is it possible to parameterize schema and table names used in queries within stored procedures using dynamic synonyms?
What we have tried is
setting parameters in a name/value pair table
reading those parameters at run time to determine schema (and table names), as the targets change based on the mode of the
application
dropping any existing synonym and recreating for the schema and tables
referencing the synonyms in the queries
The reason we are attempting this approach is because the queries to be executed are merge statements that are hundreds of lines long, not suited for dynamic SQL.
Maybe you can cover your problem when you use Invoker Rights for your procedure. Have a look at this example:
CREATE USER SCOTT_1 IDENTIFIED BY "tiger";
GRANT UNLIMITED TABLESPACE TO SCOTT_1;
CREATE USER SCOTT_2 IDENTIFIED BY "tiger";
GRANT UNLIMITED TABLESPACE TO SCOTT_2;
CREATE TABLE SCOTT_1.EMP (EMP_NAME VARCHAR2(30));
CREATE TABLE SCOTT_2.EMP (EMP_NAME VARCHAR2(30));
INSERT INTO SCOTT_1.EMP VALUES ('Schema 1');
INSERT INTO SCOTT_2.EMP VALUES ('Schema 2');
COMMIT;
CREATE SYNONYM EMP FOR SCOTT_1.EMP; -- Just needed to compile the procedure
CREATE OR REPLACE FUNCTION GetSchema(p_schema IN VARCHAR2) RETURN VARCHAR2
AUTHID CURRENT_USER
AS
res VARCHAR2(30);
BEGIN
EXECUTE IMMEDIATE 'ALTER SESSION SET CURRENT_SCHEMA = ' || p_schema;
SELECT EMP_NAME
INTO res
FROM EMP;
-- Just switch back to own schema to avoid unexpected behaviors
EXECUTE IMMEDIATE 'ALTER SESSION SET CURRENT_SCHEMA = '||USER;
RETURN res;
END;
/
SELECT GetSchema('SCOTT_1') FROM dual;
GETSCHEMA('SCOTT_1')
---------------------------------
Schema 1
1 row selected.
SELECT GetSchema('SCOTT_2') FROM dual;
GETSCHEMA('SCOTT_2')
---------------------------------
Schema 2
1 row selected.
Related
My stored procedure is like this:
create or replace procedure tpk.sp_Test_proc
IS
err_code NUMBER;
err_msg VARCHAR (500);
v_tbl_cnt NUMBER;
v_tbl_valid NUMBER;
Begin
SELECT COUNT(*) INTO v_tbl_cnt FROM USER_TABLES
WHERE TABLE_NAME IN (UPPER('Tbl1'),UPPER('tbl2'),UPPER('tbl3'));
IF(v_tbl_cnt =3) THEN
EXECUTE IMMEDIATE 'TRUNCATE TABLE Tbl1';
EXECUTE IMMEDIATE 'TRUNCATE TABLE Tbl2';
EXECUTE IMMEDIATE 'TRUNCATE TABLE Tbl3';
EXECUTE IMMEDIATE 'DROP TABLE Tbl1';
EXECUTE IMMEDIATE 'DROP TABLE Tbl2';
EXECUTE IMMEDIATE 'DROP TABLE Tbl3';
EXECUTE IMMEDIATE
'CREATE global temporary TABLE tbl1
( Id Integer... )'
Insert into tbl1
Select * from another_schema.Dw_table /* In this line it throws error Table does not exist */
end if;
end;
I tired same table with store procedure only to fetch the data its working there but when I used in Insert statement it throws an error
PL/SQL: ORA-00942 table or view does not exist.
I am totally confused - what's wrong here?
Select * from another_schema.Dw_table
You don't have a privilege to select from that table. Even if you think you do (granted via a role), it won't work in stored procedures - you have to grant it directly to user you're connected to.
Besides, there's no point in truncating tables first, and dropping them next. Just drop them.
Furthermore, there's rarely need to create tables dynamically (the way you do it), especially global temporary tables. Create them once, use them many times. No dropping. No (re)creating them in PL/SQL.
I have created a user (say DROP_PARTITION_USER) to drop partitions of a table. The table is owned by different user (say NORMAL_USER).
Currently, I have granted DROP ANY TABLE and ALTER ANY TABLE privileges to DROP_PARTITION_USER. When DROP_PARTITION_USER executes following statement, it gets executed successfully.
ALTER TABLE SCHEMANAME.TABLENAME DROP PARTITION <PARTITION_NAME> UPDATE GLOBAL INDEXES;
But, DROP ANY TABLE and ALTER ANY TABLE allows DROP_PARTITION_USER to drop and alter any table under any schema [https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_9013.htm ].
Is there any way in Oracle to restrict drop and alter table under specific schema?
The common way to solve this is to create a procedure owned by NORMAL_USER to drop one of the partitions of one of it's tables.
Then you GRANT EXECUTE on this procedure to DROP_PARTITION_USER.
You'll need no extra privileges.
CREATE OR REPLACE PROCEDURE my_drop_partition (p_table_name VARCHAR2, p_partition_name VARCHAR2)
IS
BEGIN
EXECUTE IMMEDIATE 'ALTER TABLE '||p_table_name||' DROP PARTITION '||p_partition_name;
END my_drop_partition;
/
GRANT EXECUTE ON my_drop_partition TO drop_partition_user;
You can use a DDL trigger to capture such attempts and take whatever action you like. For example
SQL> CREATE OR REPLACE TRIGGER STOP_THAT_STUFF
2 before create or alter or drop on database
3 begin
4 if ora_dict_obj_owner in ('SCOTT') and
5 ora_sysevent in ('DROP','ALTER') and
6 ora_dict_obj_name = 'MY_TABLE'
7 then
8 raise_application_error(-20000,'What the hell are you thinking!!!!');
9 end if;
10 end;
11 /
Trigger created.
SQL>
SQL> conn scott/tiger
Connected.
SQL> create table scott.my_table(x int );
Table created.
SQL> create table scott.my_other_table(x int);
Table created.
SQL> drop table my_other_table;
Table dropped.
SQL> drop table my_table;
drop table my_table
*
ERROR at line 1:
ORA-04088: error during execution of trigger 'SYS.STOP_THAT_STUFF'
ORA-00604: error occurred at recursive SQL level 1
ORA-20000: What the hell are you thinking!!!!
ORA-06512: at line 6
SQL> desc my_table
Name Null? Type
----------------------------------------------------------------------- -------- ----------------
X NUMBER(38)
I am performing an archival process on a huge database and it involves deleting the production active table and renaming another table to be the new production table. When dropping the production active table, the triggers also get deleted. So I am just taking a backup of the triggers defined on my table using
select * from all_triggers where table_name=mytablename;
My question is, can I directly copy these triggers in to the all_triggers table after I rename my other table to be the new production active table? Will the triggers still work?
Same question for defining indexes and constraints too.
Copying the triggers from one table to another can be done by copying DDL, and not updating all_triggers table. This can be done by using DBMS_METADATA.
The closest practical example I found here: Copy Triggers when you Copy a Table
The following script can be amended as per your need:
declare
p_src_tbl varchar2(30):= 'PERSONS'; --your table name
p_trg_tbl varchar2(30):= 'PSN2'; --your trigger name
l_ddl varchar2(32000);
begin
execute immediate 'create table '||p_trg_tbl||' as select * from '||p_src_tbl||' where 1=2';
for trg in (select trigger_name from user_triggers where table_name = p_src_tbl) loop
l_ddl:= cast(replace(replace(dbms_metadata.get_ddl( 'TRIGGER', trg.trigger_name),p_src_tbl,p_trg_tbl),trg.trigger_name,substr(p_trg_tbl||trg.trigger_name, 1, 30)) as varchar2);
execute immediate substr(l_ddl, 1, instr(l_ddl,'ALTER TRIGGER')-1);
end loop;
end;
/
No, you cannot directly manipulate data dictionary tables. You can't insert data directly into all_triggers (the same goes for any data dictionary table). I guess you probably could given enough hacking. It just wouldn't work and would render your database unsupported.
The correct way to go is to script out your triggers and reapply them later. If you want to do this programmatically, you can use the dbms_metadata package. If you want to get the DDL for each of the triggers on a table, you can do something like
select dbms_metadata.get_ddl( 'TRIGGER', t.trigger_name, t.owner )
from all_triggers t
where table_owner = <<owner of table>>
and table_name = <<name of table>>
To replicate your scenario i have prepared below snippet. Let me know if this helps.
--Simple example to copy Trigger from one table to another
CREATE TABLE EMP_V1 AS
SELECT * FROM EMP;
--Creating Trigger on Old Table for Example purpose
CREATE OR REPLACE TRIGGER EMP_OLD_TRIGGER
AFTER INSERT OR UPDATE ON EMP FOR EACH ROW
DECLARE
LV_ERR_CODE_OUT NUMBER;
LV_ERR_MSG_OUT VARCHAR2(2000);
BEGIN
dbms_output.put_line('Your code for data Manipulations');
--Like Insert update or DELETE activities
END;
-- To replace this trigger for emp_v2 table
set serveroutput on;
DECLARE
lv_var LONG;
BEGIN
FOR i IN (
SELECT OWNER,TRIGGER_NAME,DBMS_METADATA.GET_DDL('TRIGGER','EMP_OLD_TRIGGER') ddl_script FROM all_triggers
WHERE OWNER = 'AVROY') LOOP
NULL;
lv_var:=REPLACE(i.ddl_script,'ON EMP FOR EACH ROW','ON EMP_V1 FOR EACH ROW');
dbms_output.put_line(substr(lv_var,1,INSTR(lv_var,'ALTER TRIGGER',1)-1));
EXECUTE IMMEDIATE 'DROP TRIGGER '||I.TRIGGER_NAME;
EXECUTE IMMEDIATE lv_var;
END LOOP;
END;
--Check if DDL manipulation has been done for not
SELECT OWNER,TRIGGER_NAME,DBMS_METADATA.GET_DDL('TRIGGER','EMP_OLD_TRIGGER') ddl_script FROM all_triggers
WHERE OWNER = 'AVROY';
---------------------------------OUTPUT----------------------------------------
"
CREATE OR REPLACE TRIGGER "AVROY"."EMP_OLD_TRIGGER"
AFTER INSERT OR UPDATE ON EMP_V1 FOR EACH ROW
DECLARE
LV_ERR_CODE_OUT NUMBER;
LV_ERR_MSG_OUT VARCHAR2(2000);
BEGIN
dbms_output.put_line('Your code for data Manipulations');
--Like Insert update or DELETE activities
END;
"
-----------------------------OUTPUT----------------------------------------------
I have a problem trying to use an Execute Immediate statement containing a CREATE TABLE statement and a user defined Table Type. I get error ORA-22905 on Oracle 11g.
Is there any workaround to solve this issue?
CREATE TYPE MY_TABLE_TYPE AS TABLE OF VARCHAR2(30);
/
DECLARE
MT MY_TABLE_TYPE;
BEGIN
SELECT * BULK COLLECT INTO MT FROM DUAL;
-- Two steps
EXECUTE IMMEDIATE 'CREATE TABLE MY_TABLE1 (A VARCHAR2(30))';
EXECUTE IMMEDIATE 'INSERT INTO MY_TABLE1 SELECT * FROM TABLE(:T)' USING MT; -- OK
-- One step
EXECUTE IMMEDIATE 'CREATE TABLE MY_TABLE2 AS SELECT * FROM TABLE(:T)' USING MT; -- ERROR ORA-22905
END;
The real code for the SELECT * FROM TABLE(:T) is dynamic (main table name is temporary) and slow. That's why I try to avoid creating the table in two steps (as done with MY_TABLE1). Also with two steps I can't use SELECT * but I have to specify all the columns (variable amount and over 100 columns).
There is likely a way to completely avoid this issue. Skip the bulk collect and use a simple CREATE TABLE MY_TABLE AS SELECT * FROM DUAL; That may be an over-simplification of the real logic to gather the data. But there is almost always a way to bypass a bulk collect and store the data directly in an object with just SQL.
If a PL/SQL solution is truly needed, the error ORA-22905: cannot access rows from a non-nested table item can be avoided by creating an object type and creating the table based on that type. This may not solve the performance issue, but at least this avoids the need to re-specify all the columns in the table DDL.
CREATE TYPE MY_TABLE_OBJECT IS OBJECT
(
A VARCHAR2(30)
);
CREATE TYPE MY_TABLE_TYPE2 AS TABLE OF VARCHAR2(30);
DECLARE
MT MY_TABLE_TYPE2;
BEGIN
SELECT * BULK COLLECT INTO MT FROM DUAL;
EXECUTE IMMEDIATE 'CREATE TABLE MY_TABLE2 OF MY_TABLE_OBJECT';
EXECUTE IMMEDIATE 'INSERT INTO MY_TABLE2 SELECT * FROM TABLE(:T)' USING MT;
END;
/
I've a stored procedure created by EMP_DBA and part of the query will check whether the existing tables exist or not, if exist then drop table. This query works fine if I connect as EMP_DBA, now I want to run this stored procedure with other account let say USER1 and I've grant all the necessary rights to USER1. How to rewrite below statement in order count return 1 if the table MARKET_DATA exist in schema EMP_DBA ?
BEGIN
SELECT COUNT(*) INTO c
FROM all_tables
WHERE
table_name = 'MARKET_DATA' AND OWNER = 'EMP_DBA';
IF C = 1 THEN
EXECUTE IMMEDIATE 'DROP TABLE MARKET_DATA';
--exception when others then null;
END IF;
"I've grant all the necessary rights to USER1"
This is a slightly worrying statement. What do you mean by all the necessary rights ? The only appropriate right is execute on a stored procedure owned by EMP_DBA. That procedure should encapsulate everything. EMP_DBA doesn't (or shouldn't) want USER1to drop their tables independently. Besides it isn't possible to grant DDL statements on specific objects, or even specific schemas. And DROP ANY is a powerful privilege to hand out.
The best way to write the stored procedure is to use definer's rights (which is the default). This ensures that the code is executed with the privileges of the stored procedure's owner, not those of the executing user. That your code doesn't work - presumably because you haven't specified the table owner - suggests you haven't got your security model quite right.
In my version I've used ALL_TABLES just like you did, to show the difference between CURRENT_USER and SESSION_USER, but actually USER_TABLES would work just as well.
create or replace procedure recreate_tab
(p_tab_name in all_tables.table_name%type)
authid definer
is
n pls_integer;
begin
select count(*)
into n
from all_tables
where owner = (sys_context('userenv','current_user'))
and table_name = p_tab_name;
if n = 1
then
-- no need to specify schema because it's the procedure owner
execute immediate 'drop table '|| p_tab_name;
end if;
execute immediate 'create table '||p_tab_name
||' ( id number, descr varchar2(30))';
-- grant rights on the new table to the user executing the procedure
execute immediate 'grant select on '||p_tab_name||' to '
|| sys_context('userenv','session_user');
end recreate_tab;
/
grant execute on recreate_tab to user1
/
So. Nothing up my sleeve ...
SQL> conn user1/user1
Connected.
SQL> select count(*) from t42
2 /
select count(*) from t42
*
ERROR at line 1:
ORA-00942: table or view does not exist
SQL> select count(*) from emp_dba.t42
2 /
COUNT(*)
----------
56179
SQL> exec emp_dba.recreate_tab('T42')
PL/SQL procedure successfully completed.
SQL> select count(*) from emp_dba.t42
2 /
COUNT(*)
----------
0
SQL>
Your select is correct. You should rewrite the EXECUTE IMMEDIATE to do
DROP TABLE EMP_DBA.MARKET_DATA