How to run repeated statement with different parameter in SQL - 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>

Related

Dynamic SQL to loop through same table in multiple schemas

I'm trying to create a SQL statement which contains basic information from DBA_USERS for each schema and also select from a specific table in each schema as part of the same statement.
I have part of a statement cobbled together from other answers to similar questions on StackExchange:
DECLARE
v_sql varchar2(4000);
cursor c1 is
select o.owner
, o.object_name
, u.created
, TO_CHAR(round(sum(ds.bytes)/1024/1024/1024,'0000'))||' GB'
from dba_users u
, dba_objects o
, dba_segments ds
WHERE u.account_status = 'OPEN'
and u.DEFAULT_TABLESPACE not in ('SYSAUX','SYSTEM')
and u.username=o.owner
and o.object_name='MASTER'
and o.object_type='TABLE'
and ds.owner =o.owner;
BEGIN
for REC in c1 loop
v_sql := 'select VERSION from '||REC.owner||'.'||REC.object_name;
EXECUTE IMMEDIATE v_sql;
end loop;
END;
/
This statement runs but won't show me any results as I believe it should be using a bulk collector and printing the output using DBMS_OUTPUT.PUT_LINE
The output should be something like this:
USERNAME CREATED SIZE VERSION
SchemaA 2021-01-01 20GB 1.1
SchemaB 2021-01-02 22GB 1.2.2
SchemaC 2021-01-03 18GB 1.5.8
Firstly, how should I rewrite the statement above to output to the session, and secondly, is it possible to return the results I'm expecting?
One option to get a result as you want would be to use pipelined functions. They deliver results in the form of a table.
By the way, your query is not completely right, as you need to join more elements. That is why is always best to use ANSI syntax. However, I would keep your syntax to make easier for you the explanation.
Let me show you an example. I don't have this field version, so I am using the counter of rows:
First we need to create the two types, one as an object and the other as table of. The first is the row, the second is the table construction.
SQL> CREATE OR REPLACE TYPE t_tf_row AS OBJECT ( username varchar2(40), created_date date, size_mb varchar2(10), counter number );
/
Type created.
SQL> CREATE TYPE t_tf_tab IS TABLE OF t_tf_row;
/
Type created.
Now, we create a pipelined function very similar to yours.
SQL> CREATE OR REPLACE FUNCTION get_schema_details RETURN t_tf_tab PIPELINED
AS
v_sql varchar2(4000);
v_counter pls_integer;
BEGIN
for h in
(
select o.owner
, o.object_name
, u.created
, round(ds.bytes/1024/1024/1024) as table_size
from dba_users u
, dba_objects o
, dba_segments ds
WHERE u.account_status = 'OPEN'
and u.DEFAULT_TABLESPACE not in ('SYSAUX','SYSTEM')
and u.username=o.owner
and u.username=ds.owner
and o.object_name = ds.segment_name
and o.object_type = ds.segment_type
and o.object_name='ODSPOSTING'
and o.object_type='TABLE'
)
loop
v_sql := 'select count(*) from '||h.owner||'.'||h.object_name;
EXECUTE IMMEDIATE v_sql into v_counter;
PIPE ROW(t_tf_row(h.owner,h.created,h.table_size,v_counter));
end loop;
END;
/
Function created.
SQL> select * from table(get_schema_details());
USERNAME CREATED_D SIZE_MB COUNTER
---------------------------------------- --------- ---------- ----------
ODSVIEWS 24-MAR-20 14 71853408
ALFAODS 20-DEC-19 14 71853408
You can make the function as dynamic as you want, for example introducing input parameters instead of hardcoding the values.
UPDATE
Your test case scenario
SQL> CREATE USER SCHEMA1 IDENTIFIED BY Oracle_1234
DEFAULT TABLESPACE USERS
TEMPORARY TABLESPACE TEMP_GROUP; 2 3
User created.
SQL> GRANT CREATE TABLE TO SCHEMA1;
Grant succeeded.
SQL> GRANT UNLIMITED TABLESPACE TO SCHEMA1;
Grant succeeded.
SQL> CREATE USER SCHEMA2 IDENTIFIED BY Oracle_1234
DEFAULT TABLESPACE USERS
TEMPORARY TABLESPACE TEMP_GROUP;
User created.
SQL> GRANT CREATE TABLE TO SCHEMA2;
Grant succeeded.
SQL> GRANT UNLIMITED TABLESPACE TO SCHEMA2;
Grant succeeded.
SQL> CREATE TABLE SCHEMA1.MASTER(VERSION VARCHAR2(6 BYTE));
Table created.
SQL> CREATE TABLE SCHEMA2.MASTER(VERSION VARCHAR2(6 BYTE));
Table created.
SQL> INSERT INTO "SCHEMA1"."MASTER" (VERSION) VALUES ('1.1.0');
1 row created.
SQL> COMMIT;
Commit complete.
SQL> INSERT INTO "SCHEMA2"."MASTER" (VERSION) VALUES ('2.2.0');
1 row created.
SQL> COMMIT;
Commit complete.
Now we create the types and function.
SQL> CREATE OR REPLACE TYPE t_tf_row AS OBJECT ( username varchar2(40), created_date DATE, size_mb varchar2(10), counter NUMBER );
2 /
Type created.
SQL> CREATE OR REPLACE TYPE t_tf_tab IS TABLE OF t_tf_row;
2 /
Type created.
SQL> CREATE OR REPLACE FUNCTION get_master_version_details RETURN t_tf_tab PIPELINED
AS
v_sql varchar2(4000);
2 3 4 v_counter pls_integer;
BEGIN
5 6 FOR h IN
(
SELECT o.owner
7 8 9 , o.object_name
, u.created
, round(ds.bytes/1024/1024/1024) AS table_size
10 11 12 FROM dba_users u
, dba_objects o
, dba_segments ds
13 14 15 WHERE u.account_status = 'OPEN'
AND u.DEFAULT_TABLESPACE NOT IN ('SYSAUX','SYSTEM')
AND u.username=o.owner
16 17 18 AND u.username=ds.owner
AND o.object_name = ds.segment_name
AND o.object_type = ds.segment_type
19 20 21 AND o.object_name='MASTER'
AND o.object_type='TABLE'
)
22 23 24 loop
v_sql := 'select count(*) from '||h.owner||'.'||h.object_name;
EXECUTE IMMEDIATE v_sql INTO v_counter;
25 26 27 PIPE ROW(t_tf_row(h.owner,h.created,h.table_size,v_counter));
END loop;
END;
28 29 30 /
Function created.
SQL> SELECT COUNT(*) FROM all_objects WHERE object_name='MASTER' AND object_type='TABLE';
COUNT(*)
----------
2
SQL> SELECT * FROM TABLE(get_master_version_details());
USERNAME CREATED_D SIZE_MB COUNTER
---------------------------------------- --------- ---------- ----------
SCHEMA1 28-SEP-21 0 1
SCHEMA2 28-SEP-21 0 1
Why in your case is not working ? You have to install the function and types within a user/schema with the right privileges to run the operations you are doing.
In my example above, as a test, I did install the function and the type on my sys schema ( something you should not do it ). So, let's drop the function and types, and create an additional user for that, we will call it schema3
SQL> DROP TYPE t_tf_tab;
Type dropped.
SQL> DROP TYPE t_tf_row;
Type dropped.
SQL> DROP FUNCTION get_master_version_details;
Function dropped.
SQL> create user schema3 identified by Oracle_1234 default tablespace users temporary tablespace temp_group ;
User created.
SQL> grant select any table, create procedure, create table, select any dictionary to schema3 ;
Grant succeeded.
SQL> CREATE OR REPLACE TYPE schema3.t_tf_row AS OBJECT ( username varchar2(40), created_date DATE, size_mb varchar2(10), counter NUMBER );
2 /
Type created.
SQL> CREATE OR REPLACE TYPE schema3.t_tf_tab IS TABLE OF t_tf_row;
2 /
Type created.
SQL> CREATE OR REPLACE FUNCTION schema3.get_master_version_details RETURN t_tf_tab PIPELINED
AS
v_sql varchar2(4000);
v_counter pls_integer;
BEGIN
2 3 4 5 6 FOR h IN
7 (
SELECT o.owner
, o.object_name
8 9 10 , u.created
, round(ds.bytes/1024/1024/1024) AS table_size
FROM dba_users u
, dba_objects o
, dba_segments ds
WHERE u.account_status = 'OPEN'
11 12 13 14 15 16 AND u.DEFAULT_TABLESPACE NOT IN ('SYSAUX','SYSTEM')
AND u.username=o.owner
AND u.username=ds.owner
17 18 19 AND o.object_name = ds.segment_name
AND o.object_type = ds.segment_type
AND o.object_name='MASTER'
20 21 22 AND o.object_type='TABLE'
23 )
loop
24 25 v_sql := 'select count(*) from '||h.owner||'.'||h.object_name;
EXECUTE IMMEDIATE v_sql INTO v_counter;
PIPE ROW(t_tf_row(h.owner,h.created,h.table_size,v_counter));
26 27 28 END loop;
END;
/ 29 30
Function created.
SQL> SELECT * FROM TABLE(schema3.get_master_version_details());
USERNAME CREATED_D SIZE_MB COUNTER
---------------------------------------- --------- ---------- ----------
SCHEMA1 28-SEP-21 0 1
SCHEMA2 28-SEP-21 0 1
Be aware of the privileges I granted to schema3 in order for the pipelined function to work.
The reason you are not 'seeing' any result is that PL/SQL operates entirely within the server. It has no connection to the client, and no means of accessing the client's output display. You need to use the DBMS_OUTPUT.PUT_LINE procedure (look it up in the docs). That procedure writes to a buffer that is then returned to the client when the procedure completes. It is then up to the client to deal with that buffer. If using sqlplus, you configure it to display that output by 'set serverout on' as a session setting before invoking any procedures.
Also, I'd rewrite your procedure to eliminate the explicit cursor and use a CURSOR FOR loop: (I'd also convert to user ANSI standard JOIN syntax, but I'm not going to spend time here analyzing the query to figure out exactly how to convert that_). Also, I don't see how the procedure runs at all, given that your SELECT inside the loop needs an INTO clause to have a place to put the result.
DECLARE
v_sql varchar2(4000);
v_version varchar2(80);
BEGIN
for REC in (select o.owner
,o.object_name
,u.created
,TO_CHAR(round(sum(ds.bytes)/1024/1024/1024,'0000'))||' GB'
from dba_users u
,dba_objects o
,dba_segments ds
WHERE u.account_status = 'OPEN'
and u.DEFAULT_TABLESPACE not in ('SYSAUX','SYSTEM')
and u.username=o.owner
and o.object_name='MASTER'
and o.object_type='TABLE'
and ds.owner =o.owner;)
loop
v_sql := 'select VERSION from '||REC.owner||'.'||REC.object_name into v_version;
EXECUTE IMMEDIATE v_sql;
dbms_output.put_line('Version is '||v_version);
end loop;
END;
/
The problem is
execute immediate v_sql;
which has no output. It needs an into clause, and something to display it:
declare
demo_text varchar2(50);
begin
execute immediate 'select 2 + 2 as demo from dual'
into demo_text;
dbms_output.put_line(demo_text);
end;
4
By the way, I recommend deciding between end; and END; (bearing in mind this isn't COBOL).

How to alter more that one user using ALTER USER in ORACLE

I want to change DEFAULT TABLESPACE for all users except for SYS.
select username from DBA_USERS where username!='SYS';
gets me all users I need.
But I do not know how to properly integrate this subquery to alter user.
I tried
ALTER USER (select username from DBA_USERS where username!='SYS') DEFAULT TABLESPACE DATA;
One option is to write a PL/SQL script which will loop through all users you want and - using dynamic SQL - alter each of these users. Something like this:
What do I have?
SQL> show user
USER is "SYS"
SQL>
SQL> select tablespace_name from dba_tablespaces;
TABLESPACE_NAME
------------------------------
SYSTEM
SYSAUX
UNDOTBS1
TEMP
USERS
I won't change default tablespace for all users, just for scott and mike. You'd omit line #6.
SQL> set serveroutput on
SQL> declare
2 l_str varchar2(200);
3 begin
4 for cur_r in (select username from all_users
5 where username <> 'SYS'
6 and username in ('SCOTT', 'MIKE')
7 )
8 loop
9 l_Str := 'alter user ' || cur_r.username ||
10 ' default tablespace users';
11 dbms_output.put_line('User: ' || cur_r.username);
12 dbms_output.put_line(l_str);
13 execute immediate l_str;
14 end loop;
15 end;
16 /
User: MIKE
alter user MIKE default tablespace users
User: SCOTT
alter user SCOTT default tablespace users
PL/SQL procedure successfully completed.
SQL>
Another (maybe simpler?) option is to let SQL create bunch of statements for you:
SQL> select 'alter user ' || username || ' default tablespace users;' stmt
2 from all_users
3 where username <> 'SYS'
4 and username in ('SCOTT', 'MIKE');
STMT
-------------------------------------------------------------------
alter user MIKE default tablespace users;
alter user SCOTT default tablespace users;
SQL>
Now you'd copy these ALTER USER ... lines and paste them and - that's it. Or, you could spool result of that SELECT into a file (e.g. alter_file.sql) and then run the file as
SQL> #alter_file
So yes, there are other options, but such a PL/SQL does everything for you at once, you don't have to do anything afterwards.

Code to execute select on list of multiple Oracle databases

Need your or guidance on how I can execute a select on multiple databases provided in the list. the goal behind this code is to query multiple remote databases and insert the output in current database.
Need to db_link to be fetched from a list or table
insert into xxxx.DB_tracker value(SELECT d.name FROM v$database#**opXXX_du**);
Dynamic SQL.
Suppose that database links are stored in the link table:
SQL> select * From links;
LINK
---------
dbl_ora10
dbl_ora11
dbl_orcl
You'd then use a loop, create an insert statement and execute it. As I don't have those database links, I'm just displaying statements to the screen. You'd uncomment the execute immediate line.
SQL> set serveroutput on
SQL> declare
2 l_str varchar2(200);
3 begin
4 for cur_r in (select link from links) loop
5 l_str := 'insert into db_tracker ' ||
6 'select name from v$database#' || cur_r.link;
7 dbms_output.put_line(l_str);
8
9 -- execute immediate l_str;
10 end loop;
11 end;
12 /
insert into db_tracker select name from v$database#dbl_ora10
insert into db_tracker select name from v$database#dbl_ora11
insert into db_tracker select name from v$database#dbl_orcl
PL/SQL procedure successfully completed.
SQL>
If you want to actually select name and display it on the screen, then you need the into clause. Something like this:
SQL> set serveroutput on
SQL>
SQL> declare
2 l_name varchar2(30);
3 begin
4 for cur_r in (select link from links) loop
5 execute immediate 'select name from v$database#' || cur_r.link
6 into l_name;
7 dbms_output.put_line(l_name);
8 end loop;
9 end;
10 /
XE
PL/SQL procedure successfully completed.
SQL>

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

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>

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 ||