Is it possible to use GRANT inside a trigger in Oracle 18c? - sql

I try to create a trigger which automatically grants select on all new tables for a specific schema, whenever a new table in this schema is created.
Background for this is IBM InfoSphere Information Server's Exception Database. This tool creates new tables for exceptions that are created in DataStage Jobs and I want a group of developers to be able to query these tables without giving them permission to the owner of the schema.
So my idea was to create a trigger like this:
create or replace trigger set_permissions
after create on schema
DECLARE
obj_name VARCHAR2(30) := DICTIONARY_OBJ_NAME;
BEGIN
IF DICTIONARY_OBJ_TYPE = 'TABLE'
THEN
GRANT SELECT ON c##ESDB_USER.obj_name TO c##DATASTAGE_USER;
END IF;
END set_permissions;
But I get error "PLS-00103" after compiling the trigger. It says, that "GRANT" is not expected and it expects one of the following instead:
( begin case declare exit for goto if loop mod null pragma raise return select update while with <an identifier> <a double-quoted delimited-identifier> <a bind variable> << continue close current delete fetch lock insert open rollback savepoint set sql execute commit forall merge pipe purge json_exists json_value json_query json_object json_array
Sounds to me that GRANT is not allowed inside a trigger. If that's so, is there another way to automatically grant users select-permission to new tables inside a specific schema?

Error you got says that you can't execute DDL (yes, grant is a DDL) like that - it has to be done as dynamic SQL, using execute immediate.
However, it won't help in this case because DDL implicitly commits, and you can't commit within a trigger.
Now you'll say that you can create a trigger as an autonomous transaction. Well, yes - you can, but it wouldn't help in this case because the table is yet to be created (i.e. it doesn't exist yet).
Here's a workaround; see if it helps. In a few words:
create an auxiliary procedure (to make it simpler) which will, actually, perform grant operation
let trigger submit a job which will call that procedure
Here's how: I'm connected as Scott and will be granting privileges to user Mike (as I don't have your users):
SQL> show user
USER is "SCOTT"
SQL>
SQL> -- Auxiliary procedure
SQL> create or replace procedure p_grant (par_str in varchar2) is
2 begin
3 execute immediate par_str;
4 end;
5 /
Procedure created.
SQL> -- Trigger
SQL> create or replace trigger set_permissions
2 after create on schema
3 declare
4 l_job number;
5 l_str varchar2(200);
6 obj_name varchar2(30) := dictionary_obj_name;
7 begin
8 if dictionary_obj_type = 'TABLE'
9 then
10 l_str := 'GRANT SELECT ON ' ||obj_name || ' TO mike';
11 dbms_job.submit
12 (l_job,
13 'begin p_grant(' || chr(39) || l_str || chr(39) ||'); end;',
14 sysdate
15 );
16 end if;
17 end set_permissions;
18 /
Trigger created.
SQL>
Testing:
SQL> create table test (id number);
Table created.
SQL> insert into test values (222);
1 row created.
SQL> commit;
Commit complete.
Connect as Mike and check what it sees:
SQL> connect mike/lion
Connected.
SQL> select * from scott.test;
ID
----------
222
SQL>

Related

How to run repeated statement with different parameter in SQL

Using SQL (or PL/SQL) I'd like to do something like:
GRANT SELECT, INSERT, TRIGGER, UPDATE, DELETE, REFERENCES, RULE ON {mytable} to {userid}
but do this for n number of tables. In SAS I could create a macro and pass in the table name (and/or the userid) as a parameter. Can the same be done in SQL using a procedure?
If you have list of tables stored in some other table (or, if it is about all tables in a schema), then you could create a procedure which would accept username as a parameter and grant those privileges on all those tables to that user.
For example (Oracle, which uses PL/SQL; as you didn't mention database you really use):
SQL> create or replace procedure p_grant (par_username in varchar2) is
2 begin
3 for cur_r in (select table_name
4 from user_tables
5 where table_name in ('EMP', 'DEPT', 'BONUS')
6 )
7 loop
8 dbms_output.put_Line('Grant on table: ' || cur_r.table_name);
9 execute immediate 'grant select, insert, update, delete on ' || cur_r.table_name || ' to ' || par_username;
10 end loop;
11 end;
12 /
Procedure created.
SQL> set serveroutput on
SQL> begin
2 p_grant('mike');
3 end;
4 /
Grant on table: BONUS
Grant on table: DEPT
Grant on table: EMP
PL/SQL procedure successfully completed.
SQL>

DYNAMIC SQL FOR TABLE NAME AT RUN TIME

create or replace procedure ankit
(table_name varchar2)
is
begin
dbms_output.put('select NAME FROM '||table_name);
end;
begin
ankit('ITEM');
end;
I am trying to execute the above command and it's compiling successfully but I am not able to see the output for the same.
If you are doing it in SQL*Plus, then SET SERVEROUTPUT ON is what you need to do first.
If you are using any GUI based client tool, then check the option to enable dbms_output
And use, DBMS_OUTPUT.PUT_LINE
Edit : See this test case.
SQL> set serveroutput on;
SQL>
SQL> create or replace procedure ankit
2 (table_name varchar2)
3 is
4 BEGIN
5 dbms_output.put_line('select NAME FROM '||table_name);
6 END;
7 /
Procedure created.
SQL>
SQL> begin
2 ankit('ITEM');
3 end;
4 /
select NAME FROM ITEM
PL/SQL procedure successfully completed.
SQL>
you have to enable output before running the code in SQL * PLUS
set serveroutput on ;

ADD CONSTRAINT IF EXISTS (Oracle 11g, Postgres 8)

I'm having difficult to make one script to delete the old constraints from some databases and after, create new ones with new references.
The problem is that the databases are not equal.
eg.: the swpmnh database has the fk_cmp_solicitaca_rh_contrat constraint but the swpmcs database has not. So if I execute the script I would have an error and it won't commit.
I know that Postgres 9.x has the possibility to do DROP CONSTRAINT IF EXISTS, but neither Postgres 8.x nor Oracle 11g have this function.
I'm working and studying SQL about only 3 months, I know that this is a simple thing, but it's being a problem for me.
This is the error you will be getting:
SQL> alter table my_tab drop constraint my_cons;
alter table my_tab drop constraint my_cons
*
ERROR at line 1:
ORA-02443: Cannot drop constraint - nonexistent constraint
You can trap the ORA-02443 error in PL/SQL and ignore it (using dynamic SQL):
1 declare
2 e exception;
3 pragma exception_init (e, -2443);
4 begin
5 execute immediate 'alter table my_tab drop constraint my_cons';
6 exception
7 when e then null;
8* end;
SQL> /
PL/SQL procedure successfully completed.
That is a bit verbose, so you could create a handy procedure:
create or replace procedure drop_constraint (p_table varchar2, p_constraint varchar2) is
e exception;
pragma exception_init (e, -2443);
begin
execute immediate 'alter table ' || p_table || ' drop constraint '||p_constraint;
exception
when e then null;
end;
Then use it whenever you need it:
execute drop_constraint ('my_tab', 'my_cons1');
execute drop_constraint ('my_tab', 'my_cons2');
execute drop_constraint ('another_tab', 'another_cons');

Creating a trigger generating ID column value before insert when new tables is created

When a table create in schema (MYSCHEMA), I need to create a trigger that generate a ID column (from sequence) before insert in each created table..
How can I realize this?
I know, how I can realize generation of ID column through trigger and sequence, something like this:
CREATE OR REPLACE TRIGGER TR1
BEFORE INSERT ON TB1
FOR EACH ROW
BEGIN
SELECT SQ1.nextval
INTO :new.primary_key_column
FROM dual;
END;
But I don't know, how I can use AFTER CREATE ON SCHEMA trigger to create trigger after CREATE TABLE in my schema with BEFORE INSERT...
I've written this code:
CREATE OR REPLACE TRIGGER /*APPROOT*/after_create_table_trigger
AFTER CREATE ON APPROOT.SCHEMA
DECLARE
TABLE_NAME VARCHAR2(100);
BEGIN
IF ORA_DICT_OBJ_TYPE = 'TABLE' THEN
SELECT ORA_DICT_OBJ_NAME INTO TABLE_NAME FROM DUAL;
EXECUTE IMMEDIATE
('CREATE OR REPLACE TRIGGER id_table_gen
BEFORE INSERT ON ' || TABLE_NAME ||
' FOR EACH ROW
BEGIN
SELECT APPROOT.AE_IDSEQ.NEXTVAL
INTO :new.ID
FROM dual;
END;');
END IF;
END;
/
Then I've created test table with one field - ID, but my trigger doesn't work...
I think the reason is wrong using of event attribute function ora_dict_obj_name.
Could somebody give me advice about this?
Thank you.
works ok if i put the schema name in the DDL.
SQL> connect sys/test as sysdba
Connected.
SQL> CREATE OR REPLACE TRIGGER after_create_table_trigger
2 AFTER CREATE ON TEST.SCHEMA
3 DECLARE
4 TABLE_NAME VARCHAR2(100);
5 BEGIN
6 IF ORA_DICT_OBJ_TYPE = 'TABLE' THEN
7 SELECT ORA_DICT_OBJ_NAME INTO TABLE_NAME FROM DUAL;
8 EXECUTE IMMEDIATE
9 ('CREATE OR REPLACE TRIGGER ID_TABLE_GEN
10 BEFORE INSERT ON TEST.' || TABLE_NAME ||
11 ' FOR EACH ROW
12 BEGIN
13 SELECT TEST.AE_IDSEQ.NEXTVAL
14 INTO :new.ID
15 FROM dual;
16 END;');
17 END IF;
18 END;
19 /
Trigger created.
SQL> connect test/test
Connected.
SQL> create table mytab(id number primary key, a varchar2(1));
Table created.
SQL> insert into mytab (a) values ('a');
1 row created.
SQL> select * From mytab;
ID A
---------- -
1 a
SQL> select * from v$version;
BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - 64bi
PL/SQL Release 10.2.0.4.0 - Production
CORE 10.2.0.4.0 Production
TNS for Linux: Version 10.2.0.4.0 - Production
NLSRTL Version 10.2.0.4.0 - Production
p.s. no need to do
SELECT ORA_DICT_OBJ_NAME INTO TABLE_NAME FROM DUAL;
just paste it into the command.
CREATE OR REPLACE TRIGGER ID_TABLE_GEN
BEFORE INSERT ON APPROOT.' || ORA_DICT_OBJ_NAME ||

oracle - mixing SQL code with PLSQL when no bindings in WHERE statement

I am trying to write a very simple sql script:
select * from table_X;
and I would like to see the results in oracle sqlplus, if there are any. These results are important for further analysis.
Also to mention, it depends, how many tables were created originally, so chances are that table_X may not be in the database at all. However I want to avoid getting an error, when parsing, that table_X doesn't exist, while running that script above.
So I was trying to wrap that SQL into some PLSQL dynamic code, like this:
Define table_X="MY_TAB"
DECLARE
stmt_ VARCHAR2(2000);
exist_ number := 0;
CURSOR table_exist IS
SELECT 1
FROM user_tables
WHERE table_name = '&table_X';
BEGIN
OPEN table_exist;
FETCH table_exist INTO exist_;
CLOSE table_exist;
IF exist_ = 1 THEN
stmt_ := 'SELECT * FROM &table_X';
EXECUTE IMMEDIATE stmt_;
ELSE
dbms_output.put_line('This functionality is not installed.');
END IF;
END;
/
Why I cannot see any result (records), if there is data in MY_TAB? Do I really need to bind some columns and use ex. dbms_output to be able to see some information?
Is there any simple way to query a table without getting 'ORA-00942: table or view does not exist'
if that table doesn't exist (ideally using SQL only)?
Thanks in advance
like this if you want it in sqlplus:
SQL> var c refcursor;
SQL> create or replace function get_table(p_tab in varchar2)
2 return sys_refcursor
3 is
4 v_r sys_refcursor;
5 NO_TABLE exception;
6 pragma exception_init(NO_TABLE, -942);
7 begin
8 open v_r for 'select * from ' || dbms_assert.simple_sql_name(p_tab);
9 return v_r;
10 exception
11 when NO_TABLE
12 then
13 open v_r for select 'NO TABLE ' || p_tab as oops from dual;
14 return v_r;
15 end;
16 /
Function created.
SQL> exec :c := get_table('DUAL2');
PL/SQL procedure successfully completed.
SQL> print c
OOPS
-----------------------------------------
NO TABLE DUAL2
SQL>
SQL> exec :c := get_table('DUAL');
PL/SQL procedure successfully completed.
SQL> print c
D
-
X
Instead of execute immediate 'query' you could use execute immediate 'query' bulk collect into and then loop over it and use dbms_output to print it.
http://docs.oracle.com/cd/B19306_01/appdev.102/b14261/executeimmediate_statement.htm