PL/SQL While/For Loop - oracle

I'm a DBA, just trying to write a piece of code to capture user privileges in Oracle and write to a table. The below code works ok for one user, but if the ELSE part has multiple users I get the error: "ORA-01422: exact fetch returns more than requested number of rows". Makes sense, I realise I need a for/while loop to handle multiple rows, can someone help me with that?
'''
declare
altersystem varchar2(550);
altersystemconcat varchar2(550);
begin
select grantee
into altersystem
from dba_sys_privs
where privilege = 'ALTER SYSTEM'
and grantee not in ('SYS', 'SYSTEM');
if altersystem = 'No rows selected'
then
insert into catch
values
('900'
,'No custom users with the Alter System privilege.');
else
select concat('The following user/role has the Alter System privilege, revoke if not required: '
,altersystem)
into altersystemconcat
from dual;
insert into catch
values
('100'
,altersystemconcat);
end if;
end;
/
'''

An select ... INTO ... requires exactly one row to be returned, otherwise you get an exception. Usually you have to loop over a cursor, but you can do it even in a single command like this:
BEGIN
INSERT INTO catch
SELECT 900, 'The following user/role has the Alter System privilege, revoke if not required: '||grantee
FROM dba_sys_privs
WHERE PRIVILEGE = 'ALTER SYSTEM'
AND grantee NOT IN ('SYS', 'SYSTEM');
IF SQL%ROWCOUNT = 0 THEN
INSERT INTO catch VALUES (100, 'No custom users with the Alter System privilege.');
END IF;
END;

Umm ... no, ELSE's SELECT doesn't return too-many-rows. It selects from DUAL which has only 1 row so ...
I suspect it is the first statement, select grantee into altersystem. If there's more than one user that satisfies those conditions, you can't put them all into the variable which is declared as varchar2. Therefore, yes - a loop might help, it is simple enough.
But, why? Why don't you simply insert all those offenders using single insert? Something like this:
INSERT INTO catch
SELECT '000', grantee
FROM dba_sys_privs
WHERE privilege = 'ALTER SYSTEM'
AND grantee NOT IN ('SYS', 'SYSTEM');

This can be done in a single SQL statement - no need for PL/SQL (unless you need it as a stored procedure):
INSERT INTO catch (col1, col2) -- change col1 and col2 to the correct column_names in catch that you're inserting into
WITH no_users AS (SELECT 100 no_user_present_id, 'No custom users with the Alter System privilege.' no_user_text FROM dual),
users_there AS (SELECT 900 user_present_id, 'The following user/role has the Alter System privilege, revoke if not required: '||grantee user_there_text
FROM dba_sys_privs
WHERE PRIVILEGE = 'ALTER SYSTEM'
AND grantee NOT IN ('SYS', 'SYSTEM'))
SELECT COALESCE(ut.user_present_id, nu.no_user_present_id) ID,
COALESCE(ut.user_there_text, nu.no_user_text) text
FROM no_users nu
LEFT OUTER JOIN users_there ut ON 1 = 1;
This works by using an outer join so that the row for when there's no user is always returned. If there are no users present, the row that is returned is the default row. If there are users that have the specified grant, you will get the rows for those users returned.

Using the listagg function you can concatenate the resulting string.
Commented out the insert statements as I don't have the catch table. Printing to output instead.
Use an exception handler in case no rows found.
declare
altersystem varchar2(550);
altersystemconcat varchar2(550);
begin
select listagg(grantee, ', ') within group(order by grantee) "Grantee"
into altersystem
from dba_sys_privs
where privilege = 'ALTER SYSTEM'
and grantee not in ('SYS', 'SYSTEM');
dbms_output.put_line('The following user/role has the Alter System privilege, revoke if not required: ' || altersystem);
/*
insert into catch
values
('100'
,'The following user/role has the Alter System privilege, revoke if not required: ' || altersystem);
*/
exception
when no_data_found then
dbms_output.put_line('No custom users with the Alter System privilege.');
/*
insert into catch
values
('900'
,'No custom users with the Alter System privilege.');
*/
end;
/

Related

How to delete sequences and procedures during logoff trigger?

Could you please help me in a unique situation I am in. I am receiving "ORA-30511: invalid DDL operation in system triggers" when dropping sequences and procedures during logoff trigger.
I need to delete tables, sequences and procedures of users before logoff event happens. I am writing the table details in DB_OBJECTS table upon create using a separate trigger. Below is my logoff trigger - could you please help me where I am doing wrong. Dropping tables is working fine in the below code. Only Dropping sequences and procedures is giving me "ORA-30511: invalid DDL operation in system triggers" error.
CREATE OR REPLACE TRIGGER DELETE_BEFORE_LOGOFF
BEFORE LOGOFF ON DATABASE
DECLARE
USER_ID NUMBER := SYS_CONTEXT('USERENV', 'SESSIONID');
BEGIN
FOR O IN (SELECT USER, OBJECT_NAME, OBJECT_TYPE
FROM DB_OBJECTS WHERE SID = USER_ID
AND USERNAME = USER AND SYSDATE > CREATED_DTTM) LOOP
IF O.OBJECT_TYPE = 'TABLE' THEN
EXECUTE IMMEDIATE 'DROP TABLE ' || O.USER || '.' || O.OBJECT_NAME || ' CASCADE CONSTRAINTS';
ELSIF O.OBJECT_TYPE = 'SEQUENCE' THEN
EXECUTE IMMEDIATE 'DROP SEQUENCE ' || O.USER || '.' || O.OBJECT_NAME;
ELSIF O.OBJECT_TYPE = 'PROCEDURE' THEN
EXECUTE IMMEDIATE 'DROP PROCEDURE ' || O.USER || '.' || O.OBJECT_NAME;
END IF;
END LOOP;
EXCEPTION WHEN NO_DATA_FOUND THEN NULL;
END;
/
That's a simple one.
Error code: ORA-30511
Description: invalid DDL operation in system triggers
Cause: An attempt was made to perform an invalid DDL operation in a system trigger. Most DDL operations currently are not supported in system triggers. The only currently supported DDL operations are table operations and ALTER/COMPILE operations.
Action: Remove invalid DDL operations in system triggers.
That's why only
Dropping tables is working fine
succeeded.
Therefore, you can't do that using trigger.
You asked (in a comment) how to drop these objects, then. Manually, as far as I can tell. Though, that's quite unusual - what if someone accidentally logs off? You'd drop everything they created. If you use that schema for educational purposes (for example, every student gets their own schema), then you could create a "clean-up" script you'd run once class is over. Something like this:
SET SERVEROUTPUT ON;
DECLARE
l_user VARCHAR2 (30) := 'SCOTT';
l_str VARCHAR2 (200);
BEGIN
IF USER = l_user
THEN
FOR cur_r IN (SELECT object_name, object_type
FROM user_objects
WHERE object_name NOT IN ('EMP',
'DEPT',
'BONUS',
'SALGRADE'))
LOOP
BEGIN
l_str :=
'drop '
|| cur_r.object_type
|| ' "'
|| cur_r.object_name
|| '"';
DBMS_OUTPUT.put_line (l_str);
EXECUTE IMMEDIATE l_str;
EXCEPTION
WHEN OTHERS
THEN
NULL;
END;
END LOOP;
END IF;
END;
/
PURGE RECYCLEBIN;
It is far from being perfect; I use it to clean up my Scott schema I use to answer questions on various sites so - once it becomes a mess, I run that PL/SQL code several times (because of possible foreign key constraint).
Other option is to keep a create user script(s) (along with all grant statements) and - once class is over - drop existing user and simply recreate it.
Or, if that user contains some pre-built tables, keep export file (I mean, result of data pump export) and import it after the user is dropped.
There are various options - I don't know whether I managed to guess correctly, but now you have something to think about.

GRANT SELECT doesn't work in altered session

I have the following situation. I need to write a procedure to give one schema access to object of the other one. The thing is that this procedure is being executed by administrative account via flyway.
I tried numerous options, but face the following:
Error starting at line : 3 in command - (my begin...end procedure)
Error report -
ORA-00942: table or view does not exist
ORA-06512: at line 3
00942. 00000 - "table or view does not exist"
My code:
ALTER SESSION SET CURRENT_SCHEMA = AppUser;
BEGIN
FOR R IN (SELECT owner, table_name FROM dba_tables WHERE owner='AppUser') LOOP
EXECUTE IMMEDIATE 'GRANT SELECT ON '||R.owner||'.'||R.table_name||' TO QAUser';
END LOOP;
END;
Neither it works w/o altering schema.
You owner is AppUser in mixed case. As such, you will need to quote it when using it in statements, otherwise Oracle will convert it to uppercase.
So you could try this:
ALTER SESSION SET CURRENT_SCHEMA = AppUser;
BEGIN
FOR R IN (SELECT owner, table_name FROM dba_tables WHERE owner='AppUser') LOOP
EXECUTE IMMEDIATE 'GRANT SELECT ON "'||R.owner||'"."'||R.table_name||'" TO "QAUser"';
END LOOP;
END;
See Oracle: What exactly do quotation marks around the table name do?

Oracle logoff trigger to get sql's executed by session

I have created a table which contains the host names of all the trusted sources. I have written a oracle log off trigger to fetch details of all the sql executed by that session if the connection's host is not amongst the snif_session table. I am taking the output to a utl_file output which contains the sid, hostname, time connected.
SQL> select * from snif_Session;
ALLOWED_HOST
--------------------------------------------------
RND1
WORKGROUP\RND1
The place where i am getting stuck is what query to use to get all the sql's executed by that particular session ( i can get the sid from v$mystat).
does this work best :
select a.sql_id
,b.sql_text
from dba_hist_active_sess_history a
,dba_hist_sqltext b
where a.sql_id=b.sql_id
or
select s.sid
, s.serial#
, a.sql_text
from v$session s
join v$sqlarea a
on a.hash_value = s.sql_hash_value ;
This is the code i have written (block) which i will be placing inside a trigger.
declare
machine_id varchar2(50);
val int;
auth_terminal varchar2(50);
check_machine varchar2(1000);
mydate char(50);
osuser_1 varchar2(50);
sid_1 int;
sql_query_1 varchar2(5000);
machine_1 varchar2(50);
trace_info UTL_FILE.FILE_TYPE;
begin
select machine into check_machine
from v$session
where sid in (select distinct(sid) from v$mystat) ;
select count(*) into val
from snif_session
where allowed_host=check_machine;
if ( 1=val) then
dbms_output.put_line(check_machine|| ' dont check host' );
else
dbms_output.put_line(check_machine || ' check host' );
end if;
select osuser,sid,machine
into osuser_1,sid_1,machine_1
from v$session
where sid in (select distinct(sid) from v$mystat);
SELECT TO_char(systimestamp,'mm/dd/yyyy HH24:MI:SS') into mydate
FROM DUAL;
dbms_output.put_line(mydate || sid_1 || ' ' || osuser_1 || ' '|| machine_1);
trace_info := UTL_FILE.FOPEN('UTL_DIR', 'trace_info_file.txt', 'W');
UTL_FILE.PUTF(trace_info,mydate||' '||sid_1||' '||osuser_1||' '|| machine_1);
UTL_FILE.FCLOSE(trace_info);
EXCEPTION
WHEN utl_file.invalid_path THEN
raise_application_error(-20000, 'ERROR: Invalid PATH FOR file.');
end;
I need to include the 'sql queries' also executed by session in the utl_file output.
"I need to include the 'sql queries' also executed by session"
Neither of your suggested queries will give you all the SQL executed by a session.
V$SESSION is a dynamic view, so it just shows what is happening in a session right now.
DBA_HIST_ACTIVE_SESS_HISTORY is a series of snapshots of running SQL. It is intended for performance profiling, and as such it is basically a random sub-set of active statements. Also, it is part of the Diagnostics and Tuning Pack: you will be in breach of your license if you use it without paying the additional charge.
It appears that what you really need is an audit trail. Instead of rolling your own, why not investigate the functionality Oracle already has? There's AUDIT to track DDL activity. There's Fine-grained Auditing to monitor lower-level DML. Find out more.

Check whether a tables exist or not on another schema - ORACLE DB

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

Grant Select, Insert, Update to a Tablespace

I've got a lot of tables in a tablespace, nearly 100. I have to grant Select, Insert, Update privileges on all those tables to a user. Is it possible? When I write:
GRANT USE OF TABLESPACE MYTABLESPACE TO USERNAME
I get oracle error "invalid or missing privilege"
USE OF TABLESPACE is not a documented option, where did you find that?
You can do this to allow a user to create objects in a tablespace:
alter user username quota [amount] on mytablespace;
To grant select, insert, update and delete on objects you have to run a separate grant command for each table:
grant select, insert, update, delete on mytable1 to username;
....
Use the data dictionary view dba_tables (resp. all_tables, if you cannot access dba_tables):
declare
l_SQL varchar2(4000);
begin
for cur in (
select * from dba_tables where tablespace_name = 'mytablespace')
loop
l_sql := 'grant select, insert, update on ' || cur.owner || '.' || cur.table_name || ' to myuser';
--dbms_output.put_line(l_SQL || ';');
execute immediate l_SQL;
end loop;
end;
If you just want to generate a script, comment out the execute immediate and un-comment the dbms_output.

Resources